☰
基于BERT的中文情感分析实战:环境搭建、微调与Flask部署
2026/10/1 9:37:33 网站建设 项目流程

简介:一套基于BERT实现的中文情感分析数据分类Python源码,源自评分98分的毕业设计项目,主要面向需要完成毕设、课程设计或期末大作业的学生,也适合NLP入门者学习参考。项目代码带有清晰注释,由个人手打完成,部署门槛低,下载后简单配置即可直接运行,整体功能完善、界面美观、操作简洁。压缩包共74个文件,大小53.23MB,其中30个Python脚本覆盖模型构建、数据预处理、训练与预测等核心环节,并包含可直接调用的推理脚本;25个CSV文件为格式统一的中文情感标注语料;12个TXT文本、2个Shell脚本和2个Markdown文档则提供使用说明、环境配置与启动命令。已有254人学习下载,可视为情感分类任务的完整实战模板。项目从数据清洗、BERT微调到分类评估均有清晰实现,代码结构按功能模块划分,便于二次开发,也能帮助读者快速掌握中文情感分类的工程化流程,是毕业设计或课程设计的高分参考。

1. 为什么说“基于BERT的中文情感分析”是高分毕业设计的稳妥选题

情感分析是自然语言处理里最容易“出成果”的方向之一。京东评论、微博、外卖评价这类数据随处可下,评价标准又直观——准确率高就是高。BERT这个预训练模型把底层语义表示几乎全做完了,你只需要专注在数据清洗、微调和部署展示。相比从零实现Transformer或注意力机制,这个题目收敛确定、坑有数可循;论文里又有“预训练+迁移学习”的时髦切入点,做起来心里有底,答辩时也站得住。适合NLP方向、想拿高分又不想赌运气的同学,这也是我把这套Python源码按毕业设计规格来组织的原因。

2. 环境搭建与模型选型:把bert-base-chinese跑起来的最小配置

2.1 为什么是bert-base-chinese而不是其他中文预训练模型

BERT进入中文市场后出现了多个变体:谷歌官方的中文BERT、哈工大的BERT-wwm-ext、RoBERTa-wwm-ext-large等。我在决定毕业设计选用哪个底座时,踩过比较多的坑。哈工大的wwm系列在预训练阶段采用全词掩码,对中文这种没有天然空格分隔的语言来说更友好,在序列标注类任务上往往有更好表现。但情感分析属于句子级分类任务,wwm-ext带来的提升通常不超过0.3个百分点,而它的模型体积更大、下载也更麻烦。最稳妥的选择还是官方bert-base-chinese,原因有三:它的问题反馈最多,任何加载报错都能搜到现成解法;它的tokenizer是字级别的,不需要额外安装jieba之类的分词组件;网上公开的基准评测大多以它为baseline,论文里写对比实验时可以直接引用。

这里还有个现实的考量:毕业设计通常只有一两周时间用于调模型。如果选择RoBERTa-wwm-ext,你需要处理更复杂的配置,因为它没有原生的tokenizer文件,需要用bert-base-chinese的tokenizer代跑。这个“代跑”的逻辑在答辩时容易把自己绕晕,而使用官方bert-base-chinese整套加载就是一个from_pretrained调用,前后自洽,几乎没有解释成本。

2.2 本地环境怎么装:Python、torch和transformers的版本组合

环境装不对,后面全是泪。先说结论:Python 3.9、PyTorch 2.x、transformers 4.30+这个组合目前最稳定。用Anaconda建独立环境,这是我最常用的做法,能避免和系统Python打架:

conda create -n sentiment python=3.9 -y conda activate sentiment pip install torch --index-url https://download.pytorch.org/whl/cu118 pip install transformers datasets pandas scikit-learn tqdm

逐行看。第一条创建名为sentiment的虚拟环境并激活。第三条安装GPU版PyTorch,cu118对应CUDA 11.8,如果你显卡驱动较新,可以直接装cu121或cu124版本,没有统一标准,重点看驱动支持的最高CUDA版本。CPU机器就把--index-url换成https://download.pytorch.org/whl/cpu,否则后面训练时虽然能跑但会慢到怀疑人生。第四条安装模型库和其他数据处理库,datasets在后面做数据集封装时会用到。

安装完成后建议做个冒烟测试,确认torch能看到GPU:

python -c "import torch; print(torch.cuda.is_available(), torch.__version__)"

