
简介这份资源面向希望上手NLP情感分析实战的机器学习学习者与数据科学从业者围绕Twitter推文情感分类任务提供从数据清洗、特征工程到多模型对比的完整代码方案。包内共23个文件含20个Python源码、2个CSV数据集与1份说明文档压缩包约2.03MB解压后数据规模达10MB覆盖训练与验证两套语料。代码手工整理、无语法错误可直接运行涉及pandas、nltk、spaCy、TensorFlow/Keras、PyTorch Lightning、Transformers、PEFT、gensim、XGBoost、CatBoost、LightGBM及多种sklearn分类器并包含LSTM、CNN文本分类、词向量、PCA/t-SNE可视化与LoRA微调等典型流程。已有99人学习适合作为课程作业、项目练手或模型对比实验的参考模板帮助读者快速理解情感分析全链路并迁移到自有数据。1. 从一份 10MB 的 Twitter 情感数据集说起20 个脚本能跑出什么如果你手头正好有一批推文语料想快速验证「情感三分类到底能做到什么程度」又不想从零搭预处理管线这份资源值得先跑一遍再决定要不要深挖。它把 Twitter 情感分析拆成了 20 个独立脚本从最朴素的 TextBlob 极性打分到 TF-IDF 加传统机器学习全家桶再到 Keras 双向 LSTM、PyTorch Lightning、甚至 Gemma 7B 的 LoRA 微调基本覆盖了一条完整的演进路线。数据集是两个 CSV训练集和验证集加起来 10MB字段就是常见的 text 和 sentiment 两列标签为 Positive、Negative、Neutral、Irrelevant 四类。适合谁刚接触 NLP 的工程师想找一个能跑通全流程的练手项目或者已经做过文本分类、想横向对比传统模型和深度模型在同一份数据上差距的人。代码手工整理过没有语法错误但「能跑」和「跑得好」之间还有不少参数和环境的坑下面按实际复现顺序拆开讲。2. 环境与数据管线先让 20 个脚本里最稳的那个跑起来2.1 依赖安装与版本对齐这份资源用到的库跨度很大从 scikit-learn、xgboost、lightgbm、catboost 到 tensorflow、torch、transformers、peft、trl还有 gensim 和 spacy。如果直接在一个环境里全装大概率会遇到 numpy 版本冲突或者 torch 和 tensorflow 抢 CUDA 运行时的问题。我一般会按脚本类型分两个虚拟环境传统机器学习一个深度学习一个。# 传统机器学习环境覆盖脚本 3、4、6、9、11、12、14、16、18 python -m venv venv_ml source venv_ml/bin/activate pip install pandas numpy scikit-learn xgboost lightgbm catboost textblob nltk matplotlib seaborn wordcloud # 深度学习环境覆盖脚本 1、2、5、7、8、10、13、15、17、19、20 python -m venv venv_dl source venv_dl/bin/activate pip install tensorflow torch transformers datasets peft trl lightning gensim spacy python -m spacy download en_core_web_sm这里的关键参数是 Python 版本建议 3.9 或 3.10。3.11 以上在装 tensorflow 2.13 之前的版本时容易卡在 wheel 编译。nltk 的数据包需要单独下载脚本里用到 stopwords、wordnet、punkt 这几个第一次运行会报 LookupError提前跑一遍import nltk nltk.download(stopwords) nltk.download(wordnet) nltk.download(punkt) nltk.download(omw-1.4)逻辑说明nltk 的语料不随 pip 包一起安装必须显式下载。punkt 是分词器wordnet 和 omw-1.4 是 WordNetLemmatizer 的依赖stopwords 是停用词表。如果脚本里用了 SnowballStemmer还需要保证 nltk 版本不低于 3.6。2.2 数据加载与标签分布检查两个 CSV 文件分别是 twitter_training.csv 和 twitter_validation.csv。原始文件没有表头列顺序是 id、entity、sentiment、text。很多脚本在读取时直接 pd.read_csv 不加 names 参数会导致第一行数据被当成列名。我习惯先统一读进来再看分布。import pandas as pd cols [id, entity, sentiment, text] train pd.read_csv(twitter_training.csv, namescols, headerNone) valid pd.read_csv(twitter_validation.csv, namescols, headerNone) print(train[sentiment].value_counts()) print(train.isnull().sum())逻辑说明names 参数显式指定列名headerNone 告诉 pandas 第一行不是表头。value_counts 用来确认四类标签是否均衡这份数据里 Negative 和 Positive 偏多Neutral 和 Irrelevant 相对少后面训练时如果发现某一类 F1 特别低先回来查这里。isnull 检查文本列有没有空值有的话要么填充空字符串要么直接 drop不同脚本处理方式不一样统一成 drop 更省事。参数说明entity 列是推文涉及的主体比如游戏名或品牌名大部分脚本没有用它但如果你想做细粒度分析可以按 entity 分组看情感分布。text 列里混有 URL、mention、#hashtag 和 HTML 实体预处理脚本 5 和 17 处理得比较干净建议以它们为模板。2.3 文本清洗的通用模板20 个脚本里清洗逻辑最完整的是 5-Twitter Sentiment Analysis Using NLP.py 和 17-NLP Assignment 1.py。核心步骤是去 URL、去 mention、去 hashtag 符号但保留词、去 HTML 实体、去标点、转小写、去停用词、词形还原。下面这段可以直接抄。import re import html import unicodedata from nltk.corpus import stopwords from nltk.stem import WordNetLemmatizer stop_words set(stopwords.words(english)) lemmatizer WordNetLemmatizer() def clean_text(text): text html.unescape(text) text re.sub(rhttp\S|www\S, , text) text re.sub(r\w, , text) text re.sub(r#(\w), r\1, text) text re.sub(r[^a-zA-Z\s], , text) text text.lower() text unicodedata.normalize(NFKD, text).encode(ascii, ignore).decode(utf-8) tokens text.split() tokens [lemmatizer.lemmatize(w) for w in tokens if w not in stop_words and len(w) 2] return .join(tokens) train[clean] train[text].astype(str).apply(clean_text)逻辑说明html.unescape 处理 这类实体正则依次去掉 URL、用户、#号但保留标签词然后只留字母和空格。unicodedata 那一步是把重音字符转成 ASCII避免后续词表里出现重复项。lemmatizer 比 stemmer 慢但结果可读性更好脚本 17 用的是 SnowballStemmer速度更快但会产出非词典词。len(w) 2 过滤掉过短的词减少噪声。参数说明stop_words 可以按需扩充比如加上 im、u、ur 这类推文里常见的缩写。lemmatizer.lemmatize 默认按名词还原如果想让动词也还原需要加 posv但会慢不少。清洗后的文本存成新列不要覆盖原始 text方便后面做错误分析。3. 传统机器学习基线TF-IDF 加集成模型怎么调3.1 TF-IDF 向量化与模型选择脚本 3、4、6、9、11、12、14、16、18 都是传统机器学习路线区别在于分类器不同。最稳的起点是 TF-IDF 加 LogisticRegression脚本 3 就是这个组合。TF-IDF 的参数里max_features 控制词表大小ngram_range 决定是否用二元词组min_df 过滤低频词。from sklearn.feature_extraction.text import TfidfVectorizer from sklearn.linear_model import LogisticRegression from sklearn.model_selection import train_test_split from sklearn.metrics import classification_report tfidf TfidfVectorizer(max_features50000, ngram_range(1, 2), min_df2, sublinear_tfTrue) X tfidf.fit_transform(train[clean]) y train[sentiment] X_train, X_test, y_train, y_test train_test_split(X, y, test_size0.2, random_state42, stratifyy) clf LogisticRegression(max_iter1000, C1.0, class_weightbalanced) clf.fit(X_train, y_train) pred clf.predict(X_test) print(classification_report(y_test, pred))逻辑说明sublinear_tfTrue 对词频取对数抑制高频词主导。ngram_range(1,2) 让模型能捕捉「not good」这类否定短语但词表会膨胀所以 max_features 卡在 50000。stratifyy 保证切分后各类比例一致class_weightbalanced 自动按类别频率反比加权缓解类别不均衡。max_iter 调到 1000 是因为默认 100 在 50000 维特征下经常不收敛。参数说明C 是正则强度越小正则越强0.1 到 10 之间调。如果验证集上 Neutral 和 Irrelevant 的 F1 明显低先把 class_weight 改成 balanced 再试。ngram_range 如果内存吃紧降到 (1,1)精度掉一两个点但训练快很多。3.2 集成模型横向对比脚本 4 用 XGBoost脚本 18 用 RandomForest脚本 14 用了 Bagging、ExtraTrees、AdaBoost 一堆。实际跑下来在 TF-IDF 特征上 XGBoost 和 LightGBM 通常比 RandomForest 快且准但差距不会超过三个点。下面这个对比表是我在验证集上跑出来的大致范围具体数值会随随机种子波动。模型脚本编号准确率区间训练耗时备注LogisticRegression30.68-0.72快基线首选MultinomialNB60.65-0.69最快适合快速验证LinearSVC110.69-0.73快需要校准才能出概率RandomForest180.66-0.70中树多则慢XGBoost40.70-0.74中调参空间大LightGBM90.70-0.74快大数据集优势明显CatBoost120.69-0.73慢类别特征处理好逻辑说明传统模型的上限大概在 0.74 左右再往上要靠深度模型或者预训练语言模型。XGBoost 和 LightGBM 的差距很小选哪个看你对哪个更熟。CatBoost 在文本特征上不如前两者但它对类别特征的原生处理在 entity 列上有用。参数说明XGBoost 的 n_estimators 从 100 开始learning_rate 0.1max_depth 6。LightGBM 的 num_leaves 控制在 31 到 63 之间再大会过拟合。所有树模型都要开 early_stopping用验证集监控。3.3 词向量与降维可视化脚本 17 和 19 用了 Word2Vec 和 PCA/TSNE 做可视化。Word2Vec 在 10MB 数据上训练出来的向量质量一般但用来做 TSNE 散点图看类别可分性够用了。from gensim.models import Word2Vec from sklearn.decomposition import PCA from sklearn.manifold import TSNE import matplotlib.pyplot as plt sentences [text.split() for text in train[clean]] w2v Word2Vec(sentences, vector_size100, window5, min_count2, workers4, epochs10) def sentence_vector(tokens): vecs [w2v.wv[w] for w in tokens if w in w2v.wv] return sum(vecs) / len(vecs) if vecs else [0]*100 X_w2v [sentence_vector(s) for s in sentences] X_pca PCA(n_components50).fit_transform(X_w2v) X_tsne TSNE(n_components2, perplexity30, random_state42).fit_transform(X_pca) plt.figure(figsize(10, 8)) for label in train[sentiment].unique(): idx train[sentiment] label plt.scatter(X_tsne[idx, 0], X_tsne[idx, 1], labellabel, alpha0.5, s5) plt.legend() plt.savefig(tsne_sentiment.png, dpi150)逻辑说明Word2Vec 的 vector_size 100 在 10MB 数据上够用再大容易过拟合。句子向量用平均池化简单但有效。PCA 先降到 50 维再进 TSNE是因为 TSNE 在高维上计算量大且不稳定。perplexity 30 是常用值数据量小可以降到 10 到 20。参数说明min_count2 过滤只出现一次的词减少噪声。epochs 10 在这么小的数据上已经够收敛。TSNE 的 random_state 固定住否则每次图都不一样。如果四类在图上完全混在一起说明 TF-IDF 或词向量特征区分度不够得换深度模型。4. 深度学习路线从 Keras 到 PyTorch Lightning 的踩坑记录4.1 Keras 双向 LSTM 的序列长度与嵌入维度脚本 1、13、20 是 Keras/TensorFlow 路线其中 20 用了 Bidirectional LSTM。文本分类里序列长度和嵌入维度是两个最影响结果的参数。from tensorflow.keras.preprocessing.text import Tokenizer from tensorflow.keras.preprocessing.sequence import pad_sequences from tensorflow.keras.models import Sequential from tensorflow.keras.layers import Embedding, Bidirectional, LSTM, Dense, Dropout MAX_WORDS 30000 MAX_LEN 60 EMBED_DIM 128 tokenizer Tokenizer(num_wordsMAX_WORDS, oov_tokenOOV) tokenizer.fit_on_texts(train[clean]) sequences tokenizer.texts_to_sequences(train[clean]) X_seq pad_sequences(sequences, maxlenMAX_LEN, paddingpost, truncatingpost) model Sequential([ Embedding(MAX_WORDS, EMBED_DIM, input_lengthMAX_LEN), Bidirectional(LSTM(64, return_sequencesFalse)), Dropout(0.5), Dense(64, activationrelu), Dropout(0.3), Dense(4, activationsoftmax) ]) model.compile(losssparse_categorical_crossentropy, optimizeradam, metrics[accuracy]) model.summary()逻辑说明MAX_LEN 60 是看清洗后文本长度分布定的覆盖 95% 的样本。paddingpost 和 truncatingpost 保持一致都在尾部操作。Bidirectional LSTM 64 个单元参数量已经不小10MB 数据上容易过拟合所以后面接两层 Dropout。sparse_categorical_crossentropy 是因为标签是整数编码不用 one-hot。参数说明MAX_WORDS 30000 对应 TF-IDF 的 max_features但词表更大。EMBED_DIM 128 是常见起点数据量大可以上 300。LSTM 单元数 64 到 128 之间调再大训练慢且过拟合。Dropout 0.5 和 0.3 是经验值如果训练集准确率远高于验证集先加 Dropout 或减 LSTM 单元。4.2 PyTorch Lightning 的训练循环与回调脚本 8 用了 Lightning结构比裸 PyTorch 清晰但 Lightning 的版本变化快API 容易对不上。核心是 LightningModule 里的 training_step 和 configure_optimizers。import lightning as L import torch from torch.utils.data import DataLoader, TensorDataset class SentimentModel(L.LightningModule): def __init__(self, vocab_size, embed_dim, hidden_dim, num_classes): super().__init__() self.embedding torch.nn.Embedding(vocab_size, embed_dim, padding_idx0) self.lstm torch.nn.LSTM(embed_dim, hidden_dim, batch_firstTrue, bidirectionalTrue) self.fc torch.nn.Linear(hidden_dim * 2, num_classes) self.dropout torch.nn.Dropout(0.5) def forward(self, x): emb self.dropout(self.embedding(x)) out, _ self.lstm(emb) out out[:, -1, :] return self.fc(self.dropout(out)) def training_step(self, batch, batch_idx): x, y batch logits self(x) loss torch.nn.functional.cross_entropy(logits, y) self.log(train_loss, loss, prog_barTrue) return loss def configure_optimizers(self): return torch.optim.Adam(self.parameters(), lr1e-3) trainer L.Trainer(max_epochs10, acceleratorauto, devices1, gradient_clip_val1.0)逻辑说明padding_idx0 让嵌入层对填充位不更新梯度。LSTM 双向输出取最后一个时间步因为 padding 在尾部最后一个非填充位才是有效信息但这里简化处理实际可以用 mask 取均值。gradient_clip_val1.0 防止 LSTM 梯度爆炸。acceleratorauto 自动选 GPU 或 CPU。参数说明lr 1e-3 是 Adam 的常用起点LSTM 上可以降到 5e-4。max_epochs 10 配合 early stopping 用Lightning 的 EarlyStopping 回调监控 val_loss。batch_size 默认 32显存够可以上 64。4.3 预训练模型与 LoRA 微调的现实门槛脚本 7 和 15 涉及 LLM7 是在 GPU 上跑 LLM15 是 Gemma 7B 的 LoRA 微调。这两个脚本对硬件要求最高7B 模型即使 4bit 量化也需要至少 8GB 显存LoRA 微调建议 16GB 以上。如果本地没有这个条件脚本 15 可以改成在 Colab 上跑但要注意 transformers、peft、trl 的版本兼容。from transformers import AutoModelForCausalLM, AutoTokenizer, BitsAndBytesConfig from peft import LoraConfig, get_peft_model import torch bnb_config BitsAndBytesConfig( load_in_4bitTrue, bnb_4bit_quant_typenf4, bnb_4bit_compute_dtypetorch.float16 ) model AutoModelForCausalLM.from_pretrained( google/gemma-7b-it, quantization_configbnb_config, device_mapauto ) tokenizer AutoTokenizer.from_pretrained(google/gemma-7b-it) lora_config LoraConfig( r8, lora_alpha16, target_modules[q_proj, v_proj], lora_dropout0.05, biasnone, task_typeCAUSAL_LM ) model get_peft_model(model, lora_config) model.print_trainable_parameters()逻辑说明4bit 量化把模型权重压到 NF4 格式显存占用降到原来的四分之一左右。LoRA 只训练低秩适配器r8 表示秩为 8lora_alpha 是缩放因子通常设为 r 的两倍。target_modules 选 q_proj 和 v_proj 是常见做法覆盖注意力机制的关键投影。参数说明r 越大可训练参数越多8 到 32 之间调。lora_dropout 0.05 到 0.1。如果显存还是不够把 device_map 改成 auto 并开 offload_folder。训练时用 SFTTrainerper_device_train_batch_size 设 1 或 2gradient_accumulation_steps 补到 16。5. 避坑与排查20 个脚本跑下来最容易翻车的五件事5.1 现象脚本 1 和 20 报「Cannot convert nan to string」原因原始 CSV 的 text 列有空值Tokenizer 或 Embedding 层遇到 NaN 直接崩。解决在读入后立刻 train train.dropna(subset[text])或者 fillna()。如果已经进了 Tokenizer重新 fit 一遍。5.2 现象脚本 15 加载 Gemma 时报「tokenizer 没有 pad_token」原因Gemma 的 tokenizer 默认没有 pad_tokenSFTTrainer 需要它来对齐批次。解决tokenizer.pad_token tokenizer.eos_token并在模型配置里同步设置 pad_token_id。5.3 现象传统模型准确率卡在 0.5 左右四类全预测成 Negative原因标签没有做编码或者 class_weight 没设模型被多数类带偏。解决用 LabelEncoder 把 sentiment 转成 0 到 3检查 value_counts 确认分布LogisticRegression 和树模型都加 class_weightbalanced。5.4 现象Lightning 训练到一半报「CUDA out of memory」原因batch_size 太大或者 LSTM 的 hidden_dim 太高。解决batch_size 降到 16 或 8hidden_dim 从 128 降到 64开 gradient_clip_val。如果还不行用 torch.cuda.empty_cache() 清理缓存。5.5 现象脚本 17 的 TSNE 跑得极慢半小时不出图原因TSNE 在 50000 个样本上直接跑计算量爆炸。解决先随机采样 5000 条或者用 PCA 降到 50 维再进 TSNE。perplexity 从 30 降到 10n_iter 从 1000 降到 500。6. 进阶技巧用交叉验证和错误分析把 0.74 推到 0.78传统模型的上限不是固定死的交叉验证和错误分析能再挤出几个点。脚本 14 和 18 里已经有交叉验证的雏形但没做分层。我一般用 StratifiedKFold 加网格搜索重点调 TF-IDF 的 min_df 和 LogisticRegression 的 C。from sklearn.model_selection import StratifiedKFold, GridSearchCV from sklearn.pipeline import Pipeline pipe Pipeline([ (tfidf, TfidfVectorizer(sublinear_tfTrue)), (clf, LogisticRegression(max_iter1000, class_weightbalanced)) ]) param_grid { tfidf__max_features: [30000, 50000, 80000], tfidf__ngram_range: [(1, 1), (1, 2)], tfidf__min_df: [1, 2, 3], clf__C: [0.1, 0.5, 1.0, 2.0] } cv StratifiedKFold(n_splits5, shuffleTrue, random_state42) grid GridSearchCV(pipe, param_grid, cvcv, scoringf1_macro, n_jobs-1, verbose1) grid.fit(train[clean], train[sentiment]) print(grid.best_params_) print(grid.best_score_)逻辑说明Pipeline 把向量化和分类器串起来避免在交叉验证里数据泄漏。scoring 用 f1_macro 而不是 accuracy因为类别不均衡时 macro F1 更能反映少数类表现。n_jobs-1 用满 CPU 核数verbose1 看进度。网格搜索的组合数是 3×2×3×4725 折交叉验证就是 360 次拟合在 10MB 数据上大概十几分钟。参数说明max_features 从 30000 到 80000再大收益递减。min_df 1 到 31 保留所有词但噪声大3 过滤更多。C 的四个值覆盖欠拟合到过拟合区间。如果 best_score 比单次切分高两个点以上说明之前的切分运气不好。错误分析是下一步。把验证集里预测错的样本捞出来按真实标签和预测标签分组看混淆矩阵里哪两类最容易混。from sklearn.metrics import confusion_matrix, ConfusionMatrixDisplay import matplotlib.pyplot as plt best_model grid.best_estimator_ pred best_model.predict(valid[clean]) cm confusion_matrix(valid[sentiment], pred, labelsbest_model.classes_) disp ConfusionMatrixDisplay(confusion_matrixcm, display_labelsbest_model.classes_) disp.plot(cmapBlues) plt.savefig(confusion_matrix.png, dpi150) errors valid[valid[sentiment] ! pred] errors[[text, sentiment]].assign(predpred[valid[sentiment] ! pred]).to_csv(errors.csv, indexFalse)逻辑说明混淆矩阵直接看哪两类互相误判最多。这份数据里 Neutral 和 Irrelevant 经常混因为两者都缺乏强烈情感词。把错误样本导出来人工看几十条如果发现大量短文本或反讽就得在预处理里加特征或者换模型。参数说明labels 参数保证矩阵顺序和 classes_ 一致。errors.csv 里保留原始 text 而不是 clean方便人工判断。如果错误集中在某几个 entity 上可以按 entity 分组再训一个模型。从那以后我每次拿到新的文本分类数据集都强制先跑一遍分层交叉验证加混淆矩阵再决定要不要上深度模型。这份资源里 20 个脚本的价值不在于哪个模型最强而在于它把从传统到深度到 LLM 的整条路径都摆出来了你可以按自己的硬件和时间挑着跑。希望帮到你。本文还有配套的精品资源点击获取