news 2026/10/5 2:58:23

中文BERT情感分类全链路工程实践:从Tokenization到Docker部署

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
中文BERT情感分类全链路工程实践:从Tokenization到Docker部署

简介:本资源是一套面向自然语言处理初学者与进阶实践者的中文情感分类完整实验方案,聚焦BERT模型在真实中文文本场景下的落地应用。项目以情感分析为任务主线,提供从数据预处理、模型微调、特征提取到预测部署的全流程Python实现,覆盖NLP核心技能链,特别适合课程设计、科研复现及工程化入门学习。压缩包共22个文件,含11个关键Python脚本(如run_classifier.py、predict.py、tokenization.py等)、2个CSV数据集、2个Shell执行脚本、3个说明类文本及Markdown文档,整体4.87MB,结构清晰、模块解耦,便于按训练/预测/数据/配置分层理解。已有312人学习下载,读者可直接复现实验结果,获取可调试的BERT中文分类源码、标准化数据处理流程、详尽的实验记录与环境依赖说明(requirements.txt),并参考readme.txt和multilingual.md快速掌握项目架构与扩展路径。

1. 这不是调个 pretrain 模型就完事的“情感分类”:一个真实跑通中文 BERT 分类链路的完整工程切片

你手头刚拿到一份标着“BERT 中文情感分类”的压缩包,解压后看到run_classifier.py、train.csv、predict.py,第一反应可能是:“哦,改下路径,pip install -r requirements.txt,python run_classifier.py —data_dir ./data —bert_model bert-base-chinese … 然后坐等 acc 上 92%?”
现实是:你大概率会在第 3 步卡住——tokenization.py 报KeyError: '##好';第 5 步崩溃——ValueError: Input mask has wrong shape;第 7 步怀疑人生——预测结果全是中性,连“太差了!”和“绝了!”都分不清。
这不是模型不行,而是这份资源根本不是“玩具 demo”,而是一份从预训练语料清洗、中文子词切分适配、下游任务微调结构定义、到推理服务封装全链路可复现的工程切片。它包含 11 个 Python 脚本(含modeling.py的自定义层、tokenization.py的中文 WordPiece 实现)、2 个 Shell 脚本(train.sh和predict.sh封装了真实生产环境的启动逻辑)、以及dev.csv/train_sentiment.txt这类带原始标注噪声的中文数据——它们不是 Kaggle 那种干净 CSV,而是从电商评论、微博短文本里爬下来、带 emoji、带错别字、带中英混排的真实语料。适合谁?适合正在写毕设却卡在“BERT 怎么真正训中文”的研究生;适合想把 NLP 模型落地到客服工单情绪识别但被 tokenization 坑过三次的算法工程师;也适合刚学完《动手学深度学习》、想亲手拆开 BERT 微调黑匣子的 Python 开发者。它不教你 Attention 是什么,但它会告诉你:为什么max_seq_length=128在中文场景下必须砍到 64 才不 OOM,为什么train_sentiment.txt里的“一般般”要被 tokenizer 切成['一', '般', '般']而不是['一般', '般'],以及predict.sh里那行--do_lower_case=False是救你于中文大小写误判的后悔药。


2. 从train.sh到run_classifier.py:一条不能跳过的微调执行链

2.1train.sh:不只是 shell 脚本,它是环境与参数的契约

train.sh是整个训练流程的入口,它不是简单地调用python run_classifier.py,而是通过显式声明所有关键参数,强制你在复现前直面每一个决策点。打开它,你会看到:

#!/bin/bash export CUDA_VISIBLE_DEVICES=0 python run_classifier.py \ --task_name=sentiment \ --do_train=true \ --do_eval=true \ --data_dir=./data \ --vocab_file=./chinese_L-12_H-768_A-12/vocab.txt \ --bert_config_file=./chinese_L-12_H-768_A-12/bert_config.json \ --init_checkpoint=./chinese_L-12_H-768_A-12/pytorch_model.bin \ --max_seq_length=64 \ --train_batch_size=32 \ --learning_rate=2e-5 \ --num_train_epochs=3.0 \ --output_dir=./output/sentiment_model/

