简介这份资源面向人工智能与深度学习方向的本科或研究生毕业设计场景提供一套基于融合对抗训练与注意力机制的Bi-LSTM网络用于景区评论情感分析的完整Python实现。项目覆盖从数据标注、word2vec词向量训练、模型训练到未标注语句情感预测与评价分数生成的完整流程适合具备一定深度学习基础、希望复现或改进情感分类模型的读者。压缩包共18个文件约191KB包含4个ipynb交互式笔记本、4个py源码文件以及word2vec词向量、语料、停用词、日志与模型保存等配套数据config.py负责参数配置dataSet.py完成数据预处理tools.py提供序列长度与评价指标计算model.py给出baseline模型便于对照实验。目前已有248人学习下载。读者可借此获得一套结构清晰、可直接运行的毕设级情感分析方案理解对抗训练与注意力机制在Bi-LSTM中的融合思路并掌握从词向量到预测评分的工程化落地方法。1. 景区评论情感分析这套 Bi-LSTM 源码到底能不能直接跑通做景区运营或者旅游平台数据的人大概率都遇到过同一个尴尬后台躺着几万条评论人工看不过来用通用情感分析工具跑结果把「风景绝了但厕所太脏」判成正面。通用模型在景区这种「景色服务设施」混合语境里翻车是常态因为训练语料根本不是旅游场景。这份基于融合对抗与注意力机制的 Bi-LSTM 网络景区评论情感分析 Python 源码就是冲着这个痛点来的——它把词向量训练、标注语料建模、未标注语料预测、评分生成串成了一条完整链路还附带了 2class 和 3class 两套数据目录。适合正在做毕设的学生、想快速搭一个垂直领域情感分类 baseline 的算法工程师以及需要给景区评价做量化打分的产品同学。下面我按自己拆包复现的顺序把这份资源从环境到预测讲透。2. 环境配置与目录结构先搞清楚每个文件干什么再动手2.1 版本锁定不是玄学TensorFlow 2.3 和 Gensim 4.1 必须对齐这份源码给的环境是 Python 3.8.12、TensorFlow 2.3.0、Gensim 4.1.2、Jieba 0.42.0。很多人第一反应是「我本地 3.10 能不能跑」血泪经验是Gensim 4.x 和 3.x 的Word2Vec接口差异很大model.wv.vocab在 4.x 里已经废弃而这份代码的dataSet.py里大概率还在用旧写法或者刚好卡在 4.1 的过渡期。TensorFlow 2.3 对 CUDA 的要求是 10.1如果你用 30 系显卡建议直接 CPU 跑Bi-LSTM 在几千条评论的量级上 CPU 完全扛得住别为了省那几分钟折腾驱动。我一般会先建一个干净环境避免和本机其他项目的 TF 版本打架conda create -n scenic_sentiment python3.8.12 -y conda activate scenic_sentiment pip install tensorflow2.3.0 gensim4.1.2 jieba0.42.0 numpy pandas scikit-learn这里tensorflow2.3.0是硬约束gensim4.1.2负责 word2vec 训练jieba做中文分词。装完先别急着跑 main用python -c import tensorflow as tf; print(tf.__version__)确认版本输出必须是 2.3.0差一个小版本都可能在model.py里报AttributeError。2.2 目录里每个文件对应哪一步别上来就改 main拆开压缩包根目录下这几个东西要分清config.py是全局配置路径、超参数、词向量维度都在这dataSet.py是数据预处理负责读 corpus、分词、转 idtools.py管序列长度计算和评价指标model.py是 baseline 模型定义main_save.ipynb走训练保存main_load.ipynb走加载预测。word2vec目录下是gensim_word2vec.ipynb专门训词向量。data下面分corpus、3class、2class、resultstopwords放停用词logs放日志。常见做法是先把config.py打开确认几个路径指向实际位置。比如词向量文件路径、训练数据路径、模型保存路径这三类路径如果还是作者机器上的绝对路径你直接跑必报FileNotFoundError。改成相对路径或者你本地的绝对路径这是复现第一步。2.3 数据标注规范2class 和 3class 别混用2class是二分类正面/负面3class是三分类正面/中性/负面。摘要里写「数据标注规范语句结构」意思是原始评论要先清洗成统一格式通常是一行一条「标签\t文本」。如果你拿 3class 的数据去跑二分类的配置标签数量对不上model.py最后一层Dense的 units 会报维度错误。我一般先看config.py里num_classes设的是几再决定用哪个数据目录两者必须一致。提示corpus目录一般是未标注的原始语料用来训 word2vec2class/3class是带标签的训练数据。别把 corpus 直接喂给分类模型。3. 从 word2vec 到 Bi-LSTM 训练把词向量和模型串起来3.1 用 gensim 训词向量维度和窗口别拍脑袋景区评论的词汇量不大但「索道」「摆渡车」「讲解」「排队」这类词是否被正确编码直接决定模型能不能区分「排队太长」和「排队很快」。gensim_word2vec.ipynb里通常用Word2Vec对corpus做训练。我一般会显式指定参数而不是用默认值from gensim.models import Word2Vec import jieba # 读取 corpus每行一条评论 with open(data/corpus/comments.txt, r, encodingutf-8) as f: sentences [list(jieba.cut(line.strip())) for line in f if line.strip()] model Word2Vec( sentencessentences, vector_size200, # 词向量维度景区语料 100~200 够用 window5, # 上下文窗口评论句子短5 能覆盖局部搭配 min_count2, # 出现少于 2 次的词丢弃降噪 workers4, sg1, # skip-gram 对低频词更友好 epochs10 ) model.save(word2vec/scenic_w2v.model)vector_size200是维度和显存/内存的折中太小表达不足太大在小语料上过拟合。sg1选 skip-gram 是因为景区评论里「厕所」这种词出现频率不高但情感指向强CBOW 容易把它平滑掉。min_count2过滤掉只出现一次的口误或乱码。训完用model.wv.most_similar(风景)看一眼近邻词如果出来的都是「景色」「风光」这类说明词向量质量过关如果出来一堆无关词回去检查分词和停用词。3.2 dataSet.py 里的 id 映射和 padding长度截断是坑dataSet.py干的事是把文本转成模型能吃的 id 序列。核心逻辑一般是加载 word2vec 模型 → 构建词表 → 每条评论转成 id 列表 → 用tools.py里的序列长度函数统一 padding。这里有个容易翻车的点padding 的maxlen如果设得太小长评论被截断情感极性可能被截掉设得太大短评论补一堆 0浪费算力还引入噪声。我一般会先统计训练集评论长度的分布取 95 分位数作为maxlen。比如大部分评论在 30 字以内少数长评到 80 字那maxlen80比较稳。config.py里如果有max_sequence_length之类的字段就改它。padding 用post还是pre要和模型结构对齐Bi-LSTM 对 padding 位置不敏感但如果你后面接了注意力机制padding 的 0 会被注意力打分常见做法是在注意力层加 mask把 padding 位置的权重压掉。# dataSet.py 里常见的 id 转换逻辑示意 def text_to_ids(text, word2id, maxlen): ids [word2id.get(w, word2id[UNK]) for w in jieba.cut(text)] if len(ids) maxlen: ids ids [0] * (maxlen - len(ids)) # post padding else: ids ids[:maxlen] return idsword2id[UNK]是未登录词的处理词表里没见过的词统一映射到一个特殊 id别直接丢掉否则句子结构会断。maxlen从 config 读别硬编码。3.3 model.py 里的对抗与注意力融合到底融在哪这份资源的卖点是「融合对抗与注意力机制的 Bi-LSTM」。拆开model.pybaseline 是 Embedding Bi-LSTM 全连接。注意力机制一般是在 Bi-LSTM 输出后加一层Attention对每个时间步的输出算权重再加权求和让模型聚焦到「太脏」「绝了」这种情感词上。对抗的部分常见做法是加对抗训练Adversarial Training在 embedding 层加扰动提升模型对输入噪声的鲁棒性——景区评论里错别字、口语化表达多这个设计是有针对性的。import tensorflow as tf from tensorflow.keras import layers class AttentionLayer(layers.Layer): def __init__(self, units): super().__init__() self.W layers.Dense(units) self.V layers.Dense(1) def call(self, inputs): # inputs: (batch, seq_len, hidden) score self.V(tf.nn.tanh(self.W(inputs))) # (batch, seq_len, 1) weights tf.nn.softmax(score, axis1) context tf.reduce_sum(weights * inputs, axis1) return contextW把 Bi-LSTM 输出映射到注意力空间V打分softmax归一化成权重最后加权求和得到句子级表示。对抗部分如果代码里是用tf.GradientTape在 embedding 上加epsilon * sign(grad)那epsilon一般设 0.01~0.1太大模型训不动太小没效果。训练时看 loss 曲线如果对抗训练开启后 loss 震荡厉害先把 epsilon 调小。3.4 main_save.ipynb 训练与保存epoch 和早停怎么设main_save.ipynb是训练入口读2class或3class数据调model.py建模型编译后fit最后model.save。我一般会加 EarlyStopping 和 ModelCheckpoint避免过拟合和手动挑模型callbacks [ tf.keras.callbacks.EarlyStopping(monitorval_loss, patience3, restore_best_weightsTrue), tf.keras.callbacks.ModelCheckpoint(result/best_model.h5, monitorval_loss, save_best_onlyTrue) ] model.fit(X_train, y_train, validation_split0.2, epochs20, batch_size64, callbackscallbacks)patience3表示验证 loss 连续 3 轮不降就停restore_best_weightsTrue保证拿回最优权重。batch_size64在几千条数据上是稳的数据量过万可以调到 128。训练完看logs目录里的日志确认准确率和 F1 是否合理——景区二分类一般能到 85% 以上三分类因为中性样本难标80% 左右算正常。4. 加载模型做预测与评分生成从评论到分数的最后一公里4.1 main_load.ipynb 加载预训练模型路径和词表要对齐main_load.ipynb是推理入口加载main_save存下来的模型和 word2vec 词表对新评论做预测。这里最容易踩的坑是推理时的词表和训练时的词表不一致。如果你重新训了 word2vec 但没重新训分类模型id 映射就对不上预测结果全是乱的。我一般把词表也一起保存推理时加载同一个词表文件。import pickle from tensorflow.keras.models import load_model model load_model(result/best_model.h5, custom_objects{AttentionLayer: AttentionLayer}) with open(result/word2id.pkl, rb) as f: word2id pickle.load(f) def predict(text): ids text_to_ids(text, word2id, maxlen80) prob model.predict(np.array([ids]))[0] return probcustom_objects必须把自定义的AttentionLayer传进去否则加载模型时报Unknown layer。word2id.pkl是训练时存下来的推理时直接用别重新构建。4.2 结合预测值生成评价分数概率到分数的映射摘要里写「结合预测值生成评价分数」意思是把 softmax 输出的概率转成 0~5 或 0~10 的评分。二分类的话正面概率p可以映射成score p * 5三分类可以加权比如score p_pos * 5 p_neu * 3 p_neg * 1。这个映射规则没有标准答案取决于业务怎么定义分数。我一般会在result目录下输出每条评论的原始文本、预测标签、概率和最终分数方便人工抽检。def prob_to_score(prob, mode2class): if mode 2class: return round(prob[1] * 5, 2) # 正面概率 * 5 else: return round(prob[0]*1 prob[1]*3 prob[2]*5, 2) # 负/中/正prob[1]对应正面类别的概率索引顺序要和训练时标签编码一致。三分类的权重 1/3/5 是经验值你可以按业务调整比如中性给 2.5。4.3 批量预测与结果落盘别一条条跑实际用的时候评论是批量的main_load.ipynb里一般会读一个待预测文件批量转 id 后一次性predict再写回 csv。批量大小和训练时 batch_size 保持一致或者更大都行推理不涉及梯度显存占用小。落盘时用utf-8-sig编码避免 Excel 打开乱码。df pd.read_csv(data/result/to_predict.csv) X np.array([text_to_ids(t, word2id, 80) for t in df[comment]]) probs model.predict(X, batch_size128) df[score] [prob_to_score(p) for p in probs] df.to_csv(data/result/predicted.csv, indexFalse, encodingutf-8-sig)batch_size128是推理常用值utf-8-sig带 BOMExcel 直接双击不乱码。5. 避坑与排查复现这套源码时最容易翻车的五个地方5.1 现象跑 main_save 报KeyError: word2vec或路径找不到原因config.py里的路径还是作者机器的绝对路径或者 word2vec 模型文件没生成。解决先跑gensim_word2vec.ipynb生成模型文件再把 config 里所有路径改成你本地的相对路径用os.path.exists打印确认每个路径存在。5.2 现象Gensim 加载模型报AttributeError: Word2Vec object has no attribute vocab原因代码是按 Gensim 3.x 写的你装了 4.x。解决要么降级到gensim3.8.3要么把model.wv.vocab改成model.wv.key_to_indexmodel.wv.index2word改成model.wv.index_to_key。这份资源指定 4.1.2优先按 4.x 改代码。5.3 现象模型训练 loss 不降准确率卡在 50%原因标签和 id 没对齐或者 padding 把有效信息截没了。解决打印几条X_train和y_train看对应关系确认标签编码是 0/1 还是 1/2统计序列长度分布把maxlen调到 95 分位数以上。5.4 现象加载模型报Unknown layer: AttentionLayer原因自定义层没注册。解决load_model时传custom_objects{AttentionLayer: AttentionLayer}或者用tf.keras.models.load_model前先tf.keras.utils.register_keras_serializable()装饰自定义层。5.5 现象预测结果全是同一类原因推理词表和训练词表不一致或者模型加载的是未训练完的权重。解决确认word2id.pkl和模型是同一次训练产出的检查main_load加载的是best_model.h5而不是中间 checkpoint用训练集里的一条样本做预测看能否复现训练时的输出。6. 进阶技巧用注意力权重做可解释性抽检这套源码跑通之后真正有价值的是注意力层输出的权重。景区运营不只需要一个分数还需要知道模型为什么给这条评论打低分。我一般会改一下AttentionLayer把weights也返回出来然后对预测错误的样本做人工抽检看注意力是不是打在了「排队」「脏」「态度差」这些词上。如果注意力跑偏了说明词向量或者训练数据有问题这比只看准确率有用得多。class AttentionLayer(layers.Layer): def call(self, inputs, return_weightsFalse): score self.V(tf.nn.tanh(self.W(inputs))) weights tf.nn.softmax(score, axis1) context tf.reduce_sum(weights * inputs, axis1) if return_weights: return context, weights return context把return_weightsTrue时拿到的weights和原始分词结果对齐就能看到每个词的权重。我习惯对验证集里预测错的样本逐条看注意力分布如果「但是」后面的词权重明显更高说明模型学到了转折结构这是好现象如果权重均匀分布说明注意力没起作用回去检查W和V的维度是否匹配。还有一个实用技巧把2class和3class的模型分别训出来对同一条评论做预测如果二分类说正面、三分类说中性那这条评论大概率是「有褒有贬」的混合情感可以单独拎出来做细粒度分析。景区评论里这种混合情感占比不低单靠一个分类头容易漏掉。从那以后我每次复现这类情感分析项目都强制先跑一条训练集样本做端到端验证确认「训练→保存→加载→预测」闭环通了再去调参和看指标。希望帮到你。本文还有配套的精品资源点击获取