由于处理的是字符串,我们首先要想方法把字符串转换为向量/数字表示。一种解决方法是可以把单词映射为数字ID。
第二个问题是每行评论字数不同,而神经网络需要一致的输入(其实有些神经网络不需要,至少本帖需要),这可以使用词汇表解决。
代码部分:
安装nltk(自然语言工具库 Natural
Language Toolkit)
pip install nltk
下载nltk数据:
控制台输入 Python,然后输入以下命令。
>>> import nltk
>>> nltk.download()
等待其安装完成;测试:
>>> from nltk.corpus import brown
>>> brown.words()
['The', 'Fulton', 'County', 'Grand', 'Jury', 'said', ...]
检测代码:
-
#coding=utf-8
-
import numpy as np
-
import tensorflow as tf
-
import random
-
import cPickle
-
from collections import Counter
-
-
import nltk
-
from nltk.tokenize import word_tokenize
-
-
-
"""
-
'I'm super man'
-
tokenize:
-
['I', ''m', 'super','man' ]
-
"""
-
from nltk.stem import WordNetLemmatizer
-
-
"""
-
词形还原(lemmatizer),即把一个任何形式的英语单词还原到一般形式,与词根还原不同(stemmer),后者是抽取一个单词的词根。
-
"""
-
-
pos_file = 'pos.txt'
-
neg_file = 'neg.txt'
-
-
-
# 创建词汇表
-
def create_lexicon(pos_file, neg_file):
-
lex = []
-
-
# 读取文件
-
def process_file(f):
-
with open(pos_file, 'rb') as f:
-
lex = []
-
lines = f.readlines()
-
# print(lines)
-
for line in lines:
-
words = word_tokenize(line.lower())
-
lex += words
-
return lex
-
-
lex += process_file(pos_file)
-
lex += process_file(neg_file)
-
# print(len(lex))
-
lemmatizer = WordNetLemmatizer()
-
lex = [lemmatizer.lemmatize(word) for word in lex] # 词形还原 (cats->cat)
-
-
word_count = Counter(lex)
-
# print(word_count)
-
# {'.': 13944, ',': 10536, 'the': 10120, 'a': 9444, 'and': 7108, 'of': 6624, 'it': 4748, 'to': 3940......}
-
# 去掉一些常用词,像the,a and等等,和一些不常用词; 这些词对判断一个评论是正面还是负面没有做任何贡献
-
lex = []
-
for word in word_count:
-
if word_count[word] < 2000 and word_count[word] > 20: # 这写死了,好像能用百分比
-
lex.append(word) # 齐普夫定律-使用Python验证文本的Zipf分布 http://blog.topspeedsnail.com/archives/9546
-
return lex
-
-
-
lex = create_lexicon(pos_file, neg_file)
-
-
-
# lex里保存了文本中出现过的单词。
-
-
# 把每条评论转换为向量, 转换原理:
-
# 假设lex为['woman', 'great', 'feel', 'actually', 'looking', 'latest', 'seen', 'is'] 当然实际上要大的多
-
# 评论'i think this movie is great' 转换为 [0,1,0,0,0,0,0,1], 把评论中出现的字在lex中标记,出现过的标记为1,其余标记为0
-
def normalize_dataset(lex):
-
dataset = []
-
-
# lex:词汇表;review:评论;clf:评论对应的分类,[0,1]代表负面评论 [1,0]代表正面评论
-
def string_to_vector(lex, review, clf):
-
words = word_tokenize(line.lower())
-
lemmatizer = WordNetLemmatizer()
-
words = [lemmatizer.lemmatize(word) for word in words]
-
-
features = np.zeros(len(lex))
-
for word in words:
-
if word in lex:
-
features[lex.index(word)] = 1 # 一个句子中某个词可能出现两次,可以用+=1,其实区别不大
-
return [features, clf]
-
-
with open(pos_file, 'r') as f:
-
lines = f.readlines()
-
for line in lines:
-
one_sample = string_to_vector(lex, line, [1, 0]) # [array([ 0., 1., 0., ..., 0., 0., 0.]), [1,0]]
-
dataset.append(one_sample)
-
with open(neg_file, 'r') as f:
-
lines = f.readlines()
-
for line in lines:
-
one_sample = string_to_vector(lex, line, [0, 1]) # [array([ 0., 0., 0., ..., 0., 0., 0.]), [0,1]]]
-
dataset.append(one_sample)
-
-
# print(len(dataset))
-
return dataset
-
-
-
dataset = normalize_dataset(lex)
-
random.shuffle(dataset)
-
"""
-
#把整理好的数据保存到文件,方便使用。到此完成了数据的整理工作
-
with open('save.cPickle', 'wb') as f:
-
cPickle.dump(dataset, f)
-
"""
-
-
# 取样本中的10%做为测试数据
-
test_size = int(len(dataset) * 0.1)
-
-
dataset = np.array(dataset)
-
-
train_dataset = dataset[:-test_size]
-
test_dataset = dataset[-test_size:]
-
-
# Feed-Forward Neural Network
-
# 定义每个层有多少'神经元''
-
n_input_layer = len(lex) # 输入层
-
-
n_layer_1 = 1000 # hide layer
-
n_layer_2 = 1000 # hide layer(隐藏层)听着很神秘,其实就是除输入输出层外的中间层
-
-
n_output_layer = 2 # 输出层
-
-
-
# 定义待训练的神经网络
-
def neural_network(data):
-
# 定义第一层"神经元"的权重和biases
-
layer_1_w_b = {'w_': tf.Variable(tf.random_normal([n_input_layer, n_layer_1])),
-
'b_': tf.Variable(tf.random_normal([n_layer_1]))}
-
# 定义第二层"神经元"的权重和biases
-
layer_2_w_b = {'w_': tf.Variable(tf.random_normal([n_layer_1, n_layer_2])),
-
'b_': tf.Variable(tf.random_normal([n_layer_2]))}
-
# 定义输出层"神经元"的权重和biases
-
layer_output_w_b = {'w_': tf.Variable(tf.random_normal([n_layer_2, n_output_layer])),
-
'b_': tf.Variable(tf.random_normal([n_output_layer]))}
-
-
# w·x+b
-
layer_1 = tf.add(tf.matmul(data, layer_1_w_b['w_']), layer_1_w_b['b_'])
-
layer_1 = tf.nn.relu(layer_1) # **函数
-
layer_2 = tf.add(tf.matmul(layer_1, layer_2_w_b['w_']), layer_2_w_b['b_'])
-
layer_2 = tf.nn.relu(layer_2) # **函数
-
layer_output = tf.add(tf.matmul(layer_2, layer_output_w_b['w_']), layer_output_w_b['b_'])
-
-
return layer_output
-
-
-
# 每次使用50条数据进行训练
-
batch_size = 50
-
-
X = tf.placeholder('float', [None, len(train_dataset[0][0])])
-
# [None, len(train_x)]代表数据数据的高和宽(矩阵),好处是如果数据不符合宽高,tensorflow会报错,不指定也可以。
-
Y = tf.placeholder('float')
-
-
-
# 使用数据训练神经网络
-
def train_neural_network(X, Y):
-
predict = neural_network(X)#得到预测结果(通过神经网络)
-
cost_func = tf.reduce_mean(tf.nn.softmax_cross_entropy_with_logits(predict, Y))#定义损失函数
-
optimizer = tf.train.AdamOptimizer().minimize(cost_func) # learning rate 默认 0.001
-
-
epochs = 13#13次整体迭代
-
with tf.Session() as session:
-
session.run(tf.initialize_all_variables())
-
epoch_loss = 0
-
-
i = 0
-
random.shuffle(train_dataset)#数据集随机
-
train_x = dataset[:, 0]#train_x
-
train_y = dataset[:, 1]#标签
-
#训练过程
-
for epoch in range(epochs):
-
while i < len(train_x):
-
start = i
-
end = i + batch_size#训练了一个batch大小
-
-
batch_x = train_x[start:end]
-
batch_y = train_y[start:end]
-
-
_, c = session.run([optimizer, cost_func], feed_dict={X: list(batch_x), Y: list(batch_y)})
-
epoch_loss += c#一个epoch内一个batch的损失
-
i += batch_size#下一个开始位置
-
-
print(epoch, ' : ', epoch_loss)#输出 所有数据后的一次迭代
-
-
text_x = test_dataset[:, 0]#测试数据
-
text_y = test_dataset[:, 1]#标签
-
correct = tf.equal(tf.argmax(predict, 1), tf.argmax(Y, 1))
-
accuracy = tf.reduce_mean(tf.cast(correct, 'float'))
-
print('准确率: ', accuracy.eval({X: list(text_x), Y: list(text_y)}))
-
-
-
train_neural_network(X, Y)
执行结果:

这个结果,说明效率并不高,当然你每次运行时结果是不一样的,但最高也就70%+。
那么问题出在哪呢?
准确率低主要是因为数据量太小,同样的模型,如果使用超大数据训练,准确率会有显著的提升。
下文我会使用同样的模型,但是数据量要比本文使用的多得多,看看准确率能提高多少。由于本文使用的神经网络模型(feed-forward)过于简单,使用大数据也不一定有质的提升,尤其是涉及到自然语言处理。
很多自然语言处理(NLP)的任务,包括分词、词性标注、命名实体识别(NER)及句法分析。
一 nltk安装教程
首先,保证已经安装成功python。然后终端输入命令:pip install nltk;安装完成后,输入import nltk了,然后输入nltk.download(),这样就可以打开一个NLTK Downloader(NLTK下载器)。(具体安装过程:http://www.pythontip.com/blog/post/10011/)
成功安装后,测试。输入下边的语句就可以:
>>> from nltk.corpus import brown
>>> brown.words()
['The', 'Fulton', 'County', 'Grand', 'Jury', 'said', ...]
二 NLTK进行分词
使用的函数:
nltk.sent_tokenize(text) #对文本按照句子进行分割
nltk.word_tokenize(sent) #对句子进行分词

三 NLTK进行词性标注
用到的函数:
nltk.pos_tag(tokens)#tokens是句子分词后的结果,同样是句子级的标注

四 NLTK进行命名实体识别(NER)
用到的函数:
nltk.ne_chunk(tags)#tags是句子词性标注后的结果,同样是句子级

上例中,有两个命名实体,一个是Xi,这个应该是PER,被错误识别为GPE了; 另一个事China,被正确识别为GPE。
五 句法分析
nltk没有好的parser,推荐使用stanfordparser
但是nltk有很好的树类,该类用list实现
可以利用stanfordparser的输出构建一棵python的句法树

六 词干提取(stemming)
解释一下,Stemming 是抽取词的词干或词根形式(不一定能够表达完整语义)。NLTK中提供了三种最常用的词干提取器接口,即 Porter stemmer, Lancaster Stemmer 和 Snowball Stemmer。
Porter Stemmer基于Porter词干提取算法,来看例子:
-
>>> from nltk.stem.porter import PorterStemmer
-
>>> porter_stemmer = PorterStemmer()
-
>>> porter_stemmer.stem(‘maximum’)
-
u’maximum’
-
>>> porter_stemmer.stem(‘presumably’)
-
u’presum’
-
>>> porter_stemmer.stem(‘multiply’)
-
u’multipli’
-
>>> porter_stemmer.stem(‘provision’)
-
u’provis’
-
>>> porter_stemmer.stem(‘owed’)
-
u’owe’
Lancaster Stemmer 基于Lancaster 词干提取算法,来看例子
-
>>> from nltk.stem.lancaster import LancasterStemmer
-
>>> lancaster_stemmer = LancasterStemmer()
-
>>> lancaster_stemmer.stem(‘maximum’)
-
‘maxim’
-
>>> lancaster_stemmer.stem(‘presumably’)
-
‘presum’
-
>>> lancaster_stemmer.stem(‘presumably’)
-
‘presum’
-
>>> lancaster_stemmer.stem(‘multiply’)
-
‘multiply’
-
>>> lancaster_stemmer.stem(‘provision’)
-
u’provid’
-
>>> lancaster_stemmer.stem(‘owed’)
-
‘ow’
Snowball Stemmer基于Snowball 词干提取算法,来看例子
-
>>> from nltk.stem import SnowballStemmer
-
>>> snowball_stemmer = SnowballStemmer(“english”)
-
>>> snowball_stemmer.stem(‘maximum’)
-
u’maximum’
-
>>> snowball_stemmer.stem(‘presumably’)
-
u’presum’
-
>>> snowball_stemmer.stem(‘multiply’)
-
u’multipli’
-
>>> snowball_stemmer.stem(‘provision’)
-
u’provis’
-
>>> snowball_stemmer.stem(‘owed’)
-
u’owe’
七 词形还原(lemmatization) Lemmatisation是把一个任何形式的语言词汇还原为一般形式(能表达完整语义)。相对而言,词干提取是简单的轻量级的词形归并方式,最后获得的结果为词干,并不一定具有实际意义。词形还原处理相对复杂,获得结果为词的原形,能够承载一定意义,与词干提取相比,更具有研究和应用价值。
我们会在后面给出一个同MaxMatch算法相结合的更为复杂的例子。
八 最大匹配算法(MaxMatch)
MaxMatch算法在中文自然语言处理中常常用来进行分词(或许从名字上你已经能想到它是基于贪婪策略设计的一种算法)。通常,英语中一句话里的各个词汇之间通过空格来分割,这是非常straightforward的,但是中文却没有这个遍历。例如“我爱中华人民共和国”,这句话被分词的结果可能是这样的{‘我’,‘爱’,‘中华’,‘人民’,‘共和国’},又或者是{‘我’,‘爱’,‘中华人民共和国’},显然我们更倾向于后者的分词结果。因为‘中华人民共和国’显然是一个专有名词(把这样一个词分割来看显然并不明智)。我们选择后者的策略就是所谓的MaxMatch,即最大匹配。因为‘中华人民共和国’这个词显然要比‘中华’,‘人民’,‘共和国’这些词都长。
我们可以通过一个英文的例子来演示MaxMatch算法(其实中文处理的道理也是一样的)。算法从右侧开始逐渐减少字符串长度,以此求得可能匹配的最大长度的字符串。考虑到我们所获得的词汇可能包含有某种词型的变化,所以其中使用了Lemmatisation,然后在词库里进行匹配查找。
-
from nltk.stem import WordNetLemmatizer
-
from nltk.corpus import words
-
-
wordlist = set(words.words())
-
wordnet_lemmatizer = WordNetLemmatizer()
-
-
def max_match(text):
-
pos2 = len(text)
-
result = ''
-
while len(text) > 0:
-
word = wordnet_lemmatizer.lemmatize(text[0:pos2])
-
if word in wordlist:
-
result = result + text[0:pos2] + ' '
-
text = text[pos2:]
-
pos2 = len(text)
-
else:
-
pos2 = pos2-1
-
return result[0:-1]
来看看算法的实现效果
-
>>> string = 'theyarebirds'
-
>>> print(max_match(string))
-
they are birds