論情感分析實(shí)戰(zhàn):從京東爬蟲到機(jī)器學(xué)習(xí)模型全流程解析)
簡(jiǎn)介圍繞基于機(jī)器學(xué)習(xí)的商品評(píng)論情感分析這份畢業(yè)設(shè)計(jì)壓縮包面向計(jì)算機(jī)專業(yè)學(xué)生可用于課程設(shè)計(jì)、畢設(shè)答辯或自然語言處理入門實(shí)戰(zhàn)。項(xiàng)目涵蓋評(píng)論爬取、數(shù)據(jù)清洗、分詞、情感詞典構(gòu)建、詞頻-逆文檔頻率與詞向量特征提取以及樸素貝葉斯、支持向量機(jī)、極端梯度提升、長(zhǎng)短期記憶網(wǎng)絡(luò)等多種模型的訓(xùn)練與評(píng)估并提供圖形界面和可視化腳本能展示完整的分析流程。資源共43個(gè)文件、約66.66MB以Python腳本、表格數(shù)據(jù)、模型文件及配置文檔為主包含14個(gè)Python程序、7個(gè)CSV數(shù)據(jù)文件、2個(gè)Excel表格和多個(gè)訓(xùn)練好的情感分類模型與詞向量模型適合直接運(yùn)行和二次修改。目前已有167人學(xué)習(xí)下載。總體而言這份壓縮包覆蓋從數(shù)據(jù)采集到報(bào)告撰寫的完整環(huán)節(jié)并補(bǔ)充了Git版本管理、超參數(shù)搜索等實(shí)踐細(xì)節(jié)是一份能支撐實(shí)際答辯和功能演示的畢業(yè)設(shè)計(jì)資料。1. 情感分析不是看“好評(píng)/差評(píng)”那么簡(jiǎn)單這個(gè)畢業(yè)設(shè)計(jì)到底在做什么你可能以為“商品評(píng)論情感分析”就是把五星好評(píng)算正面、一星差評(píng)算負(fù)面然后用機(jī)器學(xué)習(xí)算法跑一個(gè)準(zhǔn)確率就完事。但真正動(dòng)手抓過京東或淘寶評(píng)論的人都知道評(píng)論區(qū)里充斥著“默認(rèn)好評(píng)”的模板文本、和商品毫不相干的湊字評(píng)價(jià)、還有那種“物流很快但質(zhì)量稀爛”的混合情感。這個(gè)基于機(jī)器學(xué)習(xí)的商品評(píng)論情感分析項(xiàng)目核心不是模型有多深而是你能不能把“用戶到底對(duì)商品持什么態(tài)度”這件事從一堆噪聲里可靠地抽出來。它適合正在做機(jī)器學(xué)習(xí)課程設(shè)計(jì)或畢業(yè)設(shè)計(jì)的同學(xué)也適合想入門 NLP 分類任務(wù)的從業(yè)者。本文把從爬蟲、清洗、特征工程到模型調(diào)參與評(píng)估的完整鏈路拆開講給你一套能照著復(fù)現(xiàn)的落地方案。2. 定技術(shù)路線先想清楚數(shù)據(jù)從哪來、模型學(xué)到什么再談準(zhǔn)確率2.1 數(shù)據(jù)源選型為什么“爬京東評(píng)論”是主流選擇這個(gè)項(xiàng)目的數(shù)據(jù)來源常見做法是爬取京東商品評(píng)論。京東的評(píng)論區(qū)有一個(gè)半公開的 JSON 接口比淘寶的反爬策略溫和得多返回字段結(jié)構(gòu)化程度高自帶評(píng)分、追評(píng)時(shí)間、購買屬性等元信息非常適合做情感分析的初始數(shù)據(jù)集。而且京東評(píng)論區(qū)分“好評(píng)”“中評(píng)”“差評(píng)”三個(gè)標(biāo)簽頁方便你直接拿到標(biāo)注數(shù)據(jù)做有監(jiān)督學(xué)習(xí)。爬蟲的請(qǐng)求頭里必須帶上User-Agent和Referer否則京東會(huì)返回 403。接口核心參數(shù)是productId商品 ID、score0 全部 / 1 好評(píng) / 2 中評(píng) / 3 差評(píng)、page和pageSize。要注意的是京東這個(gè)接口單頁最多返回 10 條評(píng)論翻頁超過一定深度會(huì)被風(fēng)控?cái)r截所以做畢業(yè)設(shè)計(jì)的話多換幾個(gè)商品 ID 比死磕單商品翻頁更穩(wěn)妥。當(dāng)接口返回的commentsCount為 0 或code不為0時(shí)說明被限流了此時(shí)不要繼續(xù)請(qǐng)求退避 3 到 5 秒再試。代碼里要保留原始響應(yīng)文本方便排查編碼問題。抓下來的數(shù)據(jù)至少要有四個(gè)字段評(píng)論內(nèi)容、評(píng)分、點(diǎn)贊數(shù)、評(píng)論時(shí)間。評(píng)分是后文標(biāo)注情感標(biāo)簽的基礎(chǔ)而點(diǎn)贊數(shù)可以作為“這條評(píng)論是否具有代表性”的輔助特征。import requests import json import time def fetch_jd_comments(product_id, score0, page1, page_size10): url https://club.jd.com/comment/productPageComments.action params { productId: product_id, score: score, sortType: 5, # 按時(shí)間排序保證每次抓取順序穩(wěn)定 page: page, pageSize: page_size, # 固定10接口不接受更大的值 isShadowSku: 0, fold: 1, } headers { User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36, Referer: fhttps://item.jd.com/{product_id}.html, } resp requests.get(url, paramsparams, headersheaders, timeout15) data resp.json() if data.get(code) ! 0: print(fpage {page} 被限流任務(wù)中止) return [] rows [] for c in data.get(comments, []): rows.append({ content: c.get(content, ), score: c.get(score), recommend: c.get(recommend, ), comment_date: c.get(creationTime, ), product_id: product_id, }) return rows if __name__ __main__: rows fetch_jd_comments(100012043978, score0, page1) print(f抓取到 {len(rows)} 條評(píng)論示例內(nèi)容: {rows[0][content][:30]})參數(shù)里sortType5是按時(shí)間排序如果不固定排序方式翻頁時(shí)可能拿到重復(fù)評(píng)論影響后續(xù)去重統(tǒng)計(jì)。isShadowSku和fold這兩個(gè)參數(shù)保持默認(rèn)即可基本不需要改動(dòng)。建議把抓取結(jié)果實(shí)時(shí)落盤成 JSON 或 CSV避免中途斷網(wǎng)導(dǎo)致全部重跑。我一般會(huì)加一個(gè)斷點(diǎn)續(xù)爬邏輯用已抓取的文件行數(shù)作為下次抓取的起始頁但畢設(shè)規(guī)模的數(shù)據(jù)量通常不需要這么復(fù)雜手動(dòng)分段跑就夠了。2.2 分類粒度二分類、三分類還是五分類分類粒度決定了項(xiàng)目的工程復(fù)雜度和評(píng)估方式。最省事的是二分類把評(píng)分 4 到 5 星視為正面1 到 2 星視為負(fù)面3 星直接丟棄。這樣邊界干凈類別均衡也好控制。但答辯時(shí)容易被老師追問“中評(píng)為什么不要”所以如果你的數(shù)據(jù)量能支撐建議做三分類好評(píng) / 中評(píng) / 差評(píng)對(duì)應(yīng)評(píng)分 5 / 3 / 1把 4 星和 2 星作為模糊地帶并入相鄰類別。五分類看上去更細(xì)致但負(fù)面樣本通常不夠且 1 星和 2 星的文本差異極小——“一般般”和“很差”之間的邊界詞向量根本分不開模型容易在相鄰類別上亂跳。我做這個(gè)項(xiàng)目時(shí)選了四分類好評(píng)、差評(píng)、中評(píng)、默認(rèn)好評(píng)模板文本單獨(dú)歸一類。這樣既避免了模糊地帶的標(biāo)注噪聲也把京東特有的“此用戶未填寫評(píng)價(jià)內(nèi)容”這類垃圾樣本隔離出去不污染正文模型。標(biāo)注映射關(guān)系建議做成配置文件或一個(gè)清晰的函數(shù)不要散落在代碼里。后續(xù)如果要換分類粒度只改這一處映射即可別的地方全部復(fù)用。def score_to_label(score): if score in (5,): return positive elif score in (4, 3): return neutral elif score in (2, 1): return negative else: return unknown import pandas as pd df pd.read_csv(jd_comments.csv) df[label] df[score].apply(score_to_label) print(df[label].value_counts())這個(gè)映射有個(gè)細(xì)節(jié)京東評(píng)分是離散的 1 到 5但不同商品的評(píng)分分布差異很大有的商品 4 星偏多有的 3 星偏多。直接用評(píng)分映射會(huì)產(chǎn)生類別不均衡后面的處理章節(jié)會(huì)詳細(xì)講。在這個(gè)階段你要關(guān)心的是每個(gè)類別的樣本量是否能撐起訓(xùn)練我一般要求每個(gè)類別不少于 3000 條否則模型學(xué)不到可靠模式。2.3 技術(shù)棧與評(píng)估口徑先定標(biāo)準(zhǔn)再談?wù){(diào)參技術(shù)棧的選擇要匹配你的運(yùn)行環(huán)境。如果只有 CPU就不要一上來就上 BERT訓(xùn)練一個(gè) epoch 可能要幾小時(shí)答辯前很容易翻車。最穩(wěn)的組合是Python pandas jieba scikit-learn模型用樸素貝葉斯或邏輯回歸打底時(shí)間充足再試試 LSTM。BERT 或者transformers這類深度模型可以作為加分項(xiàng)寫在“后續(xù)工作”里不用強(qiáng)行跑通。評(píng)估口徑要提前想清楚。類別不均衡時(shí)準(zhǔn)確率是騙人的——如果負(fù)面樣本只占 5%全部預(yù)測(cè)成正面也有 95% 的準(zhǔn)確率。正確的評(píng)估指標(biāo)是宏平均 F1macro-F1和混淆矩陣。宏平均 F1 把每個(gè)類別的 F1 單獨(dú)算再取平均能真實(shí)反映小類別的表現(xiàn)。這個(gè)口徑從第一篇實(shí)驗(yàn)開始就固定下來中途不要換否則前后對(duì)比沒有意義。from sklearn.metrics import classification_report, f1_score y_true [...] # 真實(shí)標(biāo)簽 y_pred [...] # 模型預(yù)測(cè)標(biāo)簽 report classification_report(y_true, y_pred, target_names[negative, neutral, positive]) print(report) # 重點(diǎn)看 macro avg 那一行的 f1-score如果你用的是 scikit-learn 的接口classification_report會(huì)一次性給出每個(gè)類別的精確率、召回率、F1 和樣本數(shù)。我一般要求宏平均 F1 穩(wěn)定在 0.80 以上才算及格0.85 以上是良好0.90 以上需要比較干凈的語料才能達(dá)到。數(shù)據(jù)噪聲很高的情況下0.80 就是很好的結(jié)果了不要盲目追求高指標(biāo)。3. 把京東評(píng)論變成干凈的訓(xùn)練集清洗、去重與標(biāo)注修正3.1 清洗去掉模板文本、HTML 標(biāo)簽和超長(zhǎng)噪聲京東評(píng)論區(qū)有大量非自然語言的文本。最常見的三類一是“此商品太棒了我很喜歡”之類的系統(tǒng)默認(rèn)好評(píng)提示二是用戶粘貼的商品參數(shù)或活動(dòng)鏈接三是純表情或“哈哈哈哈”這類無信息量文本。清洗的目標(biāo)不是把文本變漂亮而是把模型不需要學(xué)的噪聲去掉否則模型會(huì)把“默認(rèn)好評(píng)”學(xué)成一個(gè)獨(dú)立類別而不是學(xué)到真實(shí)的情感表達(dá)。清洗順序很關(guān)鍵。先處理 HTML 實(shí)體和解碼問題再做長(zhǎng)度過濾最后做關(guān)鍵詞去噪。如果先做長(zhǎng)度過濾含大量標(biāo)簽但正文很短的文本會(huì)被誤刪。我一般用正則把[a-zA-Z];這類 HTML 實(shí)體替換成空格再把http[s]?://\S替換成空字符串。長(zhǎng)度過濾的閾值設(shè)在 2 到 100 字之間低于 2 字的沒有信息量高于 100 字的可能是復(fù)制粘貼的評(píng)測(cè)文章對(duì)情感分類沒有幫助。import re import pandas as pd html_pattern re.compile(r[a-zA-Z];) url_pattern re.compile(rhttps?://\S) def clean_text(text): if not isinstance(text, str): return text html_pattern.sub( , text) text url_pattern.sub( , text) text re.sub(r\s, , text).strip() return text df pd.read_csv(jd_comments.csv) df[clean] df[content].apply(clean_text) df[len] df[clean].apply(len) df df[df[len] 2]這一步要輸出一個(gè)清洗后可讀的 CSV并肉眼抽樣檢查 200 條左右。檢查的目的不是看清洗規(guī)則對(duì)不對(duì)而是看有沒有漏網(wǎng)的異常文本。如果發(fā)現(xiàn)某種噪聲頻繁出現(xiàn)就把它對(duì)應(yīng)的正則或規(guī)則補(bǔ)進(jìn)去。清洗是迭代的過程不要指望一次到位。常見的漏網(wǎng)之魚是“此用戶很懶什么都沒有留下”這類半模板文本它包含真實(shí)情感嗎不包含需要單獨(dú)過濾。3.2 去重同一個(gè)用戶在不同商品下的重復(fù)評(píng)論京東用戶會(huì)在多個(gè)商品下復(fù)制粘貼同一條評(píng)論尤其是參與返京豆活動(dòng)的用戶。這類重復(fù)評(píng)論如果不處理模型會(huì)嚴(yán)重過擬合到重復(fù)文本上導(dǎo)致驗(yàn)證集準(zhǔn)確率高但真實(shí)場(chǎng)景表現(xiàn)差。去重不能只做字符串完全匹配因?yàn)橛脩艨赡苤桓囊粋€(gè)標(biāo)點(diǎn)或加一個(gè)空格我一般用歸一化后的文本做去重去掉所有空格和標(biāo)點(diǎn)再比較是否相等。更狠一點(diǎn)的做法是算文本之間的編輯距離但畢設(shè)數(shù)據(jù)量在小萬級(jí)別時(shí)直接跑difflib會(huì)慢到懷疑人生。折中方案是先用文本哈希粗篩再對(duì)哈希相同的分組做兩兩編輯距離驗(yàn)證。我實(shí)踐下來歸一化完全匹配已經(jīng)能解決大部分問題編輯距離留給極端情況。import re def normalize_for_dedup(text): return re.sub(r[\s\W_], , text) df[norm] df[clean].apply(normalize_for_dedup) df df.drop_duplicates(subset[norm], keepfirst) print(f去重后剩余 {len(df)} 條評(píng)論)去重后會(huì)改變類別分布所以去重要在清洗之后、劃分訓(xùn)練測(cè)試集之前完成。順序錯(cuò)了的話測(cè)試集里可能混著訓(xùn)練集見過的重復(fù)文本評(píng)估結(jié)果虛高。我踩過這個(gè)坑第一次實(shí)驗(yàn)沒去重測(cè)試集準(zhǔn)確率 0.91去掉重復(fù)后直接掉到 0.83這才意識(shí)到測(cè)試集泄漏了。3.3 標(biāo)注修正評(píng)分不等于情感怎么處理“陰陽怪氣”評(píng)分與真實(shí)情感并不總是對(duì)齊。有人給 1 星但評(píng)論內(nèi)容是“東西還行就是快遞太慢了”也有人給 5 星但寫的是“幫朋友買的不知道質(zhì)量如何”。如果完全信任評(píng)分做標(biāo)簽?zāi)P蜁?huì)學(xué)到錯(cuò)誤模式。但手工重標(biāo)上萬條不現(xiàn)實(shí)我一般用規(guī)則加抽檢來修正先標(biāo)記出“評(píng)分與文本情感沖突”的候選樣本再抽 10% 人工確認(rèn)。候選標(biāo)記的規(guī)則很簡(jiǎn)單文本里出現(xiàn)明確的矛盾信號(hào)詞比如“快遞慢”“質(zhì)量差”“客服不理人”的同時(shí)評(píng)分大于等于 4或者出現(xiàn)“好評(píng)”但評(píng)分小于等于 2。這類樣本通常只占總量 5% 左右人工處理成本可控。也可以在標(biāo)注修正后直接丟棄這些沖突樣本因?yàn)樗鼈儗儆跇?biāo)注噪聲保留會(huì)讓模型的決策邊界更模糊。conflict_keywords [快遞慢, 質(zhì)量差, 客服, 退貨, 垃圾, 差評(píng)] def flag_conflict(row): text row[clean] if any(k in text for k in conflict_keywords) and row[score] 4: return True return False df[conflict] df.apply(flag_conflict, axis1) print(f發(fā)現(xiàn)疑似沖突樣本 {df[conflict].sum()} 條) df_core df[~df[conflict]].copy()這一步的意義在于你辛辛苦苦用規(guī)則修正的每一個(gè)標(biāo)簽都在告訴模型“評(píng)分只是弱信號(hào)文本才是強(qiáng)信號(hào)”。我見過很多情感分析項(xiàng)目分?jǐn)?shù)很高但把測(cè)試集里的沖突樣本挑出來看模型幾乎全錯(cuò)。核驗(yàn)方式很簡(jiǎn)單訓(xùn)練完成后單獨(dú)抽出沖突樣本子集看分類準(zhǔn)確率如果低于 0.5說明模型過度依賴評(píng)分風(fēng)格而非文本語義需要加強(qiáng)清洗或調(diào)整特征。4. 讓中文變成機(jī)器能讀的數(shù)字分詞、停用詞與向量化4.1 分詞為什么不直接用 jieba 默認(rèn)詞典中文文本沒有天然空格分詞是特征工程的起點(diǎn)。jieba 是最常見的工具自帶詞典覆蓋多數(shù)通用詞匯但在商品評(píng)論領(lǐng)域會(huì)出現(xiàn)“入股不虧”“避雷”“YYDS”這類網(wǎng)絡(luò)新詞默認(rèn)詞典分不出來就會(huì)被切碎成單字丟失語義信息。解決方法是先對(duì)語料做一次詞頻統(tǒng)計(jì)找出被切碎的高頻詞手動(dòng)加入自定義詞典。自定義詞典的格式很簡(jiǎn)單每行一個(gè)詞可以帶詞頻和詞性。詞頻不要拍腦袋我一般按語料規(guī)模估算如果總詞數(shù)是 50 萬一個(gè)出現(xiàn) 200 次以上的詞詞頻給 5 就夠。詞頻給太高會(huì)影響 jieba 對(duì)其他詞的分詞判斷反而引入噪聲。分詞之后做一次驗(yàn)證打印 50 條隨機(jī)文本的分詞結(jié)果看看品牌名、商品名、情感詞是否正確切開。import jieba # user_dict.txt 每行: 詞 詞頻 詞性 # YYDS 500 nz # 避雷 300 v jieba.load_userdict(user_dict.txt) def tokenize(text): return [w for w in jieba.cut(text) if w.strip()] df_core[tokens] df_core[clean].apply(tokenize) print(df_core[tokens].head(10).tolist())分詞結(jié)果要保存下來后續(xù) TF-IDF 和 Word2Vec 都要復(fù)用不要每次訓(xùn)練都重新分詞。分詞參數(shù)里唯一需要調(diào)的其實(shí)是詞典jieba.cut的HMM參數(shù)默認(rèn)開對(duì)未登錄詞有補(bǔ)充識(shí)別效果但也會(huì)把一些英文混著中文的文本切亂。如果語料里英文占比較高可以把HMMFalse試一試對(duì)比一下前后效果再?zèng)Q定。4.2 TF-IDF向量化三個(gè)必調(diào)參數(shù)TF-IDF 是把分詞結(jié)果轉(zhuǎn)成向量最直接的方法。sklearn 的TfidfVectorizer有三個(gè)參數(shù)對(duì)最終效果影響最大max_features、min_df和ngram_range。max_features控制特征數(shù)量商品評(píng)論文本量級(jí)在 1 萬條左右時(shí)5000 到 10000 個(gè)特征就夠用設(shè)太大內(nèi)存容易爆設(shè)太小信息量不夠。min_df是文檔頻率下限默認(rèn) 1 會(huì)把只出現(xiàn)一次的生僻詞也納入特征我一般設(shè) 5讓至少出現(xiàn)在 5 條評(píng)論里的詞才進(jìn)入詞表。ngram_range是容易被忽略的點(diǎn)。二元詞組(1, 2)能捕捉“質(zhì)量好”“物流慢”這類組合情感比單個(gè)詞更穩(wěn)定但特征維度會(huì)翻幾倍。我建議先跑(1, 1)做基線再試(1, 2)對(duì)比 F1 有沒有提升。如果提升不到 1 個(gè)百分點(diǎn)就留在(1, 1)因?yàn)樘卣骶S度過高會(huì)導(dǎo)致訓(xùn)練時(shí)間變長(zhǎng)邏輯回歸還好換成其他模型就吃力了。from sklearn.feature_extraction.text import TfidfVectorizer df_core[text] df_core[tokens].apply(lambda x: .join(x)) vectorizer TfidfVectorizer( max_features8000, min_df5, ngram_range(1, 2), sublinear_tfTrue, # 用 1log(tf) 代替原始詞頻緩解高頻詞主導(dǎo) ) X vectorizer.fit_transform(df_core[text]) y df_core[label] print(f特征矩陣形狀: {X.shape})sublinear_tf是我覺得最值得開的一個(gè)參數(shù)。京東評(píng)論區(qū)“的”“了”“就”這類虛詞即使加了停用詞表也未必清干凈開了sublinear_tf之后詞頻差異被壓縮模型不再被高頻無義詞帶偏。特征矩陣是稀疏矩陣保存時(shí)直接用scipy.sparse.save_npz存成.npz文件下次加載不重復(fù)計(jì)算能省不少時(shí)間。4.3 詞向量與序列模型Word2Vec 到底該自己訓(xùn)練還是下載現(xiàn)成的如果你想嘗試 LSTM 這類序列模型就得把文本轉(zhuǎn)成詞向量序列。有兩個(gè)選擇用現(xiàn)成的中文詞向量或者用 gensim 在自己的語料上訓(xùn)練 Word2Vec。畢設(shè)層面我更推薦自己在語料上訓(xùn)練因?yàn)橹形耐ㄓ迷~向量是在新聞、百科等正式語料上訓(xùn)練的對(duì)“便宜大碗”“踩雷”這類電商口語詞覆蓋很差。自訓(xùn)練只需設(shè)置維度、窗口、最小詞頻三個(gè)參數(shù)。維度通常設(shè) 100 到 200太小表達(dá)不了語義差異太大在小語料上容易過擬合。窗口設(shè) 5 是默認(rèn)值處理短文本評(píng)論夠用。min_count設(shè) 5出現(xiàn)次數(shù)少于 5 的詞直接丟棄否則向量質(zhì)量會(huì)被稀有詞的噪聲拖垮。訓(xùn)練完成后一定要做一次相似詞測(cè)試比如查“質(zhì)量”的 top 10 相似詞如果出來一堆不相關(guān)的詞說明語料量不夠或參數(shù)不合適。from gensim.models import Word2Vec from gensim.models.word2vec import LineSentence # 把分詞結(jié)果寫入文件每行一條評(píng)論詞與詞用空格分隔 with open(corpus.txt, w, encodingutf-8) as f: for tokens in df_core[tokens]: f.write( .join(tokens) \n) model Word2Vec( sentencesLineSentence(corpus.txt), vector_size150, window5, min_count5, workers4, epochs10, ) print(model.wv.most_similar(質(zhì)量, topn10))epochs10在短文本語料上比默認(rèn)的 5 效果更好因?yàn)槊織l評(píng)論太短單次遍歷學(xué)不到足夠的共現(xiàn)信息。這里要注意一個(gè)常見翻車點(diǎn)Word2Vec在 gensim 4.0 之后把size參數(shù)改名成了vector_size網(wǎng)上老教程抄過來會(huì)直接報(bào) TypeError。詞向量訓(xùn)練好后要保存為word2vec.model和word2vec.kv兩個(gè)文件模型給后續(xù)微調(diào)用kv 給快速加載用。5. 模型評(píng)估與避坑清單準(zhǔn)確率虛高、樣本不均衡和過擬合的血淚記錄5.1 基線模型對(duì)比樸素貝葉斯、邏輯回歸、LSTM 的取舍不要一上來就訓(xùn)深度學(xué)習(xí)模型。我一般先跑兩個(gè)傳統(tǒng)機(jī)器學(xué)習(xí)基線樸素貝葉斯和邏輯回歸。樸素貝葉斯在短文本分類上很強(qiáng)計(jì)算快適合做下限參考邏輯回歸在 TF-IDF 特征上表現(xiàn)穩(wěn)定且系數(shù)可以直接當(dāng)特征重要性看答辯時(shí)解釋起來很有說服力。如果這兩個(gè)模型的宏平均 F1 已經(jīng)到 0.82那 LSTM 至少要超過 0.84 才有繼續(xù)調(diào)的必要不然直接交付傳統(tǒng)方案更劃算。from sklearn.naive_bayes import MultinomialNB from sklearn.linear_model import LogisticRegression from sklearn.model_selection import train_test_split from sklearn.metrics import f1_score X_train, X_test, y_train, y_test train_test_split( X, y, test_size0.2, random_state42, stratifyy ) nb MultinomialNB(alpha1.0) nb.fit(X_train, y_train) y_pred_nb nb.predict(X_test) print(fMultinomialNB macro-F1: {f1_score(y_test, y_pred_nb, averagemacro):.4f}) lr LogisticRegression(max_iter1000, C1.0, class_weightbalanced) lr.fit(X_train, y_train) y_pred_lr lr.predict(X_test) print(fLogisticRegression macro-F1: {f1_score(y_test, y_pred_lr, averagemacro):.4f})class_weightbalanced是處理類別不均衡最省事的辦法它會(huì)按類別頻率反向加權(quán)讓少數(shù)類別在損失函數(shù)里的占比提升。注意邏輯回歸的max_iter默認(rèn) 100TF-IDF 特征維度高時(shí)經(jīng)常不收斂要調(diào)到 1000 以上。至于 LSTM代碼量比前兩個(gè)大得多我放到下一節(jié)講。你還要記錄每個(gè)模型的訓(xùn)練時(shí)間答辯時(shí)這張“傳統(tǒng) vs 深度學(xué)習(xí)”的對(duì)比表非常加分。5.2 避坑清單五條必看的踩坑記錄5.2.1 準(zhǔn)確率 0.93 但模型實(shí)際是廢的現(xiàn)象測(cè)試集準(zhǔn)確率看著很高隨手抽 20 條真實(shí)評(píng)論預(yù)測(cè)錯(cuò)了一半。 原因類別不均衡。負(fù)面樣本只占 6%模型全預(yù)測(cè)成正面也有 94% 的準(zhǔn)確率。 解決換成宏平均 F1 作為主指標(biāo)并在訓(xùn)練時(shí)使用class_weightbalanced或過采樣/欠采樣。我常用imbalanced-learn庫的RandomOverSampler但要注意過采樣會(huì)讓驗(yàn)證集評(píng)估偏樂觀最好在過采樣之前劃分?jǐn)?shù)據(jù)。5.2.2 測(cè)試集泄漏去重不徹底導(dǎo)致指標(biāo)虛高現(xiàn)象模型在測(cè)試集上 F1 高達(dá) 0.92部署到新數(shù)據(jù)上直接崩到 0.75。 原因清洗后忘記去重同一條評(píng)論同時(shí)出現(xiàn)在訓(xùn)練集和測(cè)試集。 解決去重必須先于數(shù)據(jù)劃分。更嚴(yán)格的做法是校驗(yàn)用戶 ID確保同一個(gè)用戶的評(píng)論不會(huì)跨集合出現(xiàn)因?yàn)橥粋€(gè)人的用語風(fēng)格會(huì)讓模型學(xué)到“人”而不是“情感”。5.2.3 jieba 分詞把“不”和“好”切開導(dǎo)致情感反轉(zhuǎn)識(shí)別失敗現(xiàn)象模型對(duì)“不好”“不行”“不喜歡”全部預(yù)測(cè)錯(cuò)誤。 原因默認(rèn)詞典沒有把“不好”這類組合詞切成一個(gè)整體TF-IDF 把“不”和“好”當(dāng)成獨(dú)立特征語義被割裂。 解決建立否定詞典把“不好”“不行”“不滿意”等加入自定義詞典或者在特征基礎(chǔ)上疊加“否定詞 相鄰情感詞”的組合特征。我實(shí)測(cè)這個(gè)操作能讓負(fù)面類別 F1 提升 3 到 5 個(gè)百分點(diǎn)。5.2.4 Word2Vec 訓(xùn)練后相似詞一片混亂現(xiàn)象most_similar(質(zhì)量)返回的全是數(shù)字、標(biāo)點(diǎn)這類無意義 token。 原因語料太小或者min_count太低把噪聲詞也納入了詞表。 解決把min_count從 5 提到 10同時(shí)檢查清洗環(huán)節(jié)是否把數(shù)字和標(biāo)點(diǎn)全清干凈了。如果語料只有幾千條不要強(qiáng)行用 Word2Vec換 TF-IDF 更穩(wěn)。5.2.5 模型對(duì)“快遞”相關(guān)評(píng)論集體誤判為差評(píng)現(xiàn)象包含“快遞”的評(píng)論大量被預(yù)測(cè)為負(fù)面但實(shí)際上很多是好評(píng)。 原因語料里“快遞慢”“快遞垃圾”等負(fù)面表達(dá)占比過高模型學(xué)到“快遞”這個(gè)詞與負(fù)面的弱相關(guān)性。 解決訓(xùn)練前做一次特征分析把高頻但與情感無關(guān)的強(qiáng)特征降權(quán)或者直接用邏輯回歸系數(shù)定位這類問題。真正的解法是擴(kuò)充正面語料中“快遞”出現(xiàn)的比例讓模型看到“快遞很快”也能是正面的。5.3 混淆矩陣與錯(cuò)誤分析指標(biāo)準(zhǔn)確不代表業(yè)務(wù)可靠混淆矩陣能告訴你模型具體在哪些類別之間混淆。對(duì)于三分類情感分析最常見的現(xiàn)象是“中評(píng)”與“好評(píng)”之間界限模糊因?yàn)榫〇|的 3 星用戶常寫“還行吧”情感本身就不強(qiáng)烈。如果中評(píng)類別被大量分到好評(píng)你可以選擇調(diào)整中評(píng)的判定閾值也可以在訓(xùn)練時(shí)給中評(píng)樣本更高的權(quán)重。from sklearn.metrics import confusion_matrix, ConfusionMatrixDisplay import matplotlib.pyplot as plt cm confusion_matrix(y_test, y_pred_lr, labels[negative, neutral, positive]) disp ConfusionMatrixDisplay(confusion_matrixcm, display_labels[negative, neutral, positive]) disp.plot(cmapBlues) plt.savefig(confusion_matrix.png, dpi150, bbox_inchestight)畫混淆矩陣是給答辯看的但更重要的是從矩陣讀出下一步行動(dòng)。如果中評(píng)和好評(píng)的混淆數(shù)占了總誤差的 60%說明分類粒度本身可能不合理與其調(diào)參不如回到 2.2 節(jié)改分類定義。模型評(píng)估做到這個(gè)深度在一眾只看準(zhǔn)確率的畢設(shè)里已經(jīng)能拉開差距了。6. 把模型從“能跑”變成“能答辯”驗(yàn)證細(xì)節(jié)、可解釋性與交付技巧6.1 交叉驗(yàn)證別讓隨機(jī)種子決定你的成績(jī)單次劃分訓(xùn)練測(cè)試集結(jié)果受隨機(jī)種子影響很大。我做實(shí)驗(yàn)時(shí)固定用 5 折交叉驗(yàn)證取宏平均 F1 的均值和標(biāo)準(zhǔn)差。標(biāo)準(zhǔn)差能反映模型的穩(wěn)定性如果五折之間 F1 波動(dòng)超過 0.03說明數(shù)據(jù)分布不均勻或模型過擬合了某一類樣本。交叉驗(yàn)證的結(jié)果寫進(jìn)論文里比單次結(jié)果有說服力得多。from sklearn.model_selection import cross_val_score scores cross_val_score(lr, X, y, cv5, scoringf1_macro) print(f5-fold macro-F1: {scores.mean():.4f} ± {scores.std():.4f})6.2 可解釋性用邏輯回歸系數(shù)講清楚“模型為什么這么判”畢業(yè)設(shè)計(jì)答辯時(shí)老師最常問的問題是“你的模型依據(jù)什么做出判斷”。用深度學(xué)習(xí)模型很難回答但邏輯回歸的系數(shù)可以直接對(duì)應(yīng)到詞特征。把系數(shù)絕對(duì)值 top 20 的詞打印出來正系數(shù)是正面詞負(fù)系數(shù)是負(fù)面詞這就是一個(gè)樸素但直觀的解釋。如果要展示單條評(píng)論的預(yù)測(cè)理由可以用 LIME 庫生成局部解釋我在項(xiàng)目里對(duì)每類隨機(jī)抽 5 條評(píng)論做了解釋放在論文附錄里。6.3 交付物清單與驗(yàn)收標(biāo)準(zhǔn)交付時(shí)不要只給一個(gè)訓(xùn)練好的模型文件要包含完整的四件套清洗與爬蟲腳本、特征與訓(xùn)練代碼、模型文件、評(píng)估報(bào)告。評(píng)估報(bào)告里至少有三張圖類別分布圖、混淆矩陣、宏平均 F1 的折線對(duì)比圖。驗(yàn)收標(biāo)準(zhǔn)不是模型跑通而是新抓的 1000 條評(píng)論經(jīng)過你的推理鏈路后宏平均 F1 不低于訓(xùn)練時(shí)的 95%。我自己做這類項(xiàng)目養(yǎng)成的習(xí)慣是每次改完數(shù)據(jù)或模型先把結(jié)果記錄在一個(gè) Markdown 文件里再繼續(xù)下一步。沒有記錄的實(shí)驗(yàn)等于沒做。這個(gè)習(xí)慣讓后面寫論文時(shí)省了大量回頭補(bǔ)實(shí)驗(yàn)的時(shí)間。希望幫到你少踩一些我踩過的坑。本文還有配套的精品資源點(diǎn)擊獲取