機器學習:四套模型源碼解析與實戰(zhàn))
簡介基于情感詞典和多種機器學習模型的情感極性分析設計源碼面向自然語言處理研究者與初中級開發(fā)者用于從文本中判別積極、消極或中性情緒可應用于輿情監(jiān)控、評論分析等場景。資源包共28個文件主要由5個Python源文件、12個txt詞典/語料、4個Excel評估結果、2個png效果圖及dict、gitignore等組成整體大小17.01MB目錄結構清晰便于按模塊研讀。目前已有365人學習。源碼實現(xiàn)k-NN、樸素貝葉斯、最大熵與SVM等經典算法覆蓋情感詞典構建、特征提取、模型訓練及評估全流程并附有酒店、外賣、電影等多領域實驗語料與運行結果。通過學習可掌握文本預處理、分類模型調參和結果可視化方法適合作為情感分析入門或課程設計的參考實現(xiàn)。1. 用詞典加傳統(tǒng)機器學習做情感極性分析這份 Python 源碼能直接跑通四套模型情感極性分析這些年被深度學習占了頭條但真正落到小規(guī)模數(shù)據集、可解釋性要求高的場景基于情感詞典加傳統(tǒng)機器學習模型依然是性價比最高的方案。這份源碼打包了 k-NN、樸素貝葉斯、最大熵、SVM 四套分類器配好了中文酒店、中文外賣、英文電影三個領域的語料和情感詞典解壓之后改改路徑就能跑出評估結果。它解決的核心問題是當你想在自定義數(shù)據集上做正負情感分類又不想從零寫分詞、特征選擇、模型訓練和評估這條鏈路時這份源碼把整條 pipeline 都補齊了。適合 NLP 入門者做課程設計、研究生做 baseline 對比也適合工程師快速驗證一個文本分類需求能不能用傳統(tǒng)方案滿足。2. 先看清資源結構詞典、語料、特征文件各管哪一段拿到壓縮包先別急著跑代碼把文件按用途分組是節(jié)省時間的第一步。這份源碼的文件命名非常直白基本把訓練集規(guī)模、特征數(shù)、模型類型都寫進了文件名里比如Bayes-hotel-train-2200-test-800-f-5000就是貝葉斯模型跑酒店語料2200 條訓練、800 條測試、5000 個特征。先搞清每個文件在 pipeline 里的位置后面調參才不至于抓瞎。2.1 詞典文件拆解八類詞典如何協(xié)同完成極性判斷情感詞典是這套源碼的地基。f_dict目錄下共 8 個 txt 詞典各司其職positive_dict.txt和negative_dict.txt存情感詞adverb_dict.txt存程度副詞denial_dict.txt存否定詞conjunction_dict.txt存轉折連詞phrase_dict.txt存短語級情感單元punctuation_dict.txt存情感標點user.dict用于補充自定義詞。為什么需要這么多類型因為單純匹配情感詞會漏掉大量語義信息。以「酒店不算太差」這句話為例基礎情感詞匹配會命中「差」這個負向詞但如果只看這一個詞判斷就是負向。加入否定詞后「不」會翻轉情感方向再加入程度副詞「太」會調整情感強度。三層疊加之后這句話的情感值會從負向被拉回中性偏正真實語義才能被還原。短語詞典處理的則是「性價比很高」「服務態(tài)度好」這類組合單元單字面拆分會丟失整體語義。# 情感得分計算的樸素實現(xiàn)展示詞典如何協(xié)同 import re def load_dict(path): words set() with open(path, r, encodingutf-8) as f: for line in f: word line.strip() if word: words.add(word) return words # 加載各詞典 pos_words load_dict(f_dict/positive_dict.txt) neg_words load_dict(f_dict/negative_dict.txt) adverb_words load_dict(f_dict/adverb_dict.txt) denial_words load_dict(f_dict/denial_dict.txt) # 程度副詞權重表實際項目中按詞典順序或詞頻統(tǒng)計賦值 adverb_weight {很: 1.5, 太: 1.8, 非常: 2.0, 有點: 0.7, 稍微: 0.5} def sentence_polarity(tokens): score 0.0 i 0 while i len(tokens): word tokens[i] if word in pos_words: score 1.0 elif word in neg_words: score - 1.0 if word in denial_words: # 翻轉到下一情感詞為止實際項目中需限定窗口 for j in range(i 1, min(i 3, len(tokens))): if tokens[j] in pos_words: score - 1.5 # 否定正向 負向 elif tokens[j] in neg_words: score 1.5 if word in adverb_words: for j in range(i 1, min(i 2, len(tokens))): if tokens[j] in pos_words or tokens[j] in neg_words: base 1.0 if tokens[j] in pos_words else -1.0 score (adverb_weight.get(word, 1.2) - 1.0) * base i 1 return score這段代碼展示了詞典打分的基本循環(huán)邏輯。adverb_weight字典是手工設定的經驗值實際項目中可以用互信息或詞頻統(tǒng)計來量化每個副詞的強度系數(shù)。否定詞的翻轉窗口我限制在 3 個詞之內這是處理口語文本時比較穩(wěn)妥的做法——「不是很滿意」和「不是特別滿意」的否定范圍都覆蓋到了但再遠的詞就不該被「不」影響。最后得分大于 0 判為正向小于 0 判為負向等于 0 則交給后續(xù)的機器學習模型兜底。單純詞典匹配的準確率通常只有 70% 上下主要敗在未登錄詞和反諷句式上。但它的價值在于能生成高質量的特征候選集——通過詞典初篩出的情感候選詞配合卡方檢驗能得到比單純詞頻統(tǒng)計更精準的特征維度。2.2 語料與中間文件酒店、外賣、電影三個領域的訓練測試分布f_corpus目錄下放著 5 個語料文件ch_hotel_corpus.txt中文酒店、ch_waimai_corpus.txt和ch_waimai2_corpus.txt中文外賣、en_movie_corpus.txt和en_movie2_corpus.txt英文電影。三個領域覆蓋了兩種語言、三種文本風格這是刻意為之的——酒店評論偏正式長句外賣評論偏口語短句電影評論中英混雜且情緒表達更夸張。從 Excel 文件名可以還原出各數(shù)據集的劃分方案數(shù)據集訓練集規(guī)模測試集規(guī)模特征數(shù)對應模型酒店中文22008005000Bayes電影英文7003005000Bayes外賣中文300010005000SVMC150線性核訓練測試比大約為 7:3 到 3:1這在文本分類里屬于常見配置。700 條訓練數(shù)據對機器學習模型來說偏少但對樸素貝葉斯而言反而夠用——它假設特征獨立小樣本下不太容易過擬合。外賣數(shù)據集最大3000 條訓練樣本配合 SVM 線性核實測效果通常優(yōu)于另外兩個領域因為外賣評論句式短、情感詞密集、噪聲少。chi1.png和chi2.png是卡方檢驗特征選擇的可視化結果evaluation.PNG是各模型評估指標對比圖maxent_iteration.PNG是最大熵模型的迭代收斂曲線。這幾張圖不是擺設后面評估模型效果時可以直接對照看。3. 特征提取與降維feature_extraction.py 怎么把文本變成特征矩陣文本分類繞不開的環(huán)節(jié)是把原始文本轉成模型能吃的數(shù)值特征。這份源碼的特征提取思路和主流做法一致中文分詞后用卡方檢驗CHI做特征選擇選出最具有區(qū)分度的前 N 個特征詞最終輸出 LIBSVM 格式的特征向量。這種方案在 2016 年前后是教科書級的標準流程現(xiàn)在看依然不過時。3.1 卡方檢驗選特征為什么選前 5000 個而不是全部卡方檢驗的核心思想是衡量某個詞和某個類別之間的相關性。如果一個詞在正向文檔中出現(xiàn)的頻率顯著高于在負向文檔中的頻率它對該分類任務的信息量就大。計算每個候選詞的卡方值后按從大到小排序取前 5000 個詞組成特征詞表。5000 這個數(shù)字不是拍腦袋定的特征太多會引入噪聲降低泛化能力太少又會丟失有效區(qū)分信息對萬級語料來說3000 到 8000 之間通常能取得較好的平衡源碼選擇 5000 屬于穩(wěn)妥的中位數(shù)。# 卡方特征選擇的核心計算 import math from collections import Counter def chi_square(word, pos_docs, neg_docs, total_pos, total_neg): 計算某個詞的卡方值 word: 候選特征詞 pos_docs: 包含該詞的正向文檔集合 neg_docs: 包含該詞的負向文檔集合 total_pos: 正向文檔總數(shù) total_neg: 負向文檔總數(shù) a len(pos_docs) # 正向且包含該詞 b len(neg_docs) # 負向且包含該詞 c total_pos - a # 正向且不包含該詞 d total_neg - b # 負向且不包含該詞 n total_pos total_neg # 卡方公式注意分母為0時的保護 denominator (a c) * (b d) * (a b) * (c d) if denominator 0: return 0.0 chi2 n * (a * d - b * c) ** 2 / denominator return chi2 def select_top_k_features(tokenized_docs, labels, k5000): 從分詞后的文檔中選出卡方值最高的k個詞 tokenized_docs: 嵌套列表 [[詞1, 詞2, ...], ...] labels: 對應的情感標簽列表1為正0為負 k: 期望保留的特征數(shù) pos_docs [set(doc) for doc, label in zip(tokenized_docs, labels) if label 1] neg_docs [set(doc) for doc, label in zip(tokenized_docs, labels) if label 0] total_pos len(pos_docs) total_neg len(neg_docs) # 統(tǒng)計每個詞出現(xiàn)在哪些文檔中 word_doc_map {} for doc in pos_docs: for word in doc: word_doc_map.setdefault(word, [[], []])[0].append(id(doc)) for doc in neg_docs: for word in doc: word_doc_map.setdefault(word, [[], []])[1].append(id(doc)) word_chi2 {} for word, (pos_doc_ids, neg_doc_ids) in word_doc_map.items(): # 這里把文檔ID集合轉換為包含該詞的文檔數(shù) score chi_square(word, pos_doc_ids, neg_doc_ids, total_pos, total_neg) word_chi2[word] score # 按卡方值降序取前k個 top_k sorted(word_chi2.items(), keylambda x: x[1], reverseTrue)[:k] return [word for word, _ in top_k]卡方計算的輸入數(shù)據結構需要事先準備好pos_docs和neg_docs是包含某個詞的所有文檔的集合不是詞頻計數(shù)。我給出的word_doc_map用文檔 ID 做標識避免同一個文檔重復計數(shù)。k值可以直接替換成代碼里的f-5000參數(shù)如果你想測試不同特征數(shù)對模型效果的影響改這個值重跑即可。需要留意的是chi_square函數(shù)的四個變量a、b、c、d中c和d的值來自文檔總數(shù)減去包含該詞的文檔數(shù)需要確保文檔 ID 的集合運算正確否則算出來的卡方值全部失真。特征選擇完成后每篇文檔就會被表示成一個 5000 維的向量維度上只有 0 和 1 兩種取值——1 表示該特征詞在這篇文檔中出現(xiàn)0 表示未出現(xiàn)。這種 one-hot 式表示法雖然有信息損失丟失了詞頻但在 SVM 和樸素貝葉斯這類模型上二值特征往往比原始詞頻特征更抗噪聲也是 LIBSVM 最常用的輸入格式。3.2 從原始文本到特征向量分詞、去停用詞、向量化的完整鏈路特征提取的完整鏈路如下原始語料 → 分詞 → 去停用詞 → 卡方特征選擇 → 特征向量化 → 輸出到 Excel 或 LIBSVM 格式文件。中文部分依賴結巴分詞英文部分按空格切分即可。停用詞表直接復用punctuation_dict.txt再加進去一批無實際情感含義的高頻詞比如「的」「了」「是」「在」。# 標準的中文文本清洗與向量化流程 import jieba import re def load_stopwords(pathf_dict/punctuation_dict.txt): stopwords set() with open(path, r, encodingutf-8) as f: for line in f: word line.strip() if word: stopwords.add(word) # 補充常見無意義高頻詞 extra_stops {的, 了, 是, 在, 就, 都, 也, 很, 還, 被} stopwords.update(extra_stops) return stopwords stopwords load_stopwords() def preprocess_text(raw_text): 原始文本清洗與分詞 返回: 去停用詞后的詞列表 # 去除URL、用戶、特殊符號 text re.sub(rhttps?://\S|www\.\S, , raw_text) text re.sub(r\w, , text) # 去除所有非中英文、數(shù)字、常見標點的字符 text re.sub(r[^\u4e00-\u9fa5a-zA-Z0-9\s。、], , text) # 結巴分詞 words jieba.lcut(text) # 去停用詞和單字 filtered [w for w in words if w.strip() and w not in stopwords and len(w) 1] return filtered def text_to_vector(tokens, feature_words): 把分詞結果轉成特征向量 tokens: preprocess_text 返回的詞列表 feature_words: select_top_k_features 選出的特征詞表 返回: 與 feature_words 等長的0/1列表 token_set set(tokens) return [1 if word in token_set else 0 for word in feature_words]preprocess_text里的正則表達式[^\u4e00-\u9fa5a-zA-Z0-9\s。、]會把表情符號、emoji 全部剔除。這里有一個權衡新浪微博等社交媒體數(shù)據中 emoji 本身攜帶情感信息全部剔除會損失信號。但這份源碼針對的是酒店、外賣、電影評論這三類文本中 emoji 占比很低剔除不會明顯影響效果。如果你想處理社交媒體評論可以考慮單獨把 emoji 映射成情感標簽而不是直接刪掉。text_to_vector返回的是 0/1 向量長度為特征詞表的大小。5000 個特征對應 5000 維向量每條樣本占用的內存大約幾十 KB3000 條訓練樣本全部加載也不到幾百 MB內存完全不是問題。真正耗時的環(huán)節(jié)是卡方檢驗時的詞-文檔映射構建如果語料超過 5 萬條建議用稀疏矩陣來存儲文檔-詞頻關系否則內存消耗會指數(shù)級增長。4. 四套分類器實現(xiàn)classifiers.py 與 tools.py 的核心邏輯源碼里的classifiers.py封裝了 k-NN、樸素貝葉斯、最大熵、SVM 四個分類器的實現(xiàn)tools.py負責數(shù)據處理和評估指標計算。四套模型在同一條特征提取鏈路下運行對比結果時才能公平反映模型本身的差異。每套模型都可以獨立運行控制臺會輸出準確率、召回率、F1 值等評估指標。4.1 樸素貝葉斯與最大熵從概率視角看文本分類樸素貝葉斯假設特征之間相互獨立計算每個類別下特征向量的條件概率然后取后驗概率最大的類別作為預測結果。實際代碼中為了避免概率為 0 的極端情況會做拉普拉斯平滑。最大熵模型則放棄了獨立性假設通過約束條件逐步迭代調整特征權重在梯度下降或迭代縮放算法的驅動下逼近最優(yōu)解。兩者在中小數(shù)據集上的準確率通常很接近但最大熵的收斂速度和特征維度的關系更密切。# 樸素貝葉斯與最大熵分類器的核心實現(xiàn) import math from collections import defaultdict class NaiveBayesClassifier: def __init__(self, alpha1.0): self.alpha alpha # 拉普拉斯平滑系數(shù) self.prior {} # 先驗概率 P(c) self.cond_prob {} # 條件概率 P(w|c) self.classes [] def fit(self, X, y, feature_words): X: 0/1特征矩陣list of list y: 標簽列表 feature_words: 特征詞表 self.classes list(set(y)) n_samples len(y) # 計算先驗概率 class_counts defaultdict(int) for label in y: class_counts[label] 1 for c in self.classes: self.prior[c] class_counts[c] / n_samples # 計算條件概率 P(word|c) # 先統(tǒng)計每個類別下每個詞出現(xiàn)的文檔數(shù) word_class_count defaultdict(lambda: defaultdict(int)) class_total_count defaultdict(int) for idx, label in enumerate(y): class_total_count[label] 1 for j, val in enumerate(X[idx]): if val 1: word_class_count[label][j] 1 # 應用拉普拉斯平滑計算條件概率 vocab_size len(feature_words) for c in self.classes: self.cond_prob[c] {} total class_total_count[c] for j in range(vocab_size): count word_class_count[c][j] self.cond_prob[c][j] (count self.alpha) / (total self.alpha * vocab_size) def predict(self, X): predictions [] for x in X: best_c None best_score -float(inf) for c in self.classes: # 用對數(shù)概率避免下溢 log_prob math.log(self.prior[c]) for j, val in enumerate(x): if val 1: log_prob math.log(self.cond_prob[c][j]) if log_prob best_score: best_score log_prob best_c c predictions.append(best_c) return predictions拉普拉斯平滑系數(shù)alpha默認取 1.0這是經驗值。alpha越大對未在訓練集中出現(xiàn)的特征的懲罰越強。在實際項目中如果特征維度很高超過 1 萬建議把alpha下調到 0.5 左右否則大量未出現(xiàn)的特征會把真實信號淹沒。預測階段使用對數(shù)概率累加而不是概率連乘是因為 5000 個 0/1 特征的連乘結果會極小直接浮點運算會下溢成 0對數(shù)變換是必做的一步。最大熵的實現(xiàn)在思路上完全不同它定義特征函數(shù)通常是「特征詞是否出現(xiàn)」的指示函數(shù)然后通過迭代縮放算法反復修正特征權重使得模型的經驗分布與真實分布接近。實際應用中最大熵的訓練時間通常是樸素貝葉斯的 5 到 10 倍配合maxent_iteration.PNG那張圖可以看到迭代到 100 輪左右時模型已經收斂再多迭代只是浪費時間。4.2 SVM 與 k-NN邊界劃分與距離度量的兩種哲學SVM 通過尋找最大間隔超平面來區(qū)分正負類泛化能力強是文本分類里的首選模型之一。SVM-waimai-train-3000-test-1000-f-5000-C-150-2016-06-02-23-46-11-lin.xls里的C-150表示懲罰系數(shù)取 150lin表示線性核。C 值控制對誤分類樣本的容忍度C 越大對訓練集擬合越充分但過大會導致過擬合C 越小模型越偏向簡單的超平面。150 這個值偏高說明作者更看重訓練集上的準確率。k-NN 則完全不同它不做任何訓練直接拿測試樣本與全部訓練樣本計算距離取距離最近的 K 個鄰居中占比最高的類別作為預測結果。距離度量方式一般采用歐氏距離或余弦相似度對于 0/1 特征向量余弦相似度通常比歐氏距離更合理——因為它不受向量長度影響。# SVM與k-NN的調用封裝 from sklearn.svm import SVC from sklearn.neighbors import KNeighborsClassifier from sklearn.metrics import classification_report def train_svm(X_train, y_train, C150, kernellinear): 訓練SVM分類器 C: 懲罰系數(shù) kernel: 核函數(shù)文本分類默認線性核 clf SVC(CC, kernelkernel, probabilityFalse, tol0.001) clf.fit(X_train, y_train) return clf def train_knn(X_train, y_train, k5, metriccosine): 訓練k-NN分類器 k: 鄰居數(shù) metric: 距離度量方式 clf KNeighborsClassifier(n_neighborsk, metricmetric) clf.fit(X_train, y_train) return clf # 訓練與評估示例 def run_evaluation(X_train, y_train, X_test, y_test, model_typesvm): if model_type svm: model train_svm(X_train, y_train, C150, kernellinear) elif model_type knn: model train_knn(X_train, y_train, k5, metriccosine) else: raise ValueError(Unsupported model type) y_pred model.predict(X_test) # 打印完整評估報告包含precision、recall、f1-score print(classification_report(y_test, y_pred, target_names[負向, 正向])) return y_predSVM 的tol0.001是收斂容差值越小訓練越充分但耗時越長。文本分類中 5000 維特征、3000 條訓練樣本的數(shù)據量線性 SVM 訓練時間通常在秒級。k-NN 的 K 值調參有個常見誤區(qū)——K 越大模型越平滑但過大的 K 會把邊界樣本也拉入決策導致準確率下降。從該源碼的場景看K 取 5 是不錯的默認值但通過交叉驗證往往會發(fā)現(xiàn) K3 或 K11 在這個數(shù)據集上效果更好這個只能實測。傳統(tǒng)機器學習的真實優(yōu)勢在特征維度遠小于樣本數(shù)量的場景下最能體現(xiàn)。5000 維特征對應 3000 條訓練樣本特征數(shù)大于樣本數(shù)此時 SVM 的泛化能力依賴正則化C 值需要調小一些才能避免過擬合。作者選了 C150 可能在自己的測試集上有過驗證你也可以在 50 到 200 之間做一輪網格搜索。5. 評估結果與避坑記錄evaluation.PNG 背后的數(shù)據怎么看evaluation.PNG是四套模型在三個數(shù)據集上的綜合評估對比圖maxent_iteration.PNG展示最大熵模型每輪迭代的準確率變化。評估階段最容易出的問題不在于模型代碼寫沒寫對而在于訓練測試集劃分是否合理、評估指標是否選對、特征生成過程是否泄漏了測試集信息。很多跑出來的結果虛高就是因為代碼里不經意間把測試集的信息混進了特征選擇環(huán)節(jié)。5.1 從文件名解析評估結果train/test 比例與特征數(shù)對準確率的影響四個 Excel 文件分別對應不同模型在不同數(shù)據集上的跑分結果文件名已經透露了關鍵信息Bayes-hotel-train-2200-test-800-f-5000樸素貝葉斯酒店語料2200 訓練 / 800 測試特征數(shù) 5000Bayes-movie2-train-700-test-300-f-5000樸素貝葉斯電影語料700 訓練 / 300 測試特征數(shù) 5000SVM-waimai-train-3000-test-1000-f-5000-C-150-linSVM 線性核外賣語料3000 訓練 / 1000 測試特征數(shù) 5000C150一眼能看出的規(guī)律是訓練數(shù)據越多的數(shù)據集模型準確率普遍更高。外賣數(shù)據集的 SVM 結果通常能做到 85% 以上酒店數(shù)據的貝葉斯結果在 80% 上下電影數(shù)據只有 700 條訓練樣本準確率會掉到 75% 左右。這不是模型不行而是數(shù)據量不足的必然結果。特征數(shù)從幾百逐步提升到 5000 的過程中準確率通常先快速上升后趨于平穩(wěn)。chi1.png和chi2.png兩張圖應該展示了不同特征數(shù)下的準確率變化曲線可以看到 2000 個特征以后曲線基本走平。這意味著在實際項目中如果你計算資源有限用 2000 到 3000 個特征可以在幾乎不損失準確率的情況下大幅縮短訓練時間。評估指標上不能只盯準確率。當正負樣本比例失衡時比如外賣評論中負向樣本只占 20%準確率 80% 可能只是因為模型把所有樣本都判成了正向。正確的做法是同時觀察 precision、recall、F1-score這三個指標在classification_report的輸出中都有。如果只關注準確率而在樣本不均衡的數(shù)據集上做評估結果毫無參考價值。5.2 避坑清單詞典匹配翻車的五個高頻原因以下踩坑記錄來自實際運行這類源碼時最常見的報錯和異常結果每一條都對應具體的現(xiàn)象和修復方式。踩坑一中文語料用英文分詞器特征詞表全是空現(xiàn)象跑完特征提取feature_words為空列表后續(xù)所有模型訓練直接報維度為 0。原因源碼里中英文語料共用了同一套預處理邏輯如果默認用空格分詞中文文本會被當成長字符串整段保留卡方檢驗后不可能選出合理特征。解決預處理入口處按語料語言分流中文走jieba.lcut英文走str.split()。一個可復用的判斷方式是統(tǒng)計文本中的字符類別占比中文字符占比超過 30% 就按中文流程處理。def smart_tokenize(text): chinese_chars len(re.findall(r[\u4e00-\u9fa5], text)) total_chars len(text.strip()) chinese_ratio chinese_chars / max(total_chars, 1) if chinese_ratio 0.3: return jieba.lcut(text) else: return text.lower().split()踩坑二否定詞翻轉整句把「不是不好」判成負向現(xiàn)象包含「不是不好」「不得不點贊」的句子被模型判為負向人工復核明顯錯誤。原因否定詞處理只做了簡單的全局翻轉遇到雙重否定時邏輯徹底顛倒。not或「不」后面不管隔多遠都把情感值翻轉一次變成了反向的疊加。解決否定詞翻轉限定窗口范圍一般取否定詞后 2 到 3 個詞同時遇到兩個否定詞時視為正向表達。源碼中denial_dict.txt的覆蓋范圍也要確認是否包含「無」「莫」「非」等變體。踩坑三訓練測試集劃分前做了歸一化測試集信息泄漏現(xiàn)象模型在測試集上的準確率高達 95%但換一批新數(shù)據掉到 70%明顯不合常理。原因特征縮放或卡方特征選擇時用了全量數(shù)據的統(tǒng)計信息再把這套參數(shù)應用到測試集上等于測試集的信息提前參與了訓練。解決先劃分訓練集和測試集特征選擇只基于訓練集做??ǚ綑z驗、TF-IDF 擬合、歸一化參數(shù)都只在訓練集上計算測試集直接用訓練集生成的參數(shù)做變換。這是數(shù)據泄漏的重災區(qū)也是初學最容易忽略的點。踩坑四conjunction_dict.txt沒有參與邏輯判斷現(xiàn)象詞典文件齊全但「雖然環(huán)境好但是服務差」這類轉折句被判為正向。原因轉折連詞的作用是改變句子的情感重心——「但是」之后的語義權重應大于之前。如果代碼里只加載了轉折詞詞典卻沒有實現(xiàn)權重調整邏輯這些詞典形同虛設。解決在情感得分計算中遇到轉折詞后對前半句得分打折扣后半句權重加倍。折扣系數(shù)推薦 0.3 到 0.5具體值靠驗證集調參。踩坑五Excel 結果文件打不開或中文亂碼現(xiàn)象生成的.xls文件用 Excel 打開后中文全部亂碼或者提示文件格式損壞。原因寫入 Excel 時用了錯誤的編碼或者xls與xlsx格式混用。老版本xlwt庫寫.xls時只支持有限字符集。解決寫入時明確指定編碼為 UTF-8優(yōu)先輸出.xlsx格式。Python 側用openpyxl或pandas.to_excel()更穩(wěn)妥。這個坑不影響模型本身但會浪費十幾分鐘排查時間。6. 進階調參與驗證從 Excel 結果反推四個模型的適用邊界源碼跑通只是第一步真正有價值的是搞清楚四套模型各自適合什么場景以及如何調整參數(shù)獲得更好的效果。你可以打開evaluation.PNG對照各模型的準確率和 F1 值結合下述原則選擇自己的主力模型。以這份源碼的數(shù)據分布來看樸素貝葉斯適合小樣本高維特征場景SVM 適合樣本量中等但特征區(qū)分度高的場景最大熵適合需要概率輸出的場景k-NN 則最依賴距離度量和 K 值選擇。特征數(shù)量的敏感性檢驗是個值得做的實驗把代碼中的f-5000依次改成f-1000、f-3000、f-8000分別跑一輪評估。你會看到 1000 個特征時準確率明顯下降3000 到 5000 區(qū)間最穩(wěn)定8000 之后可能不升反降——噪聲特征開始干擾模型。以代碼里的select_top_k_features函數(shù)為例把k參數(shù)暴露出來寫個循環(huán)自動做這個敏感性分析輸出一張「特征數(shù)-準確率」對照表這比只跑一次默認參數(shù)更能說明問題。K 值調優(yōu)是 k-NN 模型的必做項。代碼中的train_knn(X_train, y_train, k5, metriccosine)可以把k分別設成 3、5、7、11、15 跑一組對比配合交叉驗證觀察曲線。通常 K 太小時噪聲干擾大太大時類別邊界被模糊。余弦相似度在這個場景下通常優(yōu)于歐氏距離因為 0/1 特征向量的歐氏距離受向量稀疏程度影響明顯而余弦相似度只看方向。SVM 的 C 值調整策略也有套路先用默認值比如 1.0跑通記錄準確率再按對數(shù)刻度嘗試 C0.1、1、10、100、200找到準確率峰值所在區(qū)間后在相鄰值之間做細粒度搜索。線性核在文本分類中通常已經足夠RBF 核雖然理論上可以擬合更復雜的邊界但參數(shù)搜索成本翻倍在小數(shù)據集上還容易過擬合。# 交叉驗證選擇最優(yōu)C值 from sklearn.model_selection import cross_val_score import numpy as np def select_best_c(X, y, c_values): best_c None best_score 0.0 results {} for c in c_values: model SVC(Cc, kernellinear, tol0.001) scores cross_val_score(model, X, y, cv5, scoringaccuracy) mean_score np.mean(scores) results[c] mean_score print(fC{c:.1f}, 平均準確率{mean_score:.4f}) if mean_score best_score: best_score mean_score best_c c return best_c, results # 候選C值按對數(shù)均勻分布 best_c, results select_best_c(X_train, y_train, [0.1, 0.5, 1.0, 5.0, 10.0, 50.0, 100.0, 200.0]) print(f最優(yōu)C值: {best_c})交叉驗證的cv5表示把訓練集切成 5 份輪流用 4 份訓練 1 份驗證。這比單次劃分訓練測試集更可靠能減少劃分隨機性帶來的評估波動。需要注意的是交叉驗證只能在訓練集上做最終評估還是要用獨立的測試集。跑完上述調參之后記住一個原則這種詞典加傳統(tǒng)機器學習的方案準確率上限大約在 88% 左右想突破這個瓶頸需要引入詞向量或深度學習模型。但在樣本量小于 1 萬、需要快速落地、要求結果可解釋的場景下這套方案依然是第一選擇。我自己做文本分類項目時不管最終要不要上深度學習都會先跑一遍這份 pipeline 作為 baseline——用傳統(tǒng)方法拿到參考分數(shù)再決定后續(xù)要不要加復雜度。從那以后我每次做情感分析類項目都強制要求自己先復現(xiàn)這個流程確認數(shù)據質量沒問題再談模型升級這個習慣幫我避開過不少數(shù)據泄漏和特征工程的大坑。希望這份源碼的拆解筆記能幫你在情感分析這條路上少走一段彎路。本文還有配套的精品資源點擊獲取