输出True和2.x就说明环境合格。我在不少机器上发现,conda默认安装的torch是CPU版,这一步测试能及时暴露问题,省得训练到一半才发现用的是CPU。

2.3 CPU也能跑通吗:显存占用、内存和时间预算

bert-base-chinese约1.02亿参数,模型文件大概400MB。用batch size=16、max_len=128跑微调,显存占用大约5到6GB,所以6GB显存的GPU勉强能跑,8GB比较舒服。超过512长度的文本必须截断,否则显存和内存都扛不住。

如果只有CPU,也不是完全不推荐,但时间成本要算清楚:1万条样本、3个epoch,CPU大概需要2到3小时每个epoch,一旦中间代码报错重启,很容易拖到提交前夜。我的建议是,能用GPU就用GPU;实在没有,可以缩小数据量,比如从原始数据里抽样3000条做演示,把训练时间压缩到20分钟以内,再用剩下的数据做离线评估。

注意:CPU训练时建议把torch.set_num_threads(4)加上,默认线程数过多反而会因为上下文切换变慢,这是很多人在笔记本上跑不动的原因之一。

2.4 模型权重下载失败:离线加载的后悔药

第一次运行from_pretrained("bert-base-chinese")时,库会尝试联网下载约400MB权重。网速不稳或镜像被限时,经常出现下载到一半超时。常见的解决方案是提前到镜像站把模型文件下下来,放到本地目录,再改成从本地路径加载。目录里需要config.json、pytorch_model.bin、vocab.txt三个文件就够:

from transformers import AutoTokenizer, AutoModelForSequenceClassification model_dir = "./chinese_bert_base" tokenizer = AutoTokenizer.from_pretrained(model_dir) model = AutoModelForSequenceClassification.from_pretrained( model_dir, num_labels=2 )

这样写的好处是加载时不走网络,也不依赖环境变量,后续把整个文件夹拷贝到别的机器一样能跑。需要注意,pytorch_model.bin下载后要检查文件大小,有些镜像站返回的是错误页存成了HTML,加载时会报“unexpected key”之类的错误,这类问题后面第五章里面专门说。

3. 数据预处理:从原始评论到BERT输入Tensor的完整转换

3.1 数据长什么样:CSV两列就够,但要注意编码

情感分析任务最常用的数据格式是CSV两列:label和text。0代表负面/差评,1代表正面/好评。我一般会在开头统一标签,避免训练时发现标签反了。读取时用pandas,指定dtype和编码可以避免很多幺蛾子:

import pandas as pd df = pd.read_csv("review_data.csv", encoding="utf-8", dtype={"label": int, "text": str}) print(df["label"].value_counts()) print(df.head())

需要注意编码问题。很多中文数据集是GBK或GB2312编码,直接用utf-8读取会报UnicodeDecodeError。Windows机器上导出的Excel CSV尤其常见。处理方式有两种:要么用encoding="gbk"读取,要么用encoding="utf-8-sig"。我一般先尝试utf-8-sig,因为它能自动去掉BOM头,读取失败再换gbk。

还有个小坑:label列里可能存在非0/1的值,比如2或-1。这种脏数据会导致分类层输出维度与数据不匹配。这里建议在预处理阶段就做一次过滤:

df = df[df["label"].isin([0, 1])]

这一步看似多余,但它能省掉训练中大量莫名其妙的报错——早年在没有这行代码的情况下,我试过label=2混进训练集,训练loss一直降到很离谱的0.6附近但accuracy卡在0.5,查了很久才发现是标签越界。

3.2 用AutoTokenizer把文本转成input_ids、attention_mask和token_type_ids

BERT的输入不是原始字符串,而是三个Tensor:input_ids(每个字的ID)、attention_mask(哪些位置是真实内容,哪些是padding)、token_type_ids(区分句子的,单句分类任务里用不到但也会生成)。用AutoTokenizer一次性搞定:

from transformers import AutoTokenizer tokenizer = AutoTokenizer.from_pretrained(model_dir) texts = df["text"].tolist() labels = df["label"].tolist() encodings = tokenizer( texts, max_length=128, padding="max_length", truncation=True, return_tensors="pt", )

这段代码把整个列表一次编码。max_length=128控制序列长度,超过128从尾部截断,不足128用padding补到同样长度。padding="max_length"表示所有样本都补到128,这样训练时不需要动态padding,速度更快但浪费显存;数据长度差异很大时,建议改成padding=True,用collate_fn动态合并。

