大家好,又见面了,我是你们的朋友全栈君。
代码(包括数据集)链接放在文末。
本人在大三期间做了一个关于“疫苗接种”主题的舆情分析,主要涉及的技术有:爬虫(微博和知乎评论)、数据清洗、文本特征提取、建立模型(SVM、BiLSTM、TextCNN、CNN+BiLSTM、BiLSTM+Attention)、文本摘要等。
本篇主要会涉及到关于数据清洗、文本特征提取以及建模(BiLSTM、TextCNN、CNN+BiLSTM、BiLSTM+Attention)几个部分,剩下的内容可以查看我另外几篇博客。
文本特征提取:常见的文本特征(句向量)提取方法有哪些?什么是One-Hot、TF-IDF?word2vec如何训练?【Python】
SVM:轻松搞懂word2vec+SVM(支持向量机)实现中英文情感分类
爬虫:我爬取了知乎和微博上网友们在热门话题讨论的内容,并对其进行了情感分析和关键词提取
英文文本:【TF-IDF、word2vec、svm、cnn、textcnn、bilstm、cnn+bilstm、bilstm+attention】英文长文本分类实战
前言
讲道理,这篇博客应该可以帮助很多只有一点点NLP的朋友,在较短的时间内了解文本分类的整个过程并用代码复现整个流程。事先说明,这里大家先不要过分要求自己去理解整个模型的原理,先搞清楚整个实现流程,体验一下敲代码并成功应用的快感。
模型简介
Bi-LSTM
LSTM的全称是Long Short-Term Memory,它是RNN(Recurrent Neural Network)的一种。LSTM由于其设计的特点,非常适合用于对时序数据的建模,如文本数据,该模型可以学习长期依赖信息,它通过特殊的门结构来去除或增加信息到细胞状态的能力,门是一种让信息选择式通过的方法,一共有三个门。第一层是忘记门,决定我们会从细胞状态中丢弃什么信息,下一步是确定什么样的新信息会被存放在细胞状态,下一步是更新细胞状态,最后输出信息。
而Bi-LSTM模型由前向的LSTM和后向的LSTM组合而成,能够很好的处理双向数据的序列信息。一个LSTM层式从左往右处理,而另一个是从右往左处理。总体而言,更适合应用于长文本的分类任务。
TextCNN
TextCNN模型是由 Yoon Kim提出的Convolutional Naural Networks for Sentence Classification一文中提出的使用卷积神经网络来处理NLP问题的模型。TextCnn在文本分类问题上有着更加卓越的表现。从直观上理解,TextCNN通过一维卷积来获取句子中n-gram的特征表示。TextCNN对文本浅层特征的抽取能力很强,在短文本领域如搜索、对话领域专注于意图分类时效果很好,应用广泛,且速度快,一般是首选;对长文本领域,TextCNN主要靠filter窗口抽取特征,在长距离建模方面能力受限,且对语序不敏感。
CNN+BiLSTM
在一些任务当中,会选择在卷积层后加上一层LSTM或BiLSTM(反过来则不行),用于增强模型对语义的理解。CNN负责提取文本的特征,而BiLSTM负责理解句子的语义信息。当CNN融合了循环神经网络时,就是结合了这两者的功能,往往效果会有所提升。
PS:不要想着拿TextCNN去融合BiLSTM,不是说不能融合,是没有必要。TextCNN本身就是一个非常优秀的模型了,在TextCNN后面加上一层循环神经网络,往往只是带来了更多的计算时间,其本身对于语义的理解,并没有什么帮助,甚至有可能对结果进行干扰。
BiLSTM+注意力机制
当输入的文本非常长的时候,我们之前引以为傲的双向长短期记忆模型也难以成为学霸,对文本有一个很好的向量表达。所以,这个时候就可以考虑使用注意力机制,来尝试抓住文本的重点。具体来讲,Attention机制就是通过保留BiLSTM编码器对输入序列的中间输出结果,再训练一个模型来对这些输入进行选择性的学习并且在模型输出时将输出序列与之进行关联。
实现流程
(1) 找数据集
首先第一步,就是要找好数据集,没有数据集模型怎么学习,怎么涨知识。
那这里呢,我们采用的情感数据集是weibo_senti_100k数据集,一共有119988条带情感标注的新浪微博评论,其中正负向评论均为 59994条,非常平衡的一个数据集。
其中label为文本标签,1代表正面评论,0代表负面评论。
(2) 数据观察
找完数据集之后呢,我们需要观察对数据集进行观察,判定它是否会不适合我们后续的任务,以及数据集的文本长度大致如何。
这里我们先利用提取关键词的思想,生成一个词云图,用于观察该数据集的热门词。
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时,句子数量就几乎没有了。
(3) 数据预处理
首先,我们可以观察文本的内容,看是否有一些文本内容是对最终的情感标注没有影响甚至会有干扰的。假设我们在后续操作做自己爬取了数据的话,数据多少都会存在一些问题,比方说一些没有意义的标签符号。或者你发现你的任务是对长文本进行情感分类,但是数据集是短文本的时候,是否需要对这些长文本进行文本摘要。(这里我们就不对数据集的文本进行清洗,毕竟人家标注就是针对整个文本标注好的)
接着,我们需要进行文本分词,这里我们用到的是结巴分词。同时我们也可以加载一些语料库,提高分词的准确性。
import jieba
jieba.load_userdict("SogouLabDic.txt")
jieba.load_userdict("dict_baidu_utf8.txt")
jieba.load_userdict("dict_pangu.txt")
jieba.load_userdict("dict_sougou_utf8.txt")
jieba.load_userdict("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
需要注意的是,在很多文本分类的任务中,都会选择去除停用词。但是在情感分类中,也往往会选择不去除停用词。比方说“我可以!!!”和“我可以。”这两句话表达的情感差异是比较大的。当然啦,是否需要去除停用词,最好还是做下对比实验。
(4) 文本特征提取
分完词之后,就是准备将数据扔到模型训练啦。但是,模型可认不得自然语言,你需要将这些文本转换成模型认识的数据格式——矩阵。所以,我们需要对文本进行特征提取,即将文本转换成矩阵的形式。这里就需要用到一些方法啦,比方说传统的平权统计的方法、TF-IDF、BOW等。那在这里我们用到的方法是Word2vec的方法。
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 ---------- data : 分词后的二维列表 """
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 : 训练好的word2vec模型 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的词语的索引
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的词语的词向量
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)
return w2indx, w2vec, data
else:
print('文本为空!')
为了满足后续建模的格式要求,这里采用的句特征的表示方法并非这种:[[词1向量], [词2向量], [词3向量], …, [词n向量]],这种表示方式的话,句特征就是一个二维的矩阵,而最终的表示方式为:[词1序号, 词2序号, 词3序号, …, 词n序号],此时句特征就成了一个向量。
另外,由于模型对于输入格式的要求必须是矩阵的形式,那么就要求每个句向量的长度必须统一,但是实际上的句子长度并非统一的形式,所以我们设置了一个maxlen作为句子的最大长度值(这个值的选择可以参考我们在数据观察时得到的数据),当maxlen取100时,句子大于100的话就需要进行切割,句子小于100时,则进行填充(通常是直接补零),这里是直接调用了pad_sequences方法。
注:如果想将word2vec换成fastText,只需要将
from gensim.models.word2vec import Word2Vec
换成
from gensim.models.fasttext import FastText
再将对应位置的Word2Vec换成FastText即可。
(5) 划分训练集
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 # 补上索引为0(频数小于10)的词
embedding_weights = np.zeros((n_symbols, vocab_dim))
for word, index in w2indx.items():
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特征
y_test = keras.utils.to_categorical(y_test, num_classes=2)
return n_symbols, embedding_weights, x_train, y_train, x_test, y_test
这里是将数据集按8:2的比例划分成了训练集和测试集,并且得到了一个词典,该词典的关键词为词序号,对应的值为词向量。
(6) 建立模型
采用的损失函数为都是交叉熵损失函数,使用Adam进行优化。
BiLSTM代码复现如下:
from keras.models import Sequential
from keras.models import load_model
from keras.layers import Bidirectional, Activation
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):
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))
model.add(Bidirectional(LSTM(output_dim=50, activation='tanh')))
model.add(Dropout(0.5))
model.add(Dense(2, activation='softmax'))
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)
TextCNN代码复现如下:
import keras
from keras import Input, Model
from keras.preprocessing import sequence
from keras.models import load_model
from keras.layers import Conv1D, MaxPooling1D, concatenate
from keras.layers.embeddings import Embedding
from keras.layers.core import Dense, Dropout, Flatten
def train_textcnn(n_symbols, embedding_weights, x_train, y_train):
main_input = Input(shape=(maxlen,), dtype='float64')
embedder = Embedding(output_dim=vocab_dim,
input_dim=n_symbols,
input_length=maxlen,
weights=[embedding_weights])
embed = embedder(main_input)
cnn1 = Conv1D(256, 3, padding='same', strides=1, activation='relu')(embed)
cnn1 = MaxPooling1D(pool_size=38)(cnn1)
cnn2 = Conv1D(256, 4, padding='same', strides=1, activation='relu')(embed)
cnn2 = MaxPooling1D(pool_size=37)(cnn2)
cnn3 = Conv1D(256, 5, padding='same', strides=1, activation='relu')(embed)
cnn3 = MaxPooling1D(pool_size=36)(cnn3)
# 合并三个输出向量
cnn = concatenate([cnn1, cnn2, cnn3], axis=-1)
flat = Flatten()(cnn)
drop = Dropout(0.5)(flat)
main_output = Dense(2, activation='softmax')(drop)
model = Model(inputs=main_input, outputs=main_output)
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('textcnn.h5')
train_textcnn(n_symbols, embedding_weights, x_train, y_train)
CNN+BiLSTM代码复现如下:
import keras
from keras import Input, Model
from keras.preprocessing import sequence
from keras.models import load_model
from keras.layers import Conv1D, MaxPooling1D, concatenate
from keras.layers import Bidirectional
from keras.layers.embeddings import Embedding
from keras.layers.recurrent import LSTM
from keras.layers.core import Dense, Dropout, Flatten
def train_cnn_bilstm(n_symbols, embedding_weights, x_train, y_train):
main_input = Input(shape=(maxlen,), dtype='float64')
embedder = Embedding(output_dim=vocab_dim,
input_dim=n_symbols,
input_length=maxlen,
weights=[embedding_weights])
embed = embedder(main_input)
cnn = Conv1D(64, 3, padding='same', strides=1, activation='relu')(embed)
bilstm = Bidirectional(LSTM(output_dim=50, dropout=0.5, activation='tanh', return_sequences=True))(cnn)
flat = Flatten()(bilstm)
main_output = Dense(2, activation='softmax')(flat)
model = Model(inputs=main_input, outputs=main_output)
model.compile(loss='categorical_crossentropy', optimizer='adam', metrics=['accuracy'])
model.fit(x_train, y_train, batch_size=batch_size, epochs=n_epoch)
model.save('cnnbilstm.h5')
train_cnn_bilstm(n_symbols, embedding_weights, x_train, y_train)
BiLSTM+注意力机制代码复现如下:
import keras
from keras import backend as K
from keras.models import Sequential
from keras.engine.topology import Layer
from keras.layers import Bidirectional
from keras.layers.embeddings import Embedding
from keras.layers.recurrent import LSTM
from keras.layers.core import Dense
# 自定义Attention层
class AttentionLayer(Layer):
def __init__(self, attention_size=None, **kwargs):
self.attention_size = attention_size
super(AttentionLayer, self).__init__(**kwargs)
def get_config(self):
config = super().get_config()
config['attention_size'] = self.attention_size
return config
def build(self, input_shape):
assert len(input_shape) == 3
self.time_steps = input_shape[1]
hidden_size = input_shape[2]
if self.attention_size is None:
self.attention_size = hidden_size
self.W = self.add_weight(name='att_weight', shape=(hidden_size, self.attention_size),
initializer='uniform', trainable=True)
self.b = self.add_weight(name='att_bias', shape=(self.attention_size,),
initializer='uniform', trainable=True)
self.V = self.add_weight(name='att_var', shape=(self.attention_size,),
initializer='uniform', trainable=True)
super(AttentionLayer, self).build(input_shape)
def call(self, inputs):
self.V = K.reshape(self.V, (-1, 1))
H = K.tanh(K.dot(inputs, self.W) + self.b)
score = K.softmax(K.dot(H, self.V), axis=1)
outputs = K.sum(score * inputs, axis=1)
return outputs
def compute_output_shape(self, input_shape):
return input_shape[0], input_shape[2]
##定义网络结构
def train_bilstm_att(n_symbols, embedding_weights, x_train, y_train, ATT_SIZE):
model = Sequential()
model.add(Embedding(output_dim=vocab_dim,
input_dim=n_symbols,
weights=[embedding_weights],
input_length=maxlen))
model.add(Bidirectional(LSTM(output_dim=50, dropout=0.5, return_sequences=True)))
model.add(AttentionLayer(attention_size=ATT_SIZE))
model.add(Dense(2, activation='softmax'))
model.compile(loss='categorical_crossentropy',
optimizer='adam', metrics=['accuracy'])
model.fit(x_train, y_train, batch_size=batch_size, epochs=n_epoch)
model.save('bilstmAtt.h5')
train_bilstm_att(n_symbols, embedding_weights, x_train, y_train)
补充一下,实现CNN+BiLSTM+Attention的融合模型的话,只需简单的在CNN+BiLSTM后加上一层Attention,或者在BiLSTM+Attention模型中的嵌入层后加上一层卷积层即可。
(7) 模型评估
from keras.models import load_model
from sklearn.metrics import classification_report
if __name__ == '__main__':
model = load_model('bilstm.h5')
# model = load_model('textcnn.h5')
# model = load_model('cnnbilstm.h5')
# model = load_model('bilstmAtt.h5', custom_objects={'AttentionLayer':AttentionLayer})
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))
自己玩玩
“智能”推荐:
轻松搞懂word2vec+SVM(支持向量机)实现中英文情感分类
【TF-IDF、word2vec、svm、cnn、textcnn、bilstm、cnn+bilstm、bilstm+attention】英文长文本分类实战
代码下载链接,有需要的请自行提取,不想hua前的朋友,可评论同我说,我会回复你,但可能会比较慢。祝好!
https://download.csdn.net/download/qq_44186838/60887420
发布者:全栈程序员-用户IM,转载请注明出处:https://javaforall.cn/154000.html原文链接:https://javaforall.cn
【正版授权,激活自己账号】: Jetbrains全家桶Ide使用,1年售后保障,每天仅需1毛
【官方授权 正版激活】: 官方授权 正版激活 支持Jetbrains家族下所有IDE 使用个人JB账号...