基于tensorflow实现中文情感分类
项目简介:
利用自然语言处理技术,实现对输入文本的情感值计算,从而准备判断舆情。
项目流程:
数据收集
项目训练数据使用的是谭松波老师的酒店评论语料。
训练样本放置在两个文件夹内:pos和neg,每个文件夹有2000个txt文件,每个文件是一段评语,共有4000个训练样本。
预训练词向量
预训练词向量使用了北京师范大学中文信息处理研究所与中国人民大学 DBIIR 实验室的研究者开源的"chinese-word-vectors"
github链接为:
https://github.com/Embedding/Chinese-Word-Vectors建模流程
- # 3.1 首先加载必用的库
- %matplotlib inline
- # 魔法命令,使用后画图不用show了。
- import numpy as np
- import matplotlib.pyplot as plt
- import re # 引入正则
- import jieba # 结巴分词
- from gensim.models import KeyedVectors # gensim用来加载预训练词向量
- import warnings
- warnings.filterwarnings("ignore")
- import bz2 # 用来解压文件
- # 3.2 解压词向量
- with open("embeddings/sgns.zhihu.bigram", 'wb') as new_file, open("embeddings/sgns.zhihu.bigram.bz2", 'rb') as file:
- decompressor = bz2.BZ2Decompressor()
- for data in iter(lambda : file.read(100 * 1024), b''):
- new_file.write(decompressor.decompress(data))
- # 3.3 加载词向量
- cn_model = KeyedVectors.load_word2vec_format('embeddings/sgns.zhihu.bigram',
- binary=False,
- unicode_errors="ignore")
- # 3.4 语料预处理
- import os
- pos_txts = os.listdir('pos')
- neg_txts = os.listdir('neg')
- train_texts_orig = []
- train_target = []
- with open("positive_samples.txt", "r", encoding="utf-8") as f:
- lines = f.readlines()
- for line in lines:
- dic = eval(line)
- train_texts_orig.append(dic["text"])
- train_target.append(dic["label"])
- with open("negative_samples.txt", "r", encoding="utf-8") as f:
- lines = f.readlines()
- for line in lines:
- dic = eval(line)
- train_texts_orig.append(dic["text"])
- train_target.append(dic["label"])
- # 3.5 训练语料
- from tensorflow.python.keras.models import Sequential
- from tensorflow.python.keras.layers import Dense, GRU, Embedding, LSTM, Bidirectional
- from tensorflow.python.keras.preprocessing.text import Tokenizer
- from tensorflow.python.keras.preprocessing.sequence import pad_sequences
- from tensorflow.python.keras.optimizers import RMSprop
- from tensorflow.python.keras.optimizers import Adam
- from tensorflow.python.keras.callbacks import EarlyStopping, ModelCheckpoint, TensorBoard, ReduceLROnPlateau
- # 3.5.1 进行分词和tokenize,首先我们去掉每个样本的标点符号,然后用jieba分词,jieba分词返回一个生成器,没法直接进行tokenize,所以我们将分词结果转换成一个list,并将它索引化,这样每一例评价的文本变成一段索引数字,对应着预训练词向量模型中的词。
- # train_tokens是一个长长的list,其中含有4000个小list,对应每一条评价
- train_tokens = []
- for text in train_texts_orig:
- # 去掉标点
- text = re.sub("[\s+\.\!\/_,$%^*(+\"\']+|[+——!,。?、~@#¥%……&*()]+", "",text)
- # 结巴分词
- cut = jieba.cut(text)
- # 结巴分词的输出结果为一个生成器
- # 把生成器转换为list
- cut_list = [ i for i in cut ]
- for i, word in enumerate(cut_list): # enumerate()
- try:
- # 将词转换为索引index
- cut_list[i] = cn_model.vocab[word].index
- except KeyError:
- # 如果词不在字典中,则输出0
- cut_list[i] = 0
- train_tokens.append(cut_list)
- # 3.5.2 索引长度标准化,因为每段评语的长度是不一样的,我们如果单纯取最长的一个评语,并把其他评填充成同样的长度,这样十分浪费计算资源,所以我们取一个折衷的长度。
- # 获得所有tokens的长度
- num_tokens = [ len(tokens) for tokens in train_tokens ]
- num_tokens = np.array(num_tokens)
- # 取tokens平均值并加上两个tokens的标准差,
- # 假设tokens长度的分布为正态分布,则max_tokens这个值可以涵盖95%左右的样本
- max_tokens = np.mean(num_tokens) + 2 * np.std(num_tokens)
- max_tokens = int(max_tokens)
- max_tokens
- # 3.5.3 准备Embedding Matrix
- # 现在来为模型准备embedding matrix(词向量矩阵),根据keras的要求,需要准备一个维度为 (????????,????????????) 的矩阵,num words代表使用的词汇的数量,emdedding dimension在现在使用的预训练词向量模型中是300,每一个词汇都用一个长度为300的向量表示。
- # 注意只选择使用前50k个使用频率最高的词,在这个预训练词向量模型中,一共有260万词汇量,如果全部使用在分类问题上会很浪费计算资源,因为训练样本很小,一共只有4k,如果有100k,200k甚至更多的训练样本时,在分类问题上可以考虑减少使用的词汇量。
- # 只使用前50000个词
- num_words = 50000
- # 初始化embedding_matrix,之后在keras上进行应用
- embedding_matrix = np.zeros((num_words, embedding_dim))
- # embedding_matrix为一个 [num_words,embedding_dim] 的矩阵
- # 维度为 50000 * 300
- for i in range(num_words):
- embedding_matrix[i,:] = cn_model[cn_model.index2word[i]]
- embedding_matrix = embedding_matrix.astype('float32')
- # 检查index是否对应,
- # 输出300意义为长度为300的embedding向量一一对应
- np.sum( cn_model[cn_model.index2word[333]] == embedding_matrix[333] )
- # 3.5.4 padding(填充)和truncating(修剪)
- # 我们把文本转换为tokens(索引)之后,每一串索引的长度并不相等,所以为了方便模型的训练我们需要把索引的长度标准化,上面我们选择了236这个可以涵盖95%训练样本的长度,接下来我们进行padding和truncating,我们一般采用'pre'的方法,这会在文本索引的前面填充0,因为根据一些研究资料中的实践,如果在文本索引后面填充0的话,会对模型造成一些不良影响。
- # 进行padding和truncating, 输入的train_tokens是一个list
- # 返回的train_pad是一个numpy array
- train_pad = pad_sequences(train_tokens, maxlen=max_tokens,
- padding='pre', truncating='pre')
- # 超出五万个词向量的词用0代替
- train_pad[ train_pad>=num_words ] = 0
- # 准备target向量,前2000样本为1,后2000为0
- train_target = np.array(train_target)
- # 进行训练和测试样本的分割
- from sklearn.model_selection import train_test_split
- # 90%的样本用来训练,剩余10%用来测试
- X_train, X_test, y_train, y_test = train_test_split(train_pad,
- train_target,
- test_size=0.1,
- random_state=12)
- # 3.5.5 现在我们用keras搭建LSTM模型,模型的第一层是Embedding层,只有当我们把tokens索引转换为词向量矩阵之后,才可以用神经网络对文本进行处理。 keras提供了Embedding接口,避免了繁琐的稀疏矩阵操作。在Embedding层我们输入的矩阵为:(????ℎ????,?????????)输出矩阵为:(????ℎ????,?????????,????????????)
- # 用LSTM对样本进行分类
- model = Sequential()
- model.add(Embedding(num_words,
- embedding_dim,
- weights=[embedding_matrix],
- input_length=max_tokens,
- trainable=False))
- model.add(Bidirectional(LSTM(units=64, return_sequences=True)))
- model.add(LSTM(units=16, return_sequences=False))
- model.add(Dense(1, activation='sigmoid'))
- # 我们使用adam以0.001的learning rate进行优化
- optimizer = Adam(lr=1e-3)
- # 建立一个权重的存储点
- path_checkpoint = 'sentiment_checkpoint.keras'
- checkpoint = ModelCheckpoint(filepath=path_checkpoint, monitor='val_loss',
- verbose=1, save_weights_only=True,
- save_best_only=True)
- # 尝试加载已训练模型
- try:
- model.load_weights(path_checkpoint)
- except Exception as e:
- print(e)
- # 定义early stoping如果3个epoch内validation loss没有改善则停止训练
- earlystopping = EarlyStopping(monitor='val_loss', patience=5, verbose=1)
- # 自动降低learning rate
- lr_reduction = ReduceLROnPlateau(monitor='val_loss',
- factor=0.1, min_lr=1e-8, patience=0,
- verbose=1)
- # 定义callback函数
- callbacks = [
- earlystopping,
- checkpoint,
- lr_reduction
- ]
- # 开始训练
- model.fit(X_train, y_train,
- validation_split=0.1,
- epochs=2,
- batch_size=128,
- callbacks=callbacks)
- # 3.6 训练结果
- #我们首先对测试样本进行预测,得到了还算满意的准确度。之后我们定义一个预测函数,来预测输入的文本的极性,可见模型对于否定句和一些简单的逻辑结构都可以进行准确的判断。
- result = model.evaluate(X_test, y_test)
- print('Accuracy:{0:.2%}'.format(result[1]))
- def predict_sentiment(text):
- print(text)
- # 去标点
- text = re.sub("[\s+\.\!\/_,$%^*(+\"\']+|[+——!,。?、~@#¥%……&*()]+", "",text)
- # 分词
- cut = jieba.cut(text)
- cut_list = [ i for i in cut ]
- # tokenize
- for i, word in enumerate(cut_list):
- try:
- cut_list[i] = cn_model.vocab[word].index
- if cut_list[i] >= 50000:
- cut_list[i] = 0
- except KeyError:
- cut_list[i] = 0
- # padding
- tokens_pad = pad_sequences([cut_list], maxlen=max_tokens,
- padding='pre', truncating='pre')
- # 预测
- result = model.predict(x=tokens_pad)
- coef = result[0][0]
- if coef >= 0.5:
- print('是一例正面评价','output=%.2f'%coef)
- else:
- print('是一例负面评价','output=%.2f'%coef)
- test_list = [
- '酒店设施不是新的,服务态度很不好',
- '酒店卫生条件非常不好',
- '床铺非常舒适',
- '房间很凉,不给开暖气',
- '房间很凉爽,空调冷气很足',
- '酒店环境不好,住宿体验很不好',
- '房间隔音不到位' ,
- '晚上回来发现没有打扫卫生',
- '因为过节所以要我临时加钱,比团购的价格贵'
- ]
- for text in test_list:
- predict_sentiment(text)
应用场景:
淘宝点评分类、网络舆情监控、