注意return_tensors="pt"返回的是PyTorch的Tensor格式,可以直接喂给模型。编码后encodings里有三个键:input_ids、attention_mask、token_type_ids。其中token_type_ids在这个任务里全为0,因为它只处理单句;如果做的是“前提-假设”这种双句任务才需要用1区分第二个句子。

3.3 自己写Dataset还是用datasets库

数据量在万级别时,自己写一个torch Dataset最直接,因为你能精确控制每个字段,出了问题也容易定位。常见做法是:

import torch from torch.utils.data import Dataset class SentimentDataset(Dataset): def __init__(self, encodings, labels): self.encodings = encodings self.labels = labels def __getitem__(self, idx): item = {key: val[idx] for key, val in self.encodings.items()} item["labels"] = torch.tensor(self.labels[idx], dtype=torch.long) return item def __len__(self): return len(self.labels) train_dataset = SentimentDataset(encodings, labels)

这里__getitem__方法返回一个字典,里面包含input_ids、attention_mask、token_type_ids和labels四个键,每个键对应的值都是Tensor。labels使用torch.long是因为CrossEntropyLoss要求目标为长整型。训练时DataLoader会按batch去取这些字典,自动堆叠成形状为[batch_size, seq_len]的Tensor。

如果不想写Dataset,HuggingFace的datasets库可以直接把列表转成Dataset对象,然后直接map。比如:

from datasets import Dataset ds = Dataset.from_dict({"text": texts, "label": labels}) ds = ds.map(lambda x: tokenizer(x["text"], max_length=128, truncation=True), batched=True) ds.set_format("torch")

两种写法我都试过。小数据量或需要反复调试时,手写Dataset更清爽;之后要用HuggingFace Trainer接口做微调时,datasets库更方便,因为Trainer自动从Dataset对象里读取input_ids等字段,不用额外适配。

3.4 训练集、验证集的切分要分层

情感分析数据往往类别不均衡,正面评论可能占70%,负面占30%。如果随机切分,运气不好时验证集可能只有少量负面样本,导致验证准确率波动很大。推荐按标签分层切分:

from sklearn.model_selection import train_test_split train_texts, val_texts, train_labels, val_labels = train_test_split( texts, labels, test_size=0.15, random_state=42, stratify=labels, )

stratify=labels参数确保切分前后各类别占比一致。random_state固定为42,保证每次运行切分结果相同,这个对复现实验非常重要——答辩时如果老师问“你上次的结果还能复现吗”,你至少能让报告里的数字稳定复现。

4. 微调训练:核心Python源码与三个影响精度的关键参数

4.1 加载预训练模型并替换分类头

AutoModelForSequenceClassification是transformers里专门为分类任务封装好的类。它加载BERT主体,并在最上面加一个全连接分类层,输出的logits维度就是num_labels。如果num_labels不设置,默认值是2,但你最好显式传,避免以后改成三分类时忘了:

import torch from transformers import AutoModelForSequenceClassification device = torch.device("cuda" if torch.cuda.is_available() else "cpu") model = AutoModelForSequenceClassification.from_pretrained( model_dir, num_labels=2, return_dict=True, ).to(device)

return_dict=True让模型输出一个包含logits、hidden_states等字段的对象。直接用model(**batch).logits拿分类结果,比旧版本输出的元组更直观。

这一节还有一个常见的进阶操作:冻结部分BERT层只训练分类头。对毕业设计来说,我建议不要冻结。微调全部参数虽然在1万条数据上也就多花20分钟,但效果通常更好。冻结方法(把requires_grad设成False)适合算力非常紧张或者数据量极小的场景,不属于必要操作。

4.2 训练循环:日志、学习率调度与梯度累积

不依赖Trainer、自己手写训练循环,能让你把每一步都看清楚。代码框架如下:

