赞
踩
文本情感分析(Sentiment Analysis)是自然语言处理(NLP)方法中常见的应用,也是一个有趣的基本任务,尤其是以提炼文本情绪内容为目的的分类。它是对带有情感色彩的主观性文本进行分析、处理、归纳和推理的过程。
本文将介绍情感分析中的情感极性(倾向)分析。所谓情感极性分析,指的是对文本进行褒义、贬义、中性的判断。在大多应用场景下,只分为两类。例如对于“喜爱”和“厌恶”这两个词,就属于不同的情感倾向。
本文将详细介绍如何使用深度学习模型中的LSTM模型来实现文本的情感分析。
我们以某电商网站中某个商品的评论作为语料(corpus.csv),该数据集的下载网址为:https://github.com/renjunxiang/Text-Classification/blob/master/TextClassification/data/data_single.csv ,该数据集一共有4310条评论数据,文本的情感分为两类:“正面”和“反面”
接着我们需要对语料做一个简单的分析:
数据集中的情感分布;
数据集中的评论句子长度分布。
使用以下Python脚本,我们可以统计出数据集中的情感分布以及评论句子长度分布。
# -*- coding: utf-8 -*- import pandas as pd import matplotlib.pyplot as plt from matplotlib import font_manager from itertools import accumulate # 统计句子长度及长度出现的频数 df = pd.read_csv('../testdata/data_single.csv') print(df.groupby('label')['label'].count()) df['length'] = df['evaluation'].apply(lambda x: len(x)) len_df = df.groupby('length').count() sent_length = len_df.index.tolist() sent_freq = len_df['evaluation'].tolist() # 绘制句子长度及出现频数统计图 plt.bar(sent_length, sent_freq) plt.title("句子长度及出现频数统计图") plt.xlabel("句子长度") plt.ylabel("句子长度出现的频数") plt.savefig("./句子长度及出现频数统计图.png") plt.show() plt.close() # 绘制句子长度累积分布函数(CDF) sent_pentage_list = [(count/sum(sent_freq)) for count in accumulate(sent_freq)] # 绘制CDF plt.plot(sent_length, sent_pentage_list) # 寻找分位点为quantile的句子长度 quantile = 0.91 #print(list(sent_pentage_list)) for length, per in zip(sent_length, sent_pentage_list): if round(per, 2) == quantile: index = length break print("\n分位点为%s的句子长度:%d." % (quantile, index)) # 绘制句子长度累积分布函数图 plt.plot(sent_length, sent_pentage_list) plt.hlines(quantile, 0, index, colors="c", linestyles="dashed") plt.vlines(index, 0, quantile, colors="c", linestyles="dashed") plt.text(0, quantile, str(quantile)) plt.text(index, 0, str(index)) plt.title("句子长度累积分布函数图") plt.xlabel("句子长度") plt.ylabel("句子长度累积频率") plt.savefig("./句子长度累积分布函数图.png") plt.show() plt.close()
输出的结果如下:
label
正面 1908
负面 2375
Name: label, dtype: int64
分位点为0.91的句子长度:183.
可以看到,正反面两类情感的比例差不多。句子长度及出现频数统计图如下:
句子长度累积分布函数图如下:
可以看到,大多数样本的句子长度集中在1-200之间,句子长度累计频率取0.91分位点,则长度为183左右。
接着我们使用深度学习中的LSTM模型来对上述数据集做情感分析,笔者实现的模型框架如下:
完整的Python代码如下:
# -*- coding: utf-8 -*- import pickle import numpy as np import pandas as pd from tensorflow.keras.utils import plot_model from tensorflow.keras.utils import to_categorical from tensorflow.keras.models import Sequential from tensorflow.keras.preprocessing.sequence import pad_sequences from tensorflow.keras.layers import LSTM, Dense, Embedding, Dropout from sklearn.model_selection import train_test_split from sklearn.metrics import accuracy_score # 导入数据 # 文件的数据中,特征为evaluation, 类别为label. def load_data(filepath, input_shape=20): df = pd.read_csv(filepath) # 标签及词汇表 labels, vocabulary = list(df['label'].unique()), list(df['evaluation'].unique()) # 构造字符级别的特征 string = '' for word in vocabulary: string += word vocabulary = set(string) # 字典列表 word_dictionary = {word: i+1 for i, word in enumerate(vocabulary)} with open('word_dict.pk', 'wb') as f: pickle.dump(word_dictionary, f) inverse_word_dictionary = {i+1: word for i, word in enumerate(vocabulary)} label_dictionary = {label: i for i, label in enumerate(labels)} with open('label_dict.pk', 'wb') as f: pickle.dump(label_dictionary, f) output_dictionary = {i: labels for i, labels in enumerate(labels)} vocab_size = len(word_dictionary.keys()) # 词汇表大小 label_size = len(label_dictionary.keys()) # 标签类别数量 # 序列填充,按input_shape填充,长度不足的按0补充 x = [[word_dictionary[word] for word in sent] for sent in df['evaluation']] x = pad_sequences(maxlen=input_shape, sequences=x, padding='post', value=0) y = [[label_dictionary[sent]] for sent in df['label']] y = [to_categorical(label, num_classes=label_size) for label in y] y = np.array([list(_[0]) for _ in y]) return x, y, output_dictionary, vocab_size, label_size, inverse_word_dictionary # 创建深度学习模型, Embedding + LSTM + Softmax. def create_LSTM(n_units, input_shape, output_dim, filepath): x, y, output_dictionary, vocab_size, label_size, inverse_word_dictionary = load_data(filepath) model = Sequential() model.add(Embedding(input_dim=vocab_size + 1, output_dim=output_dim, input_length=input_shape, mask_zero=True)) model.add(LSTM(n_units, input_shape=(x.shape[0], x.shape[1]))) model.add(Dropout(0.2)) model.add(Dense(label_size, activation='softmax')) model.compile(loss='categorical_crossentropy', optimizer='adam', metrics=['accuracy']) plot_model(model, to_file='./model_lstm.png', show_shapes=True) model.summary() return model # 模型训练 def model_train(input_shape, filepath, model_save_path): # 将数据集分为训练集和测试集,占比为9:1 # input_shape = 100 x, y, output_dictionary, vocab_size, label_size, inverse_word_dictionary = load_data(filepath, input_shape) train_x, test_x, train_y, test_y = train_test_split(x, y, test_size = 0.1, random_state = 42) # 模型输入参数,需要自己根据需要调整 n_units = 100 batch_size = 32 epochs = 5 output_dim = 20 # 模型训练 lstm_model = create_LSTM(n_units, input_shape, output_dim, filepath) lstm_model.fit(train_x, train_y, epochs=epochs, batch_size=batch_size, verbose=1) # 模型保存 lstm_model.save(model_save_path) N = test_x.shape[0] # 测试的条数 predict = [] label = [] for start, end in zip(range(0, N, 1), range(1, N+1, 1)): sentence = [inverse_word_dictionary[i] for i in test_x[start] if i != 0] y_predict = lstm_model.predict(test_x[start:end]) label_predict = output_dictionary[np.argmax(y_predict[0])] label_true = output_dictionary[np.argmax(test_y[start:end])] #print(''.join(sentence), label_true, label_predict) # 输出预测结果 predict.append(label_predict) label.append(label_true) acc = accuracy_score(predict, label) # 预测准确率 print('模型在测试集上的准确率为: %s.' % acc) if __name__ == '__main__': filepath = '../testdata/data_single.csv' input_shape = 180 model_save_path = './corpus_model.h5' model_train(input_shape, filepath, model_save_path)
对上述模型,共训练5次,训练集和测试集比例为9:1,输出的结果为:
```css
Epoch 1/5
121/121 [==============================] - 9s 72ms/step - loss: 0.4714 - accuracy: 0.7779
Epoch 2/5
121/121 [==============================] - 9s 71ms/step - loss: 0.2148 - accuracy: 0.9328
Epoch 3/5
121/121 [==============================] - 9s 71ms/step - loss: 0.1639 - accuracy: 0.9478
Epoch 4/5
121/121 [==============================] - 10s 84ms/step - loss: 0.1432 - accuracy: 0.9546
Epoch 5/5
121/121 [==============================] - 9s 75ms/step - loss: 0.1294 - accuracy: 0.9613
模型在测试集上的准确率为: 0.9020979020979021.
可以看到,该模型在训练集上的准确率为95%以上,在测试集上的准确率为90%以上,效果还是相当不错的。
接着,我们利用刚刚训练好的模型,对新的数据进行测试。笔者随机改造上述样本的评论,然后预测其情感倾向。情感预测的Python代码如下:
# -*- coding: utf-8 -*- # Import the necessary modules import pickle import numpy as np from tensorflow.keras.models import load_model from tensorflow.keras.preprocessing.sequence import pad_sequences # 导入字典 with open('word_dict.pk', 'rb') as f: word_dictionary = pickle.load(f) with open('label_dict.pk', 'rb') as f: output_dictionary = pickle.load(f) try: # 数据预处理 input_shape = 180 sent = "电视刚安装好,说实话,画质不怎么样,很差!" x = [[word_dictionary[word] for word in sent]] x = pad_sequences(maxlen=input_shape, sequences=x, padding='post', value=0) # 载入模型 model_save_path = './corpus_model.h5' lstm_model = load_model(model_save_path) # 模型预测 y_predict = lstm_model.predict(x) label_dict = {v:k for k,v in output_dictionary.items()} print('输入语句: %s' % sent) print('情感预测结果: %s' % label_dict[np.argmax(y_predict)]) except KeyError as err: print("您输入的句子有汉字不在词汇表中,请重新输入!") print("不在词汇表中的单词为:%s." % err)
输出结果如下:
输入语句: 电视刚安装好,说实话,画质不怎么样,很差!
情感预测结果: 负面
让我们再尝试着测试一些其他的评论:
输入语句: 物超所值,真心不错 情感预测结果: 正面 输入语句: 很大很好,方便安装! 情感预测结果: 正面 输入语句: 卡,慢,死机,闪退。 情感预测结果: 负面 输入语句: 这种货色就这样吧,别期待怎样。 情感预测结果: 负面 输入语句: 啥服务态度码,出了事情一个推一个,送货安装还收我50 情感预测结果: 负面 输入语句: 京东服务很好!但我买的这款电视两天后就出现这样的问题,很后悔买了这样的电视 情感预测结果: 负面 输入语句: 产品质量不错,就是这位客服的态度十分恶劣,对相关服务不予解释说明,缺乏耐心, 情感预测结果: 负面 输入语句: 很满意,电视非常好。护眼模式,很好,也很清晰。 情感预测结果: 负面
当然,该模型并不是对一切该商品的评论都会有好的效果,还是应该针对特定的语料去训练,去预测。
本文主要介绍了LSTM模型在文本情感分析方面的应用,该项目已上传Github,地址为: https://github.com/percent4/Sentiment_Analysis 。
数据集来源:https://github.com/renjunxiang/Text-Classification/blob/master/TextClassification/data/data_single.csv
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。