注意:--vocab_file指向的是chinese_L-12_H-768_A-12/vocab.txt,这是 Google 官方发布的中文 BERT Base 模型词表,而非英文版。如果你直接用bert-base-uncased的 vocab,tokenization.py会在遇到“的”“了”等高频字时抛出KeyError——因为英文 vocab 根本没有这些 token。--max_seq_length=64是硬性要求:中文平均字数密度远高于英文,128在 batch_size=32 下极易触发 CUDA out of memory,这是实测血泪经验,不是拍脑袋。

这个脚本的核心价值在于固化环境变量(CUDA_VISIBLE_DEVICES)和所有超参。它告诉你:这不是一个“改改路径就能跑”的 demo,而是一个需要你明确指定 GPU 设备、词表路径、配置文件、初始化权重的完整训练契约。删掉export CUDA_VISIBLE_DEVICES=0?你的训练可能静默占用所有卡;漏掉--do_eval=true?你将永远看不到验证集 loss 曲线,无法判断是否过拟合。

2.2run_classifier.py:微调任务的骨架,Processor是灵魂

run_classifier.py是 BERT 官方run_pretraining.py的下游任务适配器。它的核心逻辑是:加载 BERT 模型 → 构建分类 head → 读取数据 → 训练/评估。但真正决定你能否跑通的,是它依赖的DataProcessor子类。项目里没有明说,但根据train_sentiment.txt和dev.csv的格式,sentimenttask 对应的 processor 必然在run_classifier.py内部或intent.py中定义。我们来还原它:

# intent.py (推断存在,因项目正文列出 intent.py) class SentimentProcessor(DataProcessor): """Processor for the Chinese sentiment classification dataset.""" def get_train_examples(self, data_dir): # 读取 train_sentiment.txt,每行格式:label\ttext # 注意:label 可能是 '0'/'1'/'2' 或 'negative'/'neutral'/'positive' return self._create_examples( self._read_tsv(os.path.join(data_dir, "train_sentiment.txt")), "train") def get_dev_examples(self, data_dir): # 读取 dev.csv,pandas 读取后需处理 header 和 label 列名 df = pd.read_csv(os.path.join(data_dir, "dev.csv")) # 假设列名为 'text' 和 'label',且 label 是 int 类型 examples = [] for i, row in df.iterrows(): examples.append( InputExample(guid=f"dev-{i}", text_a=str(row['text']), label=str(row['label']))) return examples def get_labels(self): # 必须返回字符串列表,BERT classifier head 依赖此定义输出维度 return ["0", "1", "2"] # 或 ["negative", "neutral", "positive"]

逻辑说明:get_labels()返回值直接决定分类头的num_labels。如果train_sentiment.txt里 label 是0/1/2,但get_labels()返回["negative","positive"],模型最后一层会报维度不匹配。_create_examples()中text_a=str(row['text'])强制转字符串,是为了防止 CSV 中空单元格读成nan导致 tokenizer 崩溃。InputExample的guid字段虽不参与计算,但在 debug 时可通过它定位出错的具体样本行号。

2.3modeling.py:不是黑盒,是可调试的模型定义

modeling.py是整个项目的基石。它不是简单import transformers,而是实现了 BERT 的核心结构:BertModel、BertConfig、BertLayer。重点看BertForSequenceClassification类:

# modeling.py class BertForSequenceClassification(BertPreTrainedModel): def __init__(self, config, num_labels=3): super(BertForSequenceClassification, self).__init__(config) self.num_labels = num_labels self.bert = BertModel(config) # 加载预训练 BERT encoder self.dropout = nn.Dropout(config.hidden_dropout_prob) self.classifier = nn.Linear(config.hidden_size, num_labels) # 自定义分类头 self.apply(self.init_weights) # 权重初始化 def forward(self, input_ids, token_type_ids=None, attention_mask=None, labels=None): # 1. BERT encoder 输出 [batch, seq_len, hidden_size] outputs = self.bert(input_ids, token_type_ids, attention_mask) pooled_output = outputs[1] # 取 [CLS] token 的 pooled output # 2. dropout + classifier pooled_output = self.dropout(pooled_output) logits = self.classifier(pooled_output) if labels is not None: loss_fct = CrossEntropyLoss() loss = loss_fct(logits.view(-1, self.num_labels), labels.view(-1)) return loss, logits else: return logits