from torch.utils.data import DataLoader from transformers import get_linear_schedule_with_warmup from tqdm.auto import tqdm train_loader = DataLoader(train_dataset, batch_size=16, shuffle=True) val_loader = DataLoader(val_dataset, batch_size=64, shuffle=False) optimizer = torch.optim.AdamW(model.parameters(), lr=2e-5) total_steps = len(train_loader) * 3 scheduler = get_linear_schedule_with_warmup( optimizer, num_warmup_steps=int(0.1 * total_steps), num_training_steps=total_steps, ) for epoch in range(3): model.train() total_loss = 0 for batch in tqdm(train_loader, desc=f"Epoch {epoch+1}"): batch = {k: v.to(device) for k, v in batch.items()} outputs = model(**batch) loss = outputs.loss loss.backward() optimizer.step() scheduler.step() optimizer.zero_grad() total_loss += loss.item() print(f"Epoch {epoch+1} loss: {total_loss / len(train_loader):.4f}")

模型前向时如果传入了labels字段,AutoModelForSequenceClassification会自动计算loss并放进outputs.loss,所以不需要手工调用criterion。AdamW在BERT类模型的微调里是标配,学习率用2e-5是历代调参者公认比较稳的起始点,比1e-4之类常见于CNN任务的学习率低得多。

warmup步数设为总步数的10%,作用是让学习率从0线性上升到设定值,避免模型在初期剧烈震荡。scheduler.step()要紧跟在optimizer.step()之后调用,顺序错误会导致学习率调度错位。训练时观察每个epoch的loss,如果epoch 0结束时还在0.69附近而且下降极慢,说明学习率太低,可以趁早停机调整。

4.3 早停与最佳模型保存,别让最后5分钟的过拟合毁掉精度

BERT参数量大,训练集只有几千条时很容易过拟合。验证集准确率通常在某个epoch到达峰值,之后开始下降,这就是过拟合的典型信号。更可靠的保存方式不是保存epoch 3的模型,而是保存验证集表现最好的那次:

best_acc = 0.0 for epoch in range(epochs): # training loop... # validation loop... acc = (preds == labels).mean().item() if acc > best_acc: best_acc = acc torch.save(model.state_dict(), "best_model.pt") print(f"save best model, acc={acc:.4f}")

保存state_dict而不是整个model,好处是文件小、加载灵活,并且不依赖模型类定义所在的代码环境。加载时先重新实例化模型,再load_state_dict即可:

model = AutoModelForSequenceClassification.from_pretrained(model_dir, num_labels=2) model.load_state_dict(torch.load("best_model.pt")) model.to(device)

如果不想自己写早停逻辑,可以在验证loss连续2个epoch不降时break。不过对毕业设计来说,固定训练3个epoch然后选最高验证acc的模型已经足够。

4.4 验证代码:准确率、F1与混淆矩阵

验证阶段代码相对简单。核心是把模型切到eval模式,开torch.no_grad(),然后遍历验证集:

from sklearn.metrics import accuracy_score, f1_score, confusion_matrix model.eval() with torch.no_grad(): preds, true_labels = [], [] for batch in val_loader: batch = {k: v.to(device) for k, v in batch.items()} outputs = model(**batch) logits = outputs.logits preds.extend(torch.argmax(logits, dim=-1).cpu().tolist()) true_labels.extend(batch["labels"].cpu().tolist()) print("accuracy:", accuracy_score(true_labels, preds)) print("f1:", f1_score(true_labels, preds, average="weighted")) print(confusion_matrix(true_labels, preds))

这一步输出的数字直接写进论文的实验结果表。注意模型在eval模式下必须要加torch.no_grad(),否则会把推理阶段也保存在计算图里,显存占用直接翻倍,这是在很多机器上会导致OOM的隐藏原因。

5. 评测与避坑:准确率提不上去、显存炸掉的常见原因

5.1 先明确评测口径:准确率、F1还是AUC

情感分析虽然是二分类,但单纯看Accuracy会在样本不均衡时骗人。比如训练集里80%正面20%负面,一个把所有样本都判为正面的傻瓜模型也有80%准确率。论文里最体面的做法是同时报Accuracy、F1和混淆矩阵,前面代码里已经算好了。答辩时如果老师质疑准确率,你能拿出F1和混淆矩阵说明模型没有退化成多数类分类器,这是一个很实用的应答储备。

5.2 坑一:训练loss能下降,验证集准确率却纹丝不动

现象:训练loss从0.69降到0.2,每个epoch在训练集上表现不错,但一跑验证集就准确率50%左右,和随机猜差不多。

原因:最常见的不是过拟合,而是数据预处理时验证集和训练集被污染,比如文本在写入CSV时带了空行或NaN,被pandas读成了NaN列,模型实际在学“空文本”这个特征。还有一个可能性是train_test_split没加stratify,验证集里全是同一种标签。

