Self-Attention通过捕捉长程依赖关系、提升计算效率和增强特征表示,使得模型在处理长文本和复杂依赖关系的文本分类任务中表现更优异,详情参考Self-Attention原理详解
代码实现
数据读取
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
plt.rcParams['font.sans-serif'] = 'SimHei'
plt.rcParams['axes.unicode_minus'] = False
df = pd.read_csv('中文外卖评论数据集.csv')
print("数据基本信息:",df.info())
print("数据缺失值数量:",df.isnull().sum())
print("数据维度:",df.shape)
print("数据重复值数量:",df.duplicated().sum())
df.head()
该数据集为外卖评论数据,数据维度为11987行2列,其中label列中1代表好评0代表差评,review为评论内容,数据不存在缺失值以及重复项
文本处理
import jieba
jieba.load_userdict('专业词语.txt')
df['分词'] = df['review'].apply(lambda x: jieba.lcut(x))
stopWords = pd.read_csv('哈工大中文停用词.txt', sep='hahaha')
stopWords = ['\n']+list(stopWords.iloc[:, 0])
df['去停用词'] = df['分词'].apply(lambda x: [i for i in x if i not in stopWords])
import re
# 定义正则表达式,用于匹配标点符号、数字和表情符号
pattern = r'[^\u4e00-\u9fa5a-zA-Z]' # 匹配非中文和非英文的字符
def remove_special_characters(text):
return re.sub(pattern, '', text)
df['去特殊字符'] = df['去停用词'].apply(lambda x: [remove_special_characters(word) for word in x])
# 删除长度为1的词语
df['去特殊字符'] = df['去特殊字符'].apply(lambda x: [i for i in x if len(i) > 1])
df.head()
加载自定义词典和停用词表,对文本数据进行分词、去除停用词和特殊字符,以及删除长度为1的词语,从而对文本进行清理和预处理
词云图绘制
from wordcloud import WordCloud
from collections import Counter
all_words_1 = [word for sublist in df[df['label'] == 1]['去特殊字符'] for word in sublist]
word_count_1 = Counter(all_words_1)
font_path = 'C:/Windows/Fonts/simsun.ttc' # 宋体
# 创建词云对象并生成词云图
wordcloud_1 = WordCloud(font_path=font_path, width=800, height=400, background_color='white').generate_from_frequencies(word_count_1)
# 可视化词云图
plt.figure(figsize=(15, 5))
plt.subplot(1,2,1)
plt.title('好评词云图')
plt.imshow(wordcloud_1, interpolation='bilinear')
plt.axis("off")
all_words_2 = [word for sublist in df[df['label'] == 0]['去特殊字符'] for word in sublist]
word_count_2 = Counter(all_words_2)
wordcloud_2 = WordCloud(font_path=font_path, width=800, height=400, background_color='white').generate_from_frequencies(word_count_2)
plt.subplot(1,2,2)
plt.title('差评词云图')
plt.imshow(wordcloud_2, interpolation='bilinear')
plt.axis("off")
plt.show()
利用Tokenizer构建词典
from tensorflow.keras.preprocessing.text import Tokenizer
from tensorflow.keras.preprocessing.sequence import pad_sequences
unique_words_count = len(df['去特殊字符'].value_counts())
print("不同词汇的数量:", unique_words_count)
# 文本向量化
tokenizer = Tokenizer(num_words=5000) # 建立一个存在5000字的词典
tokenizer.fit_on_texts(df['去特殊字符'])
sequences = tokenizer.texts_to_sequences(df['去特殊字符'])
word_index = tokenizer.word_index
maxlen = 100 # 保证每个序列长度为100不足用0填补
data = pad_sequences(sequences, maxlen=maxlen)
data
先查看了原始数据存在10952个词,然后利用Tokenizer构建词典,词典只包含词频排名前5000的词,最后将文本转化为数值序列,即每个词被映射为其在词典中的索引,且将所有序列填充或截断为固定长度(这里是100),长度不足的序列用0填补,长度超过的部分被截断,读者可根据实际任务进行处理这些参数,当然这些参数越大对硬件性能要求越高,这里只是为了演示这个建模过程所以作者设置的相对较少,这样虽然会提高运行数据,但是也会影响最终的模型精确度
分割数据并处理数据
data_df = pd.DataFrame(data)
from sklearn.model_selection import train_test_split
X_temp, X_test, y_temp, y_test = train_test_split(data_df, df['label'], test_size=0.2, random_state=42, stratify=df['label'])
X_train, X_val, y_train, y_val = train_test_split(X_temp, y_temp, test_size=0.125, random_state=42, stratify=y_temp)
from tensorflow.keras.utils import to_categorical
# 将标签转换为one-hot编码
y_train_categorical = to_categorical(y_train, num_classes=2)
y_val_categorical = to_categorical(y_val, num_classes=2)
y_test_categorical = to_categorical(y_test, num_classes=2)
将预处理好的文本数据和对应的标签划分为训练集、验证集和测试集,并将标签转换为适合神经网络分类任务的one-hot编码形式
Self-Attention LSTM模型构建并训练
from tensorflow.keras.models import Sequential
from tensorflow.keras.layers import Dense, Dropout, Flatten
from tensorflow.keras.layers import Embedding
from tensorflow.keras.layers import LSTM, Bidirectional
from keras_self_attention import SeqSelfAttention
from keras.optimizers import Adam
from keras.layers import GlobalMaxPooling1D
self_attention_bilstm = Sequential()
self_attention_bilstm.add(Embedding(input_dim = 5000, # input_dim: 这是整数,指定了输入数据的最大取值(词汇表大小)
output_dim=300, # output_dim: 这是整数,指定了嵌入向量的维度
mask_zero=True))# mask_zero: 这是一个布尔值,指示是否应该将输入中的零(即对应于 input_dim 中索引0的词)视为一个应该被遮蔽的特殊值
self_attention_bilstm.add(Dropout(0.2))
self_attention_bilstm.add(Bidirectional(LSTM(128, activation='relu', return_sequences=True))) # return_sequences=True: 这是一个布尔值参数,指示 LSTM 层是否应该返回完整的输出序列
self_attention_bilstm.add(Dropout(0.2))
self_attention_bilstm.add(SeqSelfAttention(attention_activation='sigmoid')) # 添加self_attention
self_attention_bilstm.add(Dense(units=64, activation='relu'))
self_attention_bilstm.add(Dropout(0.2))
self_attention_bilstm.add(GlobalMaxPooling1D()) # 全局最大池化
self_attention_bilstm.add(Dense(units=2, activation='softmax'))
optimizer = Adam(learning_rate=0.001)
# 编译模型时指定优化器
self_attention_bilstm.compile(optimizer=optimizer, loss='categorical_crossentropy', metrics=['accuracy'])
# 模型训练
history = self_attention_bilstm.fit(
X_train, y_train_categorical,
epochs=10,
batch_size=32,
validation_data=(X_val, y_val_categorical)
)
# 评估模型
test_loss, test_acc = self_attention_bilstm.evaluate(X_test, y_test_categorical)
print(f"Test Loss: {test_loss}")
print(f"Test Accuracy: {test_acc}")
# 可视化训练过程
plt.plot(history.history['accuracy'], label='训练准确率')
plt.plot(history.history['val_accuracy'], label='验证准确率')
plt.title('模型准确率')
plt.xlabel('Epoch')
plt.ylabel('Accuracy')
plt.legend()
plt.show()
plt.plot(history.history['loss'], label='训练损失')
plt.plot(history.history['val_loss'], label='验证损失')
plt.title('模型损失')
plt.xlabel('Epoch')
plt.ylabel('Loss')
plt.legend()
plt.show()
# 保存模型
model_path = 'self_attention_bilstm_model.h5'
self_attention_bilstm.save(model_path)
print(f"模型已保存到 {model_path}")
self_attention_bilstm.summary()
利用Tensorflow构建Self-Attention LSTM模型,最后训练模型作者训练轮数、训练批数包括前面词典、序列长度都设置的较小加快了训练过程导致数据信息存在损失最后模型是存在过拟合的,如下:
把这些参数设置的更大将更考验电脑性能,且Self-Attention对于大文本、大数据量处理是更得心应手的,相对于这种小文本来说它可能还没有传统的RNN、CNN精确度高,详情参考文章2010.11929 (arxiv.org)
模型评价
# 在测试集上进行预测
y_pred_prob = self_attention_bilstm.predict(X_test)
y_pred = np.argmax(y_pred_prob, axis=1)
# 创建包含真实结果和预测结果的DataFrame
results_df = pd.DataFrame({
'真实结果': y_test,
'预测结果': y_pred
})
from sklearn.metrics import classification_report, confusion_matrix
confusion_matrix = confusion_matrix(results_df['真实结果'], results_df['预测结果']) # 计算混淆矩阵
fig, ax = plt.subplots(figsize=(10, 7))
cax = ax.matshow(confusion_matrix, cmap='Blues')
fig.colorbar(cax)
ax.set_xlabel('预测值')
ax.set_ylabel('真实值')
ax.set_xticks(np.arange(2))
ax.set_yticks(np.arange(2))
ax.set_xticklabels(['类别0', '类别1'])
ax.set_yticklabels(['类别0', '类别1'])
for (i, j), val in np.ndenumerate(confusion_matrix):
ax.text(j, i, f'{val}', ha='center', va='center', color='black')
plt.title('混淆矩阵热力图')
plt.show()
可以看见在测试集上类别0预测正确1415条预测错误252条,类别1预测正确548条预测错误147条,详细的评价指标如下
# 输出模型报告, 查看评价指标
print(classification_report(results_df['真实结果'], results_df['预测结果']))
往期推荐
Pythorch框架构建Attention-lstm时序模型
利用python meteostat库对全球气象数据访问,获取历史气象数据
如果你对类似于这样的文章感兴趣。
欢迎关注、点赞、转发~
个人观点,仅供参考