参数说明:num_labels=3必须与SentimentProcessor.get_labels()返回长度一致。pooled_output = outputs[1]是关键——outputs[0]是所有 token 的 hidden states,outputs[1]是[CLS]token 经过 pooling layer 后的向量,这才是分类任务的标准输入。如果你误用outputs[0][:, 0, :](取第一个 token),在某些 BERT 版本下会因维度对齐问题报错。self.apply(self.init_weights)确保新添加的classifier层使用正确的 Xavier 初始化,否则新层权重为零会导致训练初期 loss 不下降。


3.tokenization.py与extract_features.py:中文文本的生死线

3.1tokenization.py:WordPiece 切分不是魔法,是查表+规则

BERT 的中文能力,90% 依赖tokenization.py。它不是调用 jieba,而是实现 Google 的 WordPiece 算法。核心函数convert_to_unicode()和FullTokenizer:

# tokenization.py def convert_to_unicode(text): """Converts `text` to Unicode (if it's not already), assuming utf-8 input.""" if isinstance(text, str): return text elif isinstance(text, bytes): return text.decode("utf-8", "ignore") else: raise ValueError("Unsupported string type: %s" % (type(text))) class FullTokenizer(object): def __init__(self, vocab_file, do_lower_case=True): self.vocab = load_vocab(vocab_file) # 加载 vocab.txt 到 dict: {token: id} self.inv_vocab = {v: k for k, v in self.vocab.items()} self.basic_tokenizer = BasicTokenizer(do_lower_case=do_lower_case) self.wordpiece_tokenizer = WordpieceTokenizer(vocab=self.vocab) def tokenize(self, text): split_tokens = [] for token in self.basic_tokenizer.tokenize(text): # basic_tokenizer 先做基础清洗:去空格、标点归一化、中文字符保留 for sub_token in self.wordpiece_tokenizer.tokenize(token): # wordpiece_tokenizer 对每个 token 做子词切分 split_tokens.append(sub_token) return split_tokens

逻辑说明:convert_to_unicode()是防御性编程——确保输入是 str,避免bytes导致后续.split()失败。BasicTokenizer对中文的作用是:保留所有汉字、数字、字母,将标点(如“,”“。”)单独切分为 token,不合并。WordpieceTokenizer才是核心:它查vocab.txt,优先匹配最长子串。例如“非常好”在 vocab 中有['非常', '好'],则切分为['非常', '好'];若只有['非', '常', '好'],则切为['非', '常', '好']。vocab.txt里##好这种形式是 WordPiece 的子词标记(表示“好”作为后缀),KeyError: '##好'的本质是你的vocab.txt文件损坏或路径错误,导致load_vocab()返回空 dict。

3.2extract_features.py:特征提取不是可选步骤,是推理前置

extract_features.py的作用是:对原始文本,输出 BERT 的各层 hidden states,供下游任务(如相似度计算、特征聚类)使用。它与run_classifier.py的区别在于:不训练,只前向传播。

# extract_features.py def main(_): tokenizer = tokenization.FullTokenizer( vocab_file=FLAGS.vocab_file, do_lower_case=FLAGS.do_lower_case) examples = read_examples(FLAGS.input_file) # 读取 .txt,每行一个文本 features = convert_examples_to_features( examples=examples, tokenizer=tokenizer, max_seq_length=FLAGS.max_seq_length, doc_stride=FLAGS.doc_stride) model_fn = model_fn_builder( bert_config=modeling.BertConfig.from_json_file(FLAGS.bert_config_file), init_checkpoint=FLAGS.init_checkpoint, layer_indexes=[-1, -2, -3, -4], # 提取最后4层 use_tpu=False, use_one_hot_embeddings=False) # ... 构建 estimator 并 predict

