就这?word2vec+BiLSTM实现中英文情感分类代码详解
作者:互联网
前言
讲道理,这篇博客应该可以帮助很多只有一点点NLP的朋友,在较短的时间内了解文本分类的整个过程并用代码复现整个流程。事先说明,这里大家先不要过分要求自己去理解整个模型的原理,先搞清楚整个实现流程,体验一下敲代码并成功应用的快感。
实现流程
找数据集
首先第一步,就是要找好数据集,没有数据集模型怎么学习,怎么涨知识。
那这里呢,我们采用的情感数据集是weibo_senti_100k数据集,一共有119988条带情感标注的新浪微博评论,其中正负向评论均为 59994条,非常平衡的一个数据集。
其中label为文本标签,1代表正面评论,0代表负面评论。
数据观察
找完数据集之后呢,我们需要观察对数据集进行观察,判定它是否会不适合我们后续的任务,以及数据集的文本长度大致如何。
这里我们先利用提取关键词的思想,生成一个词云图,用于观察该数据集的热门词。
import os
import numpy as np
import pandas as pd
import jieba.analyse
from jieba import analyse
from PIL import Image
import matplotlib.pyplot as plt
from matplotlib.font_manager import FontProperties
from wordcloud import WordCloud,ImageColorGenerator
if __name__ == '__main__':
data = pd.read_csv('weibo_senti_100k.csv', header=0, encoding='utf-8-sig')['review']
tfidf = analyse.extract_tags
for line in data:
if line.strip() == '':
continue
text = line
# tfidf 仅仅从词的统计信息出发,而没有充分考虑词之间的语义信息
keywords = tfidf(text,
allowPOS=('ns', 'nr', 'nt', 'nz', 'nl', 'n', 'vn', 'vd', 'vg', 'v', 'vf', 'a', 'an', 'i'))
result = []
for keyword in keywords:
result.append(keyword)
fo = open('keywords.txt', "a+", encoding='utf-8') # 生成每句文本的关键词
for j in result:
fo.write(j)
fo.write(' ')
fo.write('\n')
fo.close()
# 开始生成词云图
lyric = ''
f = open('keywords.txt', 'r', encoding='utf-8')
for i in f:
lyric += f.read()
# print(lyric)#自动加+'\n'
# 考虑了相邻词的语义关系、基于图排序的关键词提取算法TextRank
result = jieba.analyse.textrank(lyric, topK=50, withWeight=True)
keywords = dict()
for i in result:
keywords[i[0]] = i[1]
print(keywords)
image = Image.open('./background.png')
graph = np.array(image)
print(graph)
wc = WordCloud(font_path='SIMLI.TTF', background_color='White', max_words=200, mask=graph)
wc.generate_from_frequencies(keywords)
image_color = ImageColorGenerator(graph) # 设置背景图像
plt.imshow(wc) # 画图
plt.imshow(wc.recolor(color_func=image_color)) # 根据背景图片着色
plt.axis("off") # 不显示坐标轴
plt.show()
wc.to_file('wordCloud.png')
通过这个词云图,我们可以发现这个数据集并没有明显的领域偏向,也就是说它并不是单独在某一个领域下的文本内容。比方说,如果该数据集是汽车领域的文本内容,那么当使用该数据集进行其他领域的情感标注时的主准确率就会变得相对较低。
在进行实验之前,我进行了对数据集、爬取的微博文本以及知乎文本的长度与对应的数量进行了统计,大致结果如图所示。
这里我以词为句子长度的基本单位,对数据集的句子长度进行了统计。不难发现的是,数据集的句子长度主要集中在了20附近,当长度超过100时,句子数量就几乎没有了。
数据预处理
首先,我们可以观察文本的内容,看是否有一些文本内容是对最终的情感标注没有影响甚至会有干扰的。假设我们在后续操作做自己爬取了数据的话,数据多少都会存在一些问题,比方说一些没有意义的标签符号。或者你发现你的任务是对长文本进行情感分类,但是数据集是短文本的时候,是否需要对这些长文本进行文本摘要。
接着,我们需要进行文本分词,这里我们用到的是结巴分词。同时我们也可以加载一些语料库,提高分词的准确性。
import jieba
jieba.load_userdict("./dict/SogouLabDic.txt")
jieba.load_userdict("./dict/dict_baidu_utf8.txt")
jieba.load_userdict("./dict/dict_pangu.txt")
jieba.load_userdict("./dict/dict_sougou_utf8.txt")
jieba.load_userdict("./dict/dict_tencent_utf8.txt")
#jieba.load_userdict("my_dict.txt")
def tokenizer(dataset_texts):
text = [jieba.lcut(document.replace('\n', '')) for document in dataset_texts]
return text
需要注意的是,在很多文本分类的任务中,都会选择去除停用词。但是在情感分类中,也往往会选择不去除停用词。比方说“我可以!!!”和“我可以。”这两句话表达的情感差异是比较大的。当然啦,是否需要去除停用词,最好还是做下对比实验。
文本特征提取
分完词之后,就是准备将数据扔到模型训练啦。但是,模型可认不得自然语言,你需要将这些文本转换成模型认识的数据格式——矩阵。所以,我们需要对文本进行特征提取,即将文本转换成矩阵的形式。这里就需要用到一些方法啦,比方说传统的平权统计的方法、TF-IDF、BOW等。那在这里我们用到的方法是Word2vec(单词to向量)的方法。
Word2vec,是一群用来产生词向量的相关模型。这些模型为浅而双层的神经网络,用来训练以重新建构语言学之词文本。网络以词表现,并且需猜测相邻位置的输入词,在word2vec中词袋模型假设下,词的顺序是不重要的。训练完成之后,word2vec模型可用来映射每个词到一个向量,可用来表示词对词之间的关系,该向量为神经网络之隐藏层。通过Word2Vec算法得到每个词语的高维向量(词向量,Word Embedding)表示,词向量把相近意思的词语放在相近的位置。我们只需要有大量的某语言的语料,就可以用它来训练模型,获得词向量。
那么在这里呢,我们就用数据集的文本来训练word2vec。
from gensim.models.word2vec import Word2Vec
cpu_count = multiprocessing.cpu_count()
vocab_dim = 100
n_iterations = 1
n_exposures = 10 # 所有频数超过10的词语
window_size = 7
def word2vec_train(data):
"""训练word2vec模型
Parameters
----------
model : 分词后的二维列表
"""
model = Word2Vec(size=vocab_dim,
min_count=n_exposures,
window=window_size,
workers=cpu_count,
iter=n_iterations)
model.build_vocab(data) # input: list
model.train(combined, total_examples=model.corpus_count, epochs=model.iter)
model.save('Word2vec_model.pkl')
此时我们已经得到了训练好的word2vec模型,即可以得到词语对应的词向量。到这里的话,文本特征提取就可以算结束了。但是由于标注是针对整一句话的,而非单独的一个词,我们需要求每一句话的句向量。
from keras.preprocessing import sequence
from gensim.corpora.dictionary import Dictionary
def create_dictionaries(model=None, data=None):
"""将数据集文本转换成句向量,并得到两个词典(单词to序号、单词to向量)
Parameters
----------
model : 分词后的二维列表
data : 分词后的文本列表
Returns
-------
w2indx
单词映射成序号的词典
w2vec
单词映射成向量的词典
data
数据集文本特征矩阵
"""
if (data is not None) and (model is not None):
gensim_dict = Dictionary()
gensim_dict.doc2bow(model.wv.vocab.keys(),
allow_update=True)
# freqxiao10->0 所以k+1
w2indx = {v: k + 1 for k, v in gensim_dict.items()} # 所有频数超过10的词语的索引,(k->v)=>(v->k)
f = open("word2index.txt", 'w', encoding='utf8')
for key in w2indx:
f.write(str(key))
f.write(' ')
f.write(str(w2indx[key]))
f.write('\n')
f.close()
w2vec = {word: model[word] for word in w2indx.keys()} # 所有频数超过10的词语的词向量, (word->model(word))
def parse_dataset(combined):
data = []
for sentence in combined:
new_txt = []
for word in sentence:
try:
new_txt.append(w2indx[word])
except:
new_txt.append(0)
data.append(new_txt)
return data # word=>index
data = parse_dataset(data)
data = sequence.pad_sequences(data, maxlen=maxlen) # 每个句子所含词语对应的索引,所以句子中含有频数小于10的词语,索引为0
return w2indx, w2vec, data
else:
print('No data provided...')
为了满足后续建模的格式要求,这里采用的句特征的表示方法并非这种:[[词1向量], [词2向量], [词3向量], …, [词n向量]],这种表示方式的话,句特征就是一个二维的矩阵,而最终的表示方式为:[词1序号, 词2序号, 词3序号, …, 词n序号],此时句特征就成了一个向量。
另外,由于模型对于输入格式的要求必须是矩阵的形式,那么就要求每个句向量的长度必须统一,但是实际上的句子长度并非统一的形式,所以我们设置了一个maxlen作为句子的最大长度值(这个值的选择可以参考我们在数据观察时得到的数据),当maxlen取100时,句子大于100的话就需要进行切割,句子小于100时,则进行填充(通常是直接补零),这里是直接调用了pad_sequences方法。
划分训练集
import numpy as np
import keras
from sklearn.model_selection import train_test_split
def get_data(w2indx, w2vec, data, y):
n_symbols = len(w2indx) + 1 # 所有单词的索引数,频数小于10的词语索引为0,所以加1
embedding_weights = np.zeros((n_symbols, vocab_dim)) # 初始化 索引为0的词语,词向量全为0
for word, index in w2indx.items(): # 从索引为1的词语开始,对每个词语对应其词向量
embedding_weights[index, :] = w2vec[word]
x_train, x_test, y_train, y_test = train_test_split(data, y, test_size=0.2)
y_train = keras.utils.to_categorical(y_train, num_classes=2) # 转换为对应one-hot 表示 [len(y),2]
y_test = keras.utils.to_categorical(y_test, num_classes=2)
# print x_train.shape,y_train.shape
return n_symbols, embedding_weights, x_train, y_train, x_test, y_test
这里是将数据集按8:2的比例划分成了训练集和测试集,并且得到了一个词典,该词典的关键词为词序号,对应的值为词向量。
建立模型
采用的损失函数为都是交叉熵损失函数,使用Adam进行优化。
from keras.models import Sequential
from keras.layers import Bidirectional
from keras.layers.embeddings import Embedding
from keras.layers.recurrent import LSTM
from keras.layers.core import Dense, Dropout
def train_bilstm(n_symbols, embedding_weights, x_train, y_train):
print('Defining a Simple Keras Model...')
model = Sequential() # or Graph or whatever
model.add(Embedding(output_dim=vocab_dim,
input_dim=n_symbols,
mask_zero=True,
weights=[embedding_weights],
input_length=maxlen)) # Adding Input Length
model.add(Bidirectional(LSTM(output_dim=50, activation='tanh')))
model.add(Dropout(0.5))
model.add(Dense(2, activation='softmax')) # Dense=>全连接层,输出维度=2
model.compile(loss='categorical_crossentropy',
optimizer='adam', metrics=['accuracy'])
model.fit(x_train, y_train, batch_size=batch_size, epochs=n_epoch, verbose=2)
model.save('bilstm.h5')
train_bilstm(n_symbols, embedding_weights, x_train, y_train)
模型评估
from keras.models import load_model
from sklearn.metrics import classification_report
if __name__ == '__main__':
model = load_model('bilstm.h5')
y_pred = model.predict(x_test)
for i in range(len(y_pred)):
max_value = max(y_pred[i])
for j in range(len(y_pred[i])):
if max_value == y_pred[i][j]:
y_pred[i][j] = 1
else:
y_pred[i][j] = 0
# target_names = ['负面', '正面']
print(classification_report(y_test, y_pred))
自己玩玩
大家可以仔细看看下面的动图,没有别的意思(不是,我做了一个简单的图形界面,方便我们直接使用。
这篇是中文的文本分类,后面我也会出一篇关于英文文本情感分类的博客,以及使用TextCNN模型、CNN+BiLSTM模型、支持向量机模型来做分类的博客。
共勉!
标签:word2vec,中英文,BiLSTM,train,import,model,文本,data,向量 来源: https://blog.csdn.net/qq_44186838/article/details/117995029