尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

电影评论情感分析系统实战:BiLSTM训练与Flask部署

电影评论情感分析系统实战:BiLSTM训练与Flask部署 简介基于Python深度学习的电影评论情感分析系统设计与实现文档面向计算机相关专业毕业设计或课程设计选题聚焦影评文本情感分类与可视化分析。文档以Flask框架和Word2Vec向量模型为核心涵盖系统设计、接口实现、模型训练、结果评估等完整环节可帮助读者理解深度学习在自然语言处理中的落地流程并参考其中的技术选型与代码思路。资源包共1个文件为docx格式文档大小1.34MB属于纯文字方案类资料适合快速通读框架与细节。已有466人浏览学习适合需要快速了解情感分析系统结构或寻找毕设参考的开发者。文档含摘要、英文摘要、目录及绪论等章节后续围绕电影评论数据获取、词向量训练、情感极性判断等展开为相关课题提供较完整的方案描述。1. 电影评论情感分析系统一套要跑通哪些环节才算真的“做完”做内容运营或者影视舆情的人最烦的事就是电影上映当晚几千条评论刷进来观众到底是夸是骂手动根本翻不完只看豆瓣评分又太粗。电影评论情感分析这个方向本质上是训练一个模型把每条评论分成正向、负向两到三类再按时间或词频统计舆论走向。很多人一上来就想到情感词典数正面词负面词实际效果经常翻车——“这电影真好笑”和“这电影真好笑啊”词典法都会判成正向前者却是典型的反讽。深度学习模型能学到上下文和句法痕迹但代价是要先备好数据、GPU和不大不小的跑批流程。这篇文章打算把从爬取评论、清洗数据、训练LSTM到部署Flask接口的完整链路讲清楚适合毕设、课设或小型舆情项目起步新手能照着做熟手可以直接抄里面的参数和避坑细节。2. 选型是第一步为什么这个场景适合用深度学习而不是词典法在动手写代码之前先把方案定下来。情感分析不是一个“非深度学习不可”的任务但电影评论这个场景恰好是深度学习发挥价值的典型区域。电影短评短、口语化、喜欢用省略和反讽传统方案在这些文本上很容易摸到天花板。2.1 三种方案对比词典法、SVMTF-IDF、BiLSTM先看三组常见做法方便你判断边界在哪里。方案核心思路优点痛点情感词典统计文本中正面、负面词的数量无需训练语料可解释性强不认否定结构“不好看”里“好”还是正向不认反讽新词容易被漏判SVM TF-IDF把词袋向量喂给线性分类器训练快适合小样本丢失词序“好看”和“不好看”的向量距离可能很近BiLSTM 词向量用循环网络按顺序编码句子能捕捉上下文与长距离依赖需要标注数据和较长的训练周期TF-IDF把“这部片子的笑点很尴尬”里的“尴尬”拿去做特征准确率也能到七成但遇到“尴尬得我笑出来了”就会出错因为“笑”被当成正向。BiLSTM在处理这种语义反转时明显更稳这也是我在这套系统里选BiLSTM而不是SVM的主要原因。那为什么不用更重的Transformer因为电影短评平均长度只有几十个字BERT这种动辄几亿参数的模型在这里属于杀鸡用牛刀训练成本高、部署资源大对入门项目和小型舆情任务不划算。把LSTM调好往往已经有85%左右的正负向分类准确率。2.2 整体Pipeline这条数据流向是系统的骨架整个系统可以拆成五段采集、清洗、建模、训练、服务。我一般习惯先画一个完整的数据流再分模块实现否则很容易出现“模型跑通却没法接入业务”的问题。采集段用爬虫从公开评论页抓取短评文本和相关评分。清洗段去标签、去空值、规整标点把评分转成弱标签。建模段分词、切分数据集、构建词表、做填充与截断。训练段加载词向量训练BiLSTM分类器在验证集上做早停。服务段把最佳权重导出用Flask包一个POST接口接收评论文本返回情感分数。这里有一条容易忽略的分工数据预处理和模型训练必须共用同一个词表。我见过好几个项目在离线处理时用了一套词表上线时又另写了一份分词汇编导致线上预测结果和线下评估对不上。解决的办法很简单词表用一个pickle文件离线在线都从同一个路径读取。为了让这个流程能跑起来我建议目录结构做成这样sentiment_system/ ├── data/ │ ├── raw_comments.csv │ ├── train.csv │ └── dev.csv ├── utils/ │ ├── fetch_comment.py │ ├── clean_text.py │ └── vocab.py ├── models/ │ ├── model.py │ └── bilstm.pt └── app/ └── server.py目录不需要很花哨关键是data、utils、models、app四件事各管各的。后续要扩展成BERT版本时只需要替换models/model.py数据层可以原封不动。2.3 深度学习环境怎么装先CPU后GPU避免装全家桶很多新手卡在环境是因为教程让你一次性装CUDA、cuDNN、PyTorch、numpy、jieba、pandas结果版本冲突出一堆报错。python入门的第一道坎往往不是语法而是环境。安装python本身不难难的是深度学习环境里CUDA、显卡驱动、PyTorch版本三者必须互相匹配。我的习惯是分两步走先用CPU跑通一个极小样本证明pipeline没问题再换GPU训练完整的模型。Python版本建议选3.8或3.10这两个版本对PyTorch的依赖兼容性最稳。需要装的包其实就几个torch、pandas、numpy、jieba、requests、beautifulsoup4、flask、scikit-learn。scikit-learn只在计算F1和做数据切分时用可以减少手写比例分割的代码量。关于GPU很多人以为深度学习非要A100不可。实际上像这样的短文本二分类任务词表1万左右、序列长度不超过100一张8GB显存的老显卡都够用。如果你只有CPU环境也不是不能做把训练样本控制在1万条以内BiLSTM还是能跑完的只是每个epoch从几十秒变成几分钟。做毕设或课设本身对时间不敏感CPU方案完全可以接受。装环境时用conda建一个独立虚拟环境是最稳妥的别直接装到系统python里否则改一个项目很容易把另一个项目的依赖搞坏。2.4 弱标签的思路与其人肉标三千条不如用评分做代理标签深度学习需要大量带标签数据。对电影评论来说豆瓣评分为我们提供了天然的代理标签4星和5星普遍代表“推荐”1星和2星代表“不推荐”3星处于模糊地带。我们可以直接按分数映射成二分类标签。这种做法的前提是平台评分与用户文本态度基本一致整体上成立。好处很明显一个下午可以造出几千条训练样本成本低。代价是噪声会偏高——有人打2星但理由是“粉丝攻击太多片子本身还行”这种样本会被当成负向。对入门项目而言噪声比例10%左右通常不影响模型学习主规律训练出来的模型依然比词典法可用如果要做商用级产品再考虑引入人工抽检和主动学习。数据来源也得有边界感。我一般会选3到5部不同类型、不同口碑的电影让评论覆盖“夸赞、吐槽、中评、水军刷分”各种风格。只看一部电影的评论去训练模型学到的是那部片的语言特点换个题材就失灵。这个准备工作做完下一步才是写爬虫。3. 从豆瓣评论到可用训练集爬虫、清洗与序列化数据质量直接决定深度学习模型的天花板。这一章按“采集-清洗-分词-切分”四步走每一步都给出能直接跑的代码和参数。3.1 爬取公开评论请求头与重试节奏豆瓣短评页面是服务端渲染静态HTML里就有评论内容不需要Selenium。写爬虫的目的是构建自己的训练集不是去压对方服务器所以控制速度比任何技巧都重要。requests加BeautifulSoup足够。import requests import time from bs4 import BeautifulSoup HEADERS { User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36, Accept-Language: zh-CN,zh;q0.9, } def fetch_comments(subject_id: str, max_pages: int 10): comments [] with requests.Session() as session: session.headers.update(HEADERS) for page in range(max_pages): url fhttps://movie.douban.com/subject/{subject_id}/comments resp session.get(url, params{start: page * 20, limit: 20}, timeout5) if resp.status_code ! 200: print(fpage {page} failed: {resp.status_code}) break soup BeautifulSoup(resp.text, html.parser) items soup.select(div.comment-item) if not items: print(no more comment items) break for item in items: text_node item.select_one(span.short) rating_node item.select_one(span[class*rating]) if text_node is None: continue text text_node.get_text(stripTrue) rating _parse_rating(rating_node) comments.append({text: text, rating: rating}) time.sleep(2) # 单页之间的等待时间设成1~3秒比较安全 return comments def _parse_rating(node): if node is None: return None cls node.get(class, []) for c in cls: if c.startswith(allstar): # allstar40 对应4星 return int(c.replace(allstar, )) // 10 return None逻辑说明分页通过params里的start实现每页默认20条。豆瓣星级类名是allstar40这种形式_parse_rating把它解析成4分。代码里判断items为空就break避免无限翻页。time.sleep(2)是为了不触发高频访问限制只抓一部电影时这个间隔可以接受抓很多部电影时建议加宽到3秒以上。参数说明max_pages控制最大翻页数一页20条10页就是200条。对二分类训练我一般建议每部电影抓10到20页再选3到5部不同类型的电影让数据覆盖各种语气。等待时间设成2秒是一个折中值太快容易被限流太慢采集几部电影要等很久。requests超时通常和网络波动有关遇到try/except重试一次即可不要无限重试。提示只抓公开页面控制频率别去碰需要登录才能看的完整评论既不合规又容易被限制。3.2 清洗与弱标签把4星5星设成正向1星2星设成负向抓到原始文本后先做几件事移除HTML标签去掉空白过滤掉太短或者纯表情的评论然后把评分转成标签。import re def clean_text(text: str) - str: text re.sub(r.*?, , text) text re.sub(r\s, , text).strip() return text def score_to_label(rating: int): if rating 4: return 1 if rating 2: return 0 return None # 3分样本直接舍弃 def build_dataset(rows): dataset [] drop 0 for row in rows: text clean_text(row[text]) label score_to_label(row[rating]) if not text or len(text) 4: drop 1 continue if label is None: drop 1 continue dataset.append({text: text, label: label}) return dataset逻辑说明clean_text里.*?用来去HTML标签\s把多个空白压缩成一个。score_to_label把二分类问题简化为“推荐/不推荐”3分属于态度模糊直接舍弃能减少标签噪声训练出的模型边界更清晰。build_dataset用drop变量计数方便你判断数据清洗的损耗率。参数说明len(text) 4用来过滤“不错”“还行”这类过短评论。阈值设成4是因为太短的文本缺少上下文模型很难学会有效特征。如果损耗率超过30%说明你选的电影评论太少需要补充其他数据源。清洗完的数据习惯存成CSV方便用pandas统计分布。列结构通常是字段类型说明textstr清洗后的短评文本labelint0负向/1正向source_moviestr来源电影ID便于后面做数据集去重source_movie这列很多人会漏掉。多电影混合训练时同一部电影的高频评论可能有大量重复按text去重只能去完全一致的内容加上source_movie可以审计数据来源排查“训练集里某一部电影占比过高导致模型偏向”这类问题。3.3 分词与词表构建中文进入模型的必经一步中文没有天然空格需要先分词。jieba是工程上最省事的选择精确模式对影视评论里的日常表达有还不错的切分效果。同时要注意别用搜索引擎模式那会把“哈哈哈哈”切成一堆重复词训练时容易干扰情绪特征的统计。import jieba import pickle def tokenize_text(text: str): return list(jieba.cut(text)) def build_vocab(corpus, min_freq2): freq {} for text in corpus: for w in tokenize_text(text): freq[w] freq.get(w, 0) 1 vocab {pad: 0, unk: 1} for w, c in sorted(freq.items(), keylambda x: (-x[1], x[0])): if c min_freq: vocab[w] len(vocab) return vocab # 使用示例 train_texts [row[text] for row in train_data] vocab build_vocab(train_texts) with open(data/vocab.pkl, wb) as f: pickle.dump(vocab, f)逻辑说明build_vocab统计每个词在全部训练文本里的出现次数保留出现次数在min_freq以上的词按频次从高到低分配ID。 占0 占1这样padding和未知词各自有固定ID也方便embedding层跳过padding。参数说明min_freq默认2表示只出现一次的词会被替换成 。对几千条样本的训练集这个阈值可以把词表控制在8000到12000左右既减少过拟合风险又降低embedding层的参数量。如果数据量超过3万条可以把min_freq抬高到3如果数据只有一两千条就设成1否则大量真实词会被划成未知词影响效果。语料分词后的长度统计也很关键。我一般是把每条评论的词数都统计出来看95分位点落在哪里再把最大长度设成这个值。常见电影短评的95分位在80到100个词之间所以seq_len100在大部分情况下够用。长度超过seq_len的评论截断不足的用 补足。这里的“词”指的是jieba切出的token不是字符标注的时候别弄混。3.4 数据集划分与采样均衡训练集、验证集、测试集按6:2:2切分切分前要shuffle。一个容易踩的坑是直接按顺序切结果某一片全是同一个电影、同一种风格的评论验证集指标虚高。我建议直接用sklearn的train_test_split做分层切分from sklearn.model_selection import train_test_split X [row[text] for row in dataset] y [row[label] for row in dataset] X_train, X_temp, y_train, y_temp train_test_split( X, y, test_size0.4, stratifyy, random_state42 ) X_dev, X_test, y_dev, y_test train_test_split( X_temp, y_temp, test_size0.5, stratifyy_temp, random_state42 )逻辑说明第一轮切出60%给训练集40%做临时集第二轮把临时集一分为二各20%做验证和测试。stratifyy是分层抽样保证正负样本比例在切分后的集合里和原始数据一致。对二分类任务如果不分层小样本场景下很容易出现验证集里负样本太少的情况。参数说明random_state固定成42是为了实验可复现。改任何预处理参数后重新训练对比前后效果时需要保证切分完全一致否则指标变化可能来自数据分布而不是模型改动。处理到这里数据就可以进入模型了。接下来是深度学习的核心部分。4. 用PyTorch搭BiLSTM网络结构、训练循环与参数调试动手深度学习时最忌讳一上来就堆模型。这一章从模型定义开始给出一个可以直接启动的BiLSTM训练流程并把参数说明和调参顺序讲透。4.1 模型定义双向LSTM如何编码评论内容LSTM通过三个门控遗忘门、输入门、输出门控制信息流动比普通RNN更适合长距离建模。把两个方向的LSTM拼起来可以让模型在判断“好看”时需要看到后半句的“但情节拖沓”双向结构能把这种反转信息保留到最后一步输出。import torch import torch.nn as nn class BiLSTMSentiment(nn.Module): def __init__(self, vocab_size, embedding_dim128, hidden_size128, num_layers2, num_classes2, dropout0.3): super().__init__() self.embedding nn.Embedding(vocab_size, embedding_dim, padding_idx0) self.lstm nn.LSTM( embedding_dim, hidden_size, num_layersnum_layers, batch_firstTrue, bidirectionalTrue, dropoutdropout if num_layers 1 else 0. ) self.dropout nn.Dropout(dropout) self.fc nn.Linear(hidden_size * 2, num_classes) def forward(self, x, lengths): emb self.embedding(x) # (B, T, E) packed nn.utils.rnn.pack_padded_sequence( emb, lengths.cpu(), batch_firstTrue, enforce_sortedFalse ) _, (hidden, _) self.lstm(packed) last torch.cat((hidden[-2], hidden[-1]), dim1) out self.fc(self.dropout(last)) return out核心逻辑embedding层把词ID映射成稠密向量padding_idx0让 位置的向量恒为0不参与更新。LSTM层设了batch_firstTrue输入形状是(B, T, E)T表示序列长度。pack_padded_sequence是效率关键它跳过padding位置的计算避免无效运算。最后拼接时hidden[-2]是正向LSTM往最后一个有效词方向输出的隐藏状态hidden[-1]是反向LSTM往第一个有效词方向输出的隐藏状态二者拼接后得到整句话的表示再经过全连接层映射成2分类logits。参数说明hidden_size128是最常用起点太大模型容易过拟合太小表达力不足。num_layers2表示两层堆叠深层网络能捕获更复杂的语义组合但对小数据集反而容易过拟合建议1到2层即可。dropout0.3作用于LSTM层和全连接层之前随机丢弃一部分激活削弱神经元之间的共适应。4.2 训练循环梯度裁剪、早停和学习率衰减模型搭好后训练代码也应当固定套路。先写一个Dataset类把文本实时转成词ID序列from torch.utils.data import DataLoader, Dataset class CommentDataset(Dataset): def __init__(self, texts, labels, vocab, seq_len100): self.texts texts self.labels labels self.vocab vocab self.seq_len seq_len def __len__(self): return len(self.texts) def __getitem__(self, idx): tokens list(jieba.cut(self.texts[idx])) ids [self.vocab.get(w, self.vocab[unk]) for w in tokens] if len(ids) self.seq_len: ids ids[:self.seq_len] length len(ids) ids ids [0] * (self.seq_len - length) return torch.tensor(ids, dtypetorch.long), length, self.labels[idx]这里有个细节返回的ids向量已经补齐到固定seq_lenpadding部分全部是0。length不能做padding后面pack_padded_sequence需要它找出每个样本的有效长度。labels[idx]直接用原始标签DataLoader默认会用collate_fn转成tensor。训练主循环model BiLSTMSentiment(len(vocab)) criterion nn.CrossEntropyLoss() optimizer torch.optim.Adam(model.parameters(), lr1e-3) scheduler torch.optim.lr_scheduler.ReduceLROnPlateau( optimizer, modemax, factor0.5, patience2 ) def evaluate(model, loader): model.eval() correct total 0 with torch.no_grad(): for ids, lengths, labels in loader: logits model(ids, lengths) pred logits.argmax(dim1) correct (pred labels).sum().item() total labels.size(0) return correct / total best_acc 0 for epoch in range(15): model.train() for ids, lengths, labels in train_loader: optimizer.zero_grad() logits model(ids, lengths) loss criterion(logits, labels) loss.backward() nn.utils.clip_grad_norm_(model.parameters(), 5.0) optimizer.step() acc evaluate(model, dev_loader) scheduler.step(acc) print(fepoch {epoch} dev_acc{acc:.4f}) if acc best_acc: best_acc acc torch.save(model.state_dict(), models/bilstm.pt)逻辑说明每轮训练先zero_grad清空梯度前向算logitsCrossEntropyLoss内部已经做了softmax和log运算所以模型最后一层不需要加softmax。loss.backward()后一定要做梯度裁剪LSTM训练时梯度范数很容易爆掉clip_grad_norm_设成5.0表示梯度范数超过5就整体缩放收敛会稳定很多。验证集acc用来驱动ReduceLROnPlateau连续2个epoch准确率没上升时学习率减半保存best_acc对应权重这就是早期停止。参数说明epochs设15不是死值早停起作用后通常7到10轮就停在最优位置。学习率1e-3如果发现loss震荡不下降把lr降到5e-4。batch_size我习惯是64显存不足时降到32batch越大梯度方差越小但训练慢。如果你用GPU在循环开头加ids, labels ids.to(cuda), labels.to(cuda)但lengths必须留在CPU因为pack_padded_sequence要求长度参数在CPU上强行搬过去反而报错。4.3 评价指标为什么要看F1而不是准确率对于电影评论情感分析正负样本往往不是五五开。比如一部口碑不错的电影4星以上评论可能占70%训练集准确率很容易做到85%以上但模型只要把所有样本都预测成正向准确率也有70%完全没识别出负向。因此主要看F1值尤其是负向样本的F1。评估时用sklearn轻松计算from sklearn.metrics import classification_report preds [] labels_list [] with torch.no_grad(): for ids, lengths, labels in test_loader: logits model(ids, lengths) preds.extend(logits.argmax(dim1).tolist()) labels_list.extend(labels.tolist()) print(classification_report(labels_list, preds, target_names[负向, 正向]))F1是精确率和召回率的调和平均。如果报告里负向的F1明显低于正向说明模型把很多吐槽评论误判成了正向。这时优先检查数据是否均衡、评论里是否大量含有讽刺语气再考虑增加训练样本或者引入BERT。4.4 参数对照表一页纸说清默认值和调整方向参数默认值调整方向一句话经验embedding_dim128小词表128大词表256维度太高对短文本收益不大hidden_size128过拟合可降到64两层的128对1万样本够用num_layers2数据多可到3数据少时1层反而更好dropout0.3过拟合到0.5大于0.6容易欠拟合lr1e-3不收敛降到5e-4首选Adam暂时不用动优化器seq_len100取决于长度统计不要超过120除非是长评batch_size64显存不足降到324的倍数与显卡对齐更占带宽clip_grad_norm5.0梯度爆炸时降到1.0固定值即可不是热门调参对象这张表请当成起点而不是终点。每个项目的词表、样本量、长短评分布都不同调参顺序一般是先定seq_len再调hidden_size和dropout最后动embedding_dim。把基线跑出来后再用控制变量法逐项调每次只改一个参数记录对应的F1别同时改三个值否则翻车了都不知道是哪一步的锅。5. 训练和部署中的5个高频踩坑现象、原因与解决模型能不能落地往往不取决于网络结构多高级而取决于你避开了多少坑。这五条是电影评论情感分析里最常遇到的按“现象、原因、解决”写清楚。5.1 训练集准确率接近100%验证集F1却很低现象模型在训练集上几乎不掉点但验证集负向样本F1不到60%整体指标比随机好不了多少。原因典型的过拟合加上样本不均衡。短文本情感分析任务中模型记住了训练集里的特定词和句式换一批新评论就失效。此外训练集里负向样本占比太低模型直接学会把所有输入往正向输出。解决先打印训练集与验证集的标签分布算一个“全预测为正向”的baseline F1做对照。再给模型增加dropout把0.3改成0.5并开启early stopping。最后检查词表大小如果vocab里只有3000个词而语料有几万条大概率是清理环节把大量词误杀了回到数据清洗环节看看正则是否误伤。5.2 换了个电影语料再训练模型判定风格变得很“冲”现象第一批数据练出来的模型预测还行加了一批新电影评论后准确率反而下降有些常见的好评都被判成负向。原因新增语料里某类电影占比太高。比如加入大量恐怖片评论很多描述血腥的词汇被模型当成了负向特征而这种语气和观众偏好并不完全对应也可能是某部电影被大量刷低分标签本身就是噪声。解决按电影来源统计训练集占比单部电影评论最多占20%超了做下采样。训练前按来源分组做去重同一个用户短评反复出现也要清理。宁可数据量少一点也别让分布失衡。5.3 用GPU训练时报错cuDNN error: CUDNN_STATUS_NOT_INITIALIZED现象程序跑到第二个epoch时抛出cuDNN错误有时报错指向某个LSTM算子重启后又好了。原因模型占用显存超过显卡余量cuDNN初始化失败。有时是其他进程占用了显存有时是DataLoader的num_workers开太大内存交换到显存产生冲突。解决先用nvidia-smi查看显存占用把batch_size从64降到32DataLoader里的num_workers设为0或2别设成CPU核心数训练前调用torch.cuda.empty_cache()释放缓存。如果仍然报错重启python进程多数是显存碎片化问题。5.4 上线预测时同一个词在训练和预测阶段行为不一致现象线下评估F1有85%上线后随机抽100条评论发现模型对“不像”这个词特别敏感处处判负向。原因训练时用了训练集的词表但预测时又加载了另一个路径的词典两者词汇ID不对齐或者预测时没有走同一个预处理函数输入文本里的标点、空格没有清洗干净导致分词结果不一样。解决把vocab.pkl和clean_text、tokenize函数统一封装到一个model_bundle里预测时只允许从这一个入口进入。测试时用管道验证对同一条评论先离线预测一次再经接口预测一次两次结果必须完全一致不一致就去查预处理路径。5.5 Flask启动后第一次请求特别慢甚至超时现象接口服务跑起来了但第一个POST请求等了将近10秒之后恢复正常。原因Flask应用启动时没有加载模型第一次请求才执行torch.load同时GPU初始化、cuDNN warmup都发生在这一次请求里。如果加载路径不对还会直接抛FileNotFoundError但被包装成了超时提示。解决在Flask模块的全局作用域里提前加载模型用try/except包裹启动时用一个简单输入预跑一次前向传播作为预热。还要注意torch.load默认把权重加载到CPU如果你部署在GPU环境需要先map_locationcpu再model.to(cuda)否则设备不匹配会报错。6. 把模型包成HTTP接口Flask部署与预测自检最后一步是让别人能用你的模型。部署时我只保留一个最小可用的Flask接口然后给出三句必测文本用来在交付前快速判断模型是不是真的管用。6.1 最小Flask接口模型加载和封装放在全局作用域确保服务启动时就完成初始化from flask import Flask, request, jsonify model.eval() app.post(/sentiment) def sentiment(): data request.get_json() text data.get(text, ) ids encode(text, vocab, seq_len100) # 分词 - 查词表 - padding logits model(ids, torch.tensor([len(ids)])).flatten() prob torch.softmax(logits, dim0) return jsonify({ label: 正向 if prob[1] 0.5 else 负向, score: float(prob[1]) })这里没有贴encode的完整实现因为那就是第3章的tokenize和padding逻辑。关键是返回概率而不是离散标签业务侧可以根据阈值决定“疑似吐槽”的评论是否进入人工审核。阈值默认0.5跑完自检后可以再微调。6.2 三个必测句接口写完别急着接业务先用三类典型句子做回归测试否定型“这部片子很不错但是我不推荐给朋友。”反讽型“导演真是敢拍敢把这种剧本搬上银幕。”重复表达型“哈哈哈哈太好笑了笑到肚子疼。”这三类分别检验转折语义、反讽识别、表情语气的处理能力。预期结果需要人工先定好再和模型输出比对。如果三类里有两类不符合预期大概率是训练数据里缺少类似表达这不是调参能解决的要回到数据采集环节补样本。6.3 一条进阶路径从BiLSTM换到BERT想继续往上提效果最快的方式不是继续调LSTM而是做迁移学习。常见做法是用中文BERT或RoBERTa作为句子编码器后面接线性分类层做微调。BERT能同时看到整句话的上下文对反讽更敏感但训练时间从十几分钟变成小时级显存需求也更大。这种从基线上扩展成BERT的路径是最容易复现的深度学习实战项目案例答辩或项目汇报时也更有纵深。我现在做文本分类习惯是先拿一个简单模型快速跑通数据流再决定要不要升级模型结构。先把第3章的数据管道和第4章的训练代码跑通剩下的都是水到渠成的事。希望这几条部署习惯能帮到你。本文还有配套的精品资源点击获取
返回列表