解决:先打印验证集的标签分布,val_labels.value_counts(),确认两类样本都存在。再检查train_dataset和val_dataset的tokenizer结果,看看有没有空input_ids。最稳妥的方式是把预处理后的数据集随机抽几条打印出来,人眼看一下,确认文本和标签对得上。这一步排错花10分钟,比闷头调参两天有用得多。

5.3 坑二:第二次跑结果完全不一样,怀疑是玄学

现象:同一个脚本、同一条随机种子,隔一天再跑,准确率从87%变成84%。

原因:PyTorch的DataLoader默认shuffle用全局随机状态,模型权重初始化使用的是随机状态,甚至GPU上的非确定性操作都会导致结果波动。你只固定了train_test_split的random_state,没有固定训练阶段的随机种子。

解决:训练脚本最前面固定所有随机源:

import random import numpy as np import torch def set_seed(seed=42): random.seed(seed) np.random.seed(seed) torch.manual_seed(seed) torch.cuda.manual_seed_all(seed) torch.backends.cudnn.deterministic = True torch.backends.cudnn.benchmark = False

set_seed(42)之后,实验结果基本可复现。需要说明的是,即使这样做,GPU上某些操作(比如atomicAdd)仍可能引入微小误差,但毕业设计的精度波动会控制在0.5%以内,完全够用。

5.4 坑三:CUDA out of memory,到底改batch_size还是改max_len

现象:训练第二个batch就报RuntimeError: CUDA out of memory。

原因:显存门槛卡住了。BERT本身占约1.2GB,前向中间激活值会随batch_size和max_len线性增长,backward还要再存一份梯度,显存很容易顶到上限。

解决:先降batch_size,从16降到8或4,这是最直接的手段。如果还不行,再降max_len,128改成64,或者打开梯度累积。梯度累积的做法是把batch拆成多个小batch,累计若干次loss再更新一次权重,效果上等价于大batch:

accumulation_steps = 4 optimizer.zero_grad() for step, batch in enumerate(train_loader): outputs = model(**batch) loss = outputs.loss / accumulation_steps loss.backward() if (step + 1) % accumulation_steps == 0: optimizer.step() scheduler.step() optimizer.zero_grad()

注意这里loss要除以accumulation_steps,否则等效的batch size变大但学习率没有相应调整,loss曲线会被拉高。

5.5 坑四:长文本被尾截断,情感关键词被切没了

现象:某条明显是差评的文本被模型判成好评,调试时发现文本结尾的关键词“垃圾”“差评”没有被编码进去。

原因:tokenizer的truncation=True默认是截断尾部,而BERT最大输入是512。中文电商评论超过512的很少,但如果你做的是微博长文本或新闻评论,尾部恰恰最容易出现情感词。

解决:改用保留头部和尾部的自定义截断策略。常见办法是先用tokenizer.encode得到完整token序列,再手动拼接:

def smart_truncate(text, max_len=128): tokens = tokenizer.encode(text, add_special_tokens=False) if len(tokens) <= max_len - 2: return tokenizer.encode(text) head = tokens[:80] tail = tokens[-40:] for t in tail: head.append(t) return head

这个策略能保住文本开头和结尾的情感信息,代价是中间内容丢失。用在情感分析上效果通常比尾截断好一些,因为中文评论的“起兴”和“总结”往往就分布在两端。

5.6 坑五:标签不均衡导致Accuracy虚高

现象:训练结束后Accuracy有92%,看着很唬人。一算F1,负类的F1只有0.4。

原因:负面样本只占10%,模型只要全部判正面就能拿到90%准确率。这也是情感分析里最容易在答辩时被一票否决的问题。

解决:训练阶段使用weighted CrossEntropyLoss,给样本少的类别更高的权重。实现上从sklearn拿class_weight:

from sklearn.utils.class_weight import compute_class_weight import numpy as np classes = np.array([0, 1]) weights = compute_class_weight(class_weight="balanced", classes=classes, y=np.array(labels)) criterion = torch.nn.CrossEntropyLoss(weight=torch.tensor(weights, dtype=torch.float).to(device))

然后把criterion替换掉模型内置的loss。注意同时把批次里的labels传给模型时,模型会继续自动算loss,所以要么在调用模型前把labels从batch字典里删掉:

batch_no_labels = {k: v for k, v in batch.items() if k != "labels"} logits = model(**batch_no_labels).logits loss = criterion(logits, batch["labels"])

方法虽然有点绕,但这是处理不均衡数据最干净的做法。如果嫌麻烦,也可以用HuggingFace Trainer并配置class_weight参数,但手写循环里更可控。

6. 落地展示:用Flask把模型包装成可演示的情感分析接口

6.1 一个最小可用的情感分析API

毕业设计答辩最怕“说了半天,没有能点上两下的东西”。把训练好的BERT模型包成一个web接口,输入一句评价立刻返回正面/负面和置信度,展示效果远好于在终端里打印一行准确率。用Flask几十行就能搞定:

from flask import Flask, request, jsonify import torch from transformers import AutoTokenizer, AutoModelForSequenceClassification app = Flask(__name__) device = torch.device("cuda" if torch.cuda.is_available() else "cpu") tokenizer = AutoTokenizer.from_pretrained("./chinese_bert_base") model = AutoModelForSequenceClassification.from_pretrained( "./chinese_bert_base", num_labels=2 ) model.load_state_dict(torch.load("best_model.pt", map_location=device)) model.to(device).eval() @app.route("/predict", methods=["POST"]) def predict(): data = request.get_json(force=True) text = data.get("text", "") if not text: return jsonify({"error": "empty text"}), 400 inputs = tokenizer( text, max_length=128, truncation=True, return_tensors="pt", ) inputs = {k: v.to(device) for k, v in inputs.items()} with torch.no_grad(): logits = model(**inputs).logits probs = torch.softmax(logits, dim=-1)[0] pred = int(torch.argmax(probs).item()) confidence = float(probs[pred].item()) return jsonify({ "label": pred, "sentiment": "正面" if pred == 1 else "负面", "confidence": round(confidence, 4), }) if __name__ == "__main__": app.run(host="0.0.0.0", port=5000)

这里我习惯把return_dict里的logits取出来,再套一个softmax得到接近概率的置信度。注意模型初始化后要load_state_dict载入训练好的权重,否则接口返回的是未微调模型的随机结果。

启动后用curl测一下:

curl -X POST http://127.0.0.1:5000/predict \ -H "Content-Type: application/json" \ -d '{"text": "这家店的奶茶太甜了,下次不会再来了"}'

返回{"label":0,"sentiment":"负面","confidence":0.98}。调用方只要换掉请求里的文本,就能展示任意评论的情感倾向。Flask默认只监听本机IP,host="0.0.0.0"是为了答辩时展厅里的电脑能通过局域网访问,安全性在局域网演示场景下够用。

6.2 推理性能的小优化:批处理和半精度

BERT单条推理在GPU上大约20到50ms,CPU上大约200到500ms,交互上已经足够顺滑。但如果做批量数据预测,比如一次性给5000条评论分类统计好评率,逐条for循环会显得很磨叽。最简单的优化是让接口支持批量传入:

def batch_predict(texts, max_len=128): inputs = tokenizer( texts, padding=True, truncation=True, max_length=max_len, return_tensors="pt", ) inputs = {k: v.to(device) for k, v in inputs.items()} with torch.no_grad(): logits = model(**inputs).logits preds = torch.argmax(logits, dim=-1).cpu().tolist() return preds

padding=True让每个batch内的样本自动对齐到最长序列,比padding到固定长度更省算力。实际测试中,GPU上把1000条评论合批预测能把吞吐量提升七八倍。如果追求极致,还可以把模型转成half精度,model.half(),显存直接减半、速度还有提升,但注意输入Tensor也要对应转成half。

6.3 答辩前的最后一件事:准备一组反例测试用例

答辩老师很容易拿一两个反例测试模型的泛化能力,比如“这家店味道不错,就是等位太久”这类混合情感句子。我习惯提前准备20条覆盖否定、转折、emoji、繁体字的测试用例,把预测结果和人工判断列成一张表写进幻灯片附录。这样做既能证明你考虑过边界情况,也能提前暴露模型的短板,不至于现场被打个措手不及。说到底,毕业设计展示的不是“一个完美模型”,而是“一个你完全理解、知道边界在哪的模型”。这个体感,比堆一堆看起来很新的指标更让老师信服。希望这份基于BERT的中文情感分析Python源码整理,能帮你少走几个弯路,把精力花在真正能加分的地方。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询