Self-Attention LSTM教程:一步步实现文本分类

技术

picture.image

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()
      

picture.image

该数据集为外卖评论数据,数据维度为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()
      

picture.image

加载自定义词典和停用词表,对文本数据进行分词、去除停用词和特殊字符,以及删除长度为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()
      

picture.image

利用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
      

picture.image

先查看了原始数据存在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()
      

picture.image

利用Tensorflow构建Self-Attention LSTM模型,最后训练模型作者训练轮数、训练批数包括前面词典、序列长度都设置的较小加快了训练过程导致数据信息存在损失最后模型是存在过拟合的,如下:

picture.image

把这些参数设置的更大将更考验电脑性能,且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()
      

picture.image

可以看见在测试集上类别0预测正确1415条预测错误252条,类别1预测正确548条预测错误147条,详细的评价指标如下


          
# 输出模型报告, 查看评价指标
          
print(classification_report(results_df['真实结果'], results_df['预测结果']))
      

picture.image

往期推荐

Pythorch框架构建Attention-lstm时序模型

Self-Attention原理详解

SHAP全解析:机器学习、深度学习模型解释保姆级教程

利用python meteostat库对全球气象数据访问,获取历史气象数据

Python桑基图可视化:揭示数据行为路径

梯度提升集成:LightGBM与XGBoost组合预测

特征工程——数据转换

回归任务常见评价指标解读以及Python实现

如果你对类似于这样的文章感兴趣。

欢迎关注、点赞、转发~

个人观点,仅供参考

0
0
0
0
关于作者

文章

0

获赞

0

收藏

0

相关资源
字节跳动基于 DataLeap 的 DataOps 实践
随着数字化转型的推进以及业务数仓建设不断完善,大数据开发体量及复杂性逐步上升,如何保证数据稳定、正确、持续产出成为数据开发者核心诉求,也成为平台建设面临的挑战之一。本次分享主要介绍字节对于DataOps的理解 以及 DataOps在内部业务如何落地实践。
相关产品
评论
未登录
看完啦,登录分享一下感受吧~
暂无评论