参数说明:layer_indexes=[-1, -2, -3, -4]表示提取倒数四层的 hidden states(即第12、11、10、9层)。这是经验选择:底层捕获字形/语法,高层捕获语义,情感分类通常用高层特征。doc_stride用于长文本分段滑动(如一篇 500 字评论),但中文情感文本普遍 < 100 字,此处doc_stride可设为 0。convert_examples_to_features()会调用tokenizer.tokenize(),并生成input_ids、input_mask、segment_ids三元组——这正是run_classifier.py训练时喂给模型的数据格式。如果你的predict.py报Input mask has wrong shape,90% 是因为predict.py里max_seq_length与train.sh中不一致,导致 padding 长度不同。

3.3predict.py:如何让训练好的模型真正“说话”

predict.py是推理脚本,它加载./output/sentiment_model/下的 checkpoint,并对新文本做预测:

# predict.py def main(_): tokenizer = tokenization.FullTokenizer( vocab_file=FLAGS.vocab_file, do_lower_case=FLAGS.do_lower_case) # 1. 加载模型 model = modeling.BertForSequenceClassification.from_pretrained( FLAGS.init_checkpoint, num_labels=len(processor.get_labels())) # 2. 对输入文本预处理 text = "这个手机太卡了,完全不想再用了!" tokens = tokenizer.tokenize(text) # 截断 + 添加 [CLS] [SEP] if len(tokens) > FLAGS.max_seq_length - 2: tokens = tokens[0:(FLAGS.max_seq_length - 2)] tokens = ["[CLS]"] + tokens + ["[SEP]"] input_ids = tokenizer.convert_tokens_to_ids(tokens) input_mask = [1] * len(input_ids) segment_ids = [0] * len(input_ids) # 3. padding while len(input_ids) < FLAGS.max_seq_length: input_ids.append(0) input_mask.append(0) segment_ids.append(0) # 4. 转 tensor 并预测 input_ids = torch.tensor([input_ids], dtype=torch.long) input_mask = torch.tensor([input_mask], dtype=torch.long) segment_ids = torch.tensor([segment_ids], dtype=torch.long) with torch.no_grad(): logits = model(input_ids, segment_ids, input_mask) predictions = torch.nn.functional.softmax(logits, dim=-1) predicted_class = torch.argmax(predictions, dim=-1).item() print(f"Text: {text}") print(f"Predicted class: {predicted_class}, Prob: {predictions[0][predicted_class].item():.4f}")

关键点:tokenizer.convert_tokens_to_ids(tokens)将['[CLS]', '这', '个', '手', '机', '太', '卡', '了', ...]转为[101, 2769, 1581, 3221, 776, 1745, 1372, 1110, ...],ID 101 是[CLS]的固定 ID。input_mask全 1 表示有效 token,padding 位置为 0。segment_ids全 0 因为单句任务无需区分 A/B 句。这里max_seq_length必须与train.sh中完全一致,否则input_ids长度不匹配,model()调用直接报错。


4. 避坑:那些让你在凌晨三点对着 terminal 咬牙切齿的 4 个真实翻车现场

4.1 现象:tokenization.py报KeyError: '##好'

原因:vocab.txt文件缺失、路径错误,或你误用了英文 BERT 的 vocab(bert-base-uncased/vocab.txt)。中文 vocab 中##好是合法子词,英文 vocab 中不存在。
解决:确认--vocab_file参数指向chinese_L-12_H-768_A-12/vocab.txt;用head -n 5 chinese_L-12_H-768_A-12/vocab.txt查看前几行,应看到[PAD]、[UNK]、[CLS]、[SEP]、[MASK],然后是大量中文字符和##开头的子词。若看到the、and、of,说明你用了英文 vocab。

4.2 现象:run_classifier.py启动后立即 OOM(CUDA out of memory)

原因:--train_batch_size=32和--max_seq_length=128的组合在单张 11G V100 上必然爆显存。中文文本平均长度接近英文 1.5 倍,128实际占用显存是英文的 2 倍以上。
解决:按梯度下降法调整:先将--max_seq_length降至64,再试--train_batch_size=16;若仍 OOM,再降batch_size到8。记住:中文场景下max_seq_length=64是安全起点,128是奢侈配置。

4.3 现象:训练 loss 从 1.1 降到 0.01 后突然飙升到 2.5,验证 acc 波动剧烈

原因:train_sentiment.txt和dev.csv的 label 映射不一致。例如train_sentiment.txt用0=negative, 1=neutral, 2=positive,但dev.csv的label列是1,2,3或negative,positive,neutral,导致get_dev_examples()读入的 label 与get_labels()定义的顺序错位。
解决:用 pandas 直接检查数据:

import pandas as pd train_df = pd.read_csv('./data/dev.csv') print(train_df['label'].value_counts()) # 看实际分布 print(set(train_df['label'].astype(str))) # 看字符串集合

然后修改SentimentProcessor.get_labels()为["0", "1", "2"]或["negative", "neutral", "positive"],确保与数据文件中的字符串完全一致(包括大小写、空格)。

4.4 现象:predict.py输出Predicted class: 1, Prob: 0.9999,但输入是“垃圾产品,退货!”

原因:predict.py中do_lower_case参数与训练时不一致。中文虽无大小写,但do_lower_case=False是官方中文 BERT 的默认设置;若你在train.sh中没显式写--do_lower_case=False,而predict.py里 tokenizer 初始化用了do_lower_case=True,则 tokenizer 会把“退货”转为“退货”,但 vocab 中存的是“退货”,导致convert_tokens_to_ids()返回[0](UNK ID),模型输入全为 UNK,输出退化为先验分布(即多数类)。
解决:统一在所有脚本中显式声明do_lower_case=False。在predict.py的FullTokenizer初始化处加:

tokenizer = tokenization.FullTokenizer( vocab_file=FLAGS.vocab_file, do_lower_case=False) # 强制 False

5.predict.sh与requirements.txt:从单次预测到可交付服务的临门一脚

5.1predict.sh:把模型变成一行命令的生产力

predict.sh是predict.py的生产级封装,它解决了三个实际问题:输入标准化、输出结构化、错误兜底。

#!/bin/bash # predict.sh INPUT_FILE=${1:-"./data/test_input.txt"} # 第一个参数为输入文件,缺省为 test_input.txt OUTPUT_FILE=${2:-"./output/predictions.json"} # 第二个参数为输出文件 if [ ! -f "$INPUT_FILE" ]; then echo "Error: Input file $INPUT_FILE not found!" exit 1 fi # 1. 检查模型目录是否存在 if [ ! -d "./output/sentiment_model/" ]; then echo "Error: Model directory ./output/sentiment_model/ not found! Run train.sh first." exit 1 fi # 2. 执行预测,捕获 stderr 到 log python predict.py \ --input_file="$INPUT_FILE" \ --output_file="$OUTPUT_FILE" \ --vocab_file=./chinese_L-12_H-768_A-12/vocab.txt \ --bert_config_file=./chinese_L-12_H-768_A-12/bert_config.json \ --init_checkpoint=./output/sentiment_model/model.ckpt-1000 \ --max_seq_length=64 \ --do_lower_case=False \ 2> ./output/predict_error.log # 3. 检查预测是否成功 if [ $? -eq 0 ]; then echo "Prediction completed. Results saved to $OUTPUT_FILE" # 附加:统计各类别数量 python -c " import json; with open('$OUTPUT_FILE') as f: data = json.load(f); from collections import Counter; print('Class distribution:', Counter([x['label'] for x in data]))" else echo "Prediction failed. Check ./output/predict_error.log" exit 1 fi

价值点:INPUT_FILE=${1:-"./data/test_input.txt"}支持命令行传参,./predict.sh ./my_comments.txt ./results.json即可批量预测。2> ./output/predict_error.log将所有报错重定向到日志,避免 terminal 刷屏丢失关键信息。最后的python -c用一行命令统计预测结果分布,让你一眼看出模型是否严重偏向某类(如全预测为中性)。这已经不是实验脚本,而是可嵌入 CI/CD 流程的交付物。

5.2requirements.txt:版本锁死是复现的唯一护栏

requirements.txt内容看似平淡,却是跨环境复现的生命线:

numpy==1.19.5 torch==1.7.1 tensorflow==1.15.0 pandas==1.1.5 scikit-learn==0.24.1

为什么锁死?torch==1.7.1是关键:BERT PyTorch 实现大量使用torch.jit.trace和nn.MultiheadAttention的旧 API,torch>=1.8会因attn_mask参数变更导致modeling.py中 attention 计算报错。tensorflow==1.15.0是为了兼容run_squad.py(SQuAD 问答任务),该脚本依赖 TF 1.x 的 graph mode,TF2.x会直接 import 失败。pandas==1.1.5解决read_csv在dev.csv有空行时的解析 bug(新版 pandas 默认skip_blank_lines=True,旧版需显式指定)。不要pip install -r requirements.txt后就以为万事大吉——用pip list | grep torch确认版本,用python -c "import torch; print(torch.__version__)"双重校验。

5.3 从train.sh到 Dockerfile:一个可交付的最小服务镜像

既然train.sh和predict.sh已封装好,下一步就是容器化。一个极简但生产可用的Dockerfile:

FROM nvidia/cuda:11.0-cudnn8-runtime-ubuntu18.04 # 安装系统依赖 RUN apt-get update && apt-get install -y python3-pip python3-dev && \ rm -rf /var/lib/apt/lists/* # 复制项目 COPY . /app WORKDIR /app # 创建虚拟环境(避免污染系统 python) RUN python3 -m pip install --upgrade pip RUN python3 -m pip install virtualenv RUN python3 -m virtualenv venv RUN source venv/bin/activate && pip install -r requirements.txt # 复制预训练模型 RUN mkdir -p /app/chinese_L-12_H-768_A-12 # (此处需手动下载 google 官方中文 BERT 并解压到 /app/chinese_L-12_H-768_A-12/) # 暴露端口(为后续 API 化预留) EXPOSE 5000 # 启动脚本 COPY entrypoint.sh /app/entrypoint.sh RUN chmod +x /app/entrypoint.sh ENTRYPOINT ["/app/entrypoint.sh"]

entrypoint.sh内容:

#!/bin/bash # entrypoint.sh source /app/venv/bin/activate if [ "$1" = "train" ]; then bash train.sh elif [ "$1" = "predict" ]; then bash predict.sh ${2:-"./data/test_input.txt"} ${3:-"./output/predictions.json"} else echo "Usage: docker run <image> [train|predict] [input_file] [output_file]" exit 1 fi

交付价值:docker build -t bert-sentiment .后,docker run bert-sentiment train即启动训练;docker run -v $(pwd)/my_data:/app/data bert-sentiment predict /app/data/comments.txt /app/output/res.json即完成预测。所有环境、依赖、模型、代码全部打包进一个镜像,这才是真正的“可复现”。你不再需要向同事解释“你得装 CUDA 11.0,然后 pip install 特定版本 torch”,只需docker pull和docker run。


6. 验证:用dev.csv的混淆矩阵说话,而不是只信accuracy=0.89

6.1 为什么accuracy是危险的指标?

假设你的dev.csv有 1000 条样本,其中 700 条是中性(label=1),200 条负面(0),100 条正面(2)。一个永远预测“中性”的模型,accuracy=70%。而你的模型accuracy=89%,听起来不错——但如果它把所有正面都判为中性,所有负面都判为中性,accuracy依然可以很高。情感分析的核心挑战是识别“极端情绪”,不是区分中性。

6.2 用classification_report看清真相

在run_classifier.py的main()函数末尾,添加验证集预测结果的详细报告:

# 在 eval 阶段结束后,添加: from sklearn.metrics import classification_report, confusion_matrix import numpy as np # 假设 eval_results 是一个 list of (true_label, pred_label) true_labels = [x[0] for x in eval_results] pred_labels = [x[1] for x in eval_results] print("\n=== Detailed Evaluation Report ===") print(classification_report(true_labels, pred_labels, target_names=["negative", "neutral", "positive"], digits=4)) print("\n=== Confusion Matrix ===") cm = confusion_matrix(true_labels, pred_labels) print(cm)

输出解读:classification_report会给出每个类别的precision(查准率)、recall(查全率)、f1-score。重点关注negative和positive的recall:如果negative recall=0.32,意味着 100 个负面评论,模型只找出了 32 个,漏掉了 68 个——这对客服系统是灾难性的。confusion_matrix的对角线是正确预测数,非对角线是混淆数。如果cm[0][1](负面→中性)很大,说明模型把负面当成了中性,根源可能是训练数据中负面样本太少,或train_sentiment.txt里负面表达太隐晦(如“还行吧”被标为负面)。

6.3 人工抽检:5 个最不确定的预测样本

predict.py可以增强为输出每个样本的 top-3 预测概率:

# predict.py 中修改输出逻辑 probabilities = torch.nn.functional.softmax(logits, dim=-1)[0] top3_probs, top3_indices = torch.topk(probabilities, k=3) for i, (prob, idx) in enumerate(zip(top3_probs, top3_indices)): label_name = processor.get_labels()[idx.item()] print(f" Top-{i+1}: {label_name} ({prob.item():.4f})")

操作:运行./predict.sh ./data/dev_sample.txt,取dev_sample.txt中 5 个Top-1和Top-2概率接近(如0.48 vs 0.45)的样本,人工判断哪个更合理。你会发现:模型在“一般般”上纠结,是因为训练数据里这个词既标过中性也标过负面;在“绝了!”上犹豫,是因为vocab.txt里没有“绝了”这个子词,被切成了['绝', '了'],而['绝']在 vocab 中是生僻字,embedding 质量差。这些样本就是你下一轮数据清洗和增强的靶子。

从那以后我每次跑完run_classifier.py,都强制走一遍python -m sklearn.metrics classification_report ...,再抽 5 个低置信度样本人工过一遍。不是为了追求报表上的数字好看,而是为了确认模型真的“懂”了中文情绪的微妙——比如知道“还行”和“还行吧”的差别,“差”和“太差了”的强度差异。这些细节藏在train_sentiment.txt的每一行里,藏在tokenization.py的每一个##前缀里,也藏在你predict.sh输出的每一行 JSON 中。希望帮到你。

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

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/5 2:58:16

Java基于ECharts的疫情数据可视化系统:CSV清洗到图表联动

简介&#xff1a;一套基于Java技术栈的疫情数据可视化分析系统完整源码&#xff0c;面向需要学习Spring Boot、MyBatis、MySQL与ECharts整合开发的中高级开发者&#xff0c;也适合作为毕业设计或课程项目的参考实现。项目内置爬虫程序自动抓取COVID-19累计确诊、治愈、死亡等数…

作者头像 李华
网站建设 2026/10/5 2:58:15

YOLOv5山地自行车检测实战:bicycles4高质量VOC数据集

简介&#xff1a;本资源是面向机器视觉算法工程师与智能交通项目开发者的YOLOv5专用非机动车违规停放检测数据集子集&#xff0c;聚焦共享单车治理场景中的自行车识别任务。包内含766张高质量JPEG图像及对应749份PASCAL VOC格式XML标注文件&#xff0c;覆盖山地、公路、越野、通…

作者头像 李华
网站建设 2026/10/5 2:57:34

Java面试:分布式数据库与Spring Cloud网关实战拆解

互联网大厂Java面试&#xff1a;分布式数据库与Spring Cloud网关实践在准备Java面试的时候&#xff0c;多数人都会把精力花在算法题、JVM调优这些传统考点上&#xff0c;但真正经历过几轮大厂面试的同学会发现&#xff0c;面试官越来越喜欢把“分布式数据库”和“Spring Cloud网…

作者头像 李华
网站建设 2026/10/5 2:56:39

虚拟机搭建Linux环境全指南:从选型配置到开发排错

很多朋友一听到“搭建虚拟机环境Linux”&#xff0c;脑子里第一个动作就是去下载VMware、拉一个ISO镜像&#xff0c;然后一路下一步。等你真正装完开始用&#xff0c;才会发现里面的细节多到让人头疼&#xff1a;镜像选哪个版本、磁盘怎么分、网络配不通、宿主机蓝屏、虚拟机连…

作者头像 李华
网站建设 2026/10/5 2:56:00

深度学习驱动的DOA估计:从协方差矩阵到SubspaceNet实践指南

简介&#xff1a;一份基于Python的深度学习波达方向&#xff08;DOA&#xff09;估计入门代码包&#xff0c;面向信号处理与深度学习交叉领域的学习者&#xff0c;聚焦窄带信号场景下的方向估计问题。包内共6个文件&#xff0c;全部为Python脚本&#xff0c;涵盖数据准备、信号…

作者头像 李华