》回目關(guān)鍵詞:中文古白話文本分析實(shí)戰(zhàn))
做文本分析這些年我越來(lái)越覺(jué)得中文語(yǔ)料的坑比想象中多。前陣子接了個(gè)活兒把《紅樓夢(mèng)》一百二十回按回目做核心關(guān)鍵詞提取用來(lái)快速瀏覽每回到底講了啥順便看看能不能發(fā)現(xiàn)一些隱藏的敘事線索。標(biāo)題里寫(xiě)的“基于 TF-IDF”聽(tīng)起來(lái)是個(gè)老掉牙的方法但真拿它去處理古白話小說(shuō)尤其是《紅樓夢(mèng)》這種人物多、場(chǎng)景切換快、虛實(shí)交織的文本還是會(huì)遇到一堆預(yù)料之外的麻煩。這篇就復(fù)盤(pán)一下整個(gè)實(shí)戰(zhàn)過(guò)程包括我踩過(guò)的坑、調(diào)過(guò)的參數(shù)以及最后怎么讓結(jié)果看起來(lái)像人話而不是術(shù)語(yǔ)堆砌。1. 項(xiàng)目概述為什么選 TF-IDF 和《紅樓夢(mèng)》分回文本1.1 核心需求從三十萬(wàn)字的章回體里快速定位每回焦點(diǎn)《紅樓夢(mèng)》全書(shū)一百二十回每回少則五六千字多則上萬(wàn)字通讀一遍費(fèi)時(shí)費(fèi)力。文本挖掘的第一步通常是“降維”先把每回的主要內(nèi)容壓縮成幾個(gè)關(guān)鍵詞讓人一眼看出這回的主線是什么。比如某回如果提到“鳳姐”“操辦”“喪事”大概率能猜到是協(xié)理寧國(guó)府的情節(jié)如果出現(xiàn)“黛玉”“焚稿”“斷癡情”那基本是高鶚續(xù)書(shū)里的名場(chǎng)面。這個(gè)需求在實(shí)際場(chǎng)景里很有用——做文學(xué)研究的人可以用它輔助標(biāo)注做在線閱讀平臺(tái)的編輯可以用它生成章節(jié)摘要普通讀者用它當(dāng)導(dǎo)讀也方便。但“提取核心關(guān)鍵詞”這件事難點(diǎn)不在算法本身而在“什么算核心”。在《紅樓夢(mèng)》這種文本里高頻人名、地名、器物名會(huì)淹沒(méi)真正的動(dòng)作和事件比如賈母、寶玉這兩個(gè)詞幾乎每回都會(huì)出現(xiàn)但對(duì)“這一回”的區(qū)分度極低。所以做這件事的核心思路不是單純統(tǒng)計(jì)詞頻而是找一個(gè)能平衡“詞頻”和“區(qū)分度”的指標(biāo)TF-IDF就是這么個(gè)東西。1.2 方案選型為什么沒(méi)用 LDA 也沒(méi)用 TextRank在動(dòng)手之前肯定要糾結(jié)一下方案。市面上常見(jiàn)的文本關(guān)鍵詞提取無(wú)外乎三種路線統(tǒng)計(jì)路線TF-IDF、詞頻、圖路線TextRank、主題模型路線LDA。我當(dāng)時(shí)選 TF-IDF 而不是另外兩個(gè)原因有三條。第一LDA 適合做“主題歸納”而不是“關(guān)鍵詞提取”。LDA 輸出的是詞的概率分布擬合的是“這篇文章在談?wù)撌裁粗黝}”粒度太粗。比如某回模型算出“主題1家庭事務(wù)”你還是不知道具體是哪個(gè)事件。而且 LDA 需要人為指定主題數(shù) k調(diào)起來(lái)主觀性太強(qiáng)不同 k 值結(jié)果天差地別不太適合做批量自動(dòng)化的章節(jié)導(dǎo)讀。第二TextRank 的效果受文本長(zhǎng)度影響很大而且更吃圖構(gòu)建時(shí)的共現(xiàn)窗口參數(shù)。分回文本長(zhǎng)度不一從 3000 到 9000 字都有固定窗口會(huì)把短章和長(zhǎng)章的連邊數(shù)量搞得不可比導(dǎo)致有些回目提取出的詞特別散亂。TF-IDF 對(duì)文本長(zhǎng)度沒(méi)有那么敏感因?yàn)樗举|(zhì)上是在每個(gè)文檔內(nèi)部做詞頻歸一化再乘逆文檔頻率。第三TF-IDF 在 sklearn 里實(shí)現(xiàn)成熟、參數(shù)透明跑批處理非常穩(wěn)。配合 jieba 分詞百來(lái)行代碼就能出一個(gè)基線結(jié)果后續(xù)調(diào)優(yōu)的空間也大。說(shuō)白了先用一套可解釋性強(qiáng)的基線方法把流程打通后面再疊加 TextRank 或者詞向量做融合心里才踏實(shí)。最終我的技術(shù)棧定成了Python jieba 分詞 sklearn 的 TfidfVectorizer pandas 做結(jié)果整理。數(shù)據(jù)源用的是網(wǎng)上公開(kāi)的《紅樓夢(mèng)》全文 TXT按回目切分好。整個(gè)流程跑完一百二十回每個(gè)回目輸出 8-10 個(gè)核心關(guān)鍵詞大概花了不到二十分鐘效果比預(yù)想的好。2. TF-IDF 核心原理與中文文本預(yù)處理2.1 一個(gè)樸素但有效的基礎(chǔ)假設(shè)詞的重要性 在本文中多 在全書(shū)中少先從原理說(shuō)起。TF-IDF 由兩部分組成TF 是詞頻IDF 是逆文檔頻率。前者衡量詞在當(dāng)前文檔中出現(xiàn)的頻繁程度后者衡量詞在整個(gè)語(yǔ)料庫(kù)中出現(xiàn)文檔數(shù)量的倒數(shù)取對(duì)數(shù)。公式是TF-IDF(t, d) TF(t, d) × IDF(t)其中 IDF(t) log(N / (1 DF(t)))N 是總文檔數(shù)DF(t) 是包含詞 t 的文檔數(shù)。這里的關(guān)鍵思想用大白話講就是一個(gè)詞如果在某一回里反復(fù)出現(xiàn)但在其他回目里很少出現(xiàn)那它大概率是這一回的特征詞。反過(guò)來(lái)像“說(shuō)道”“只見(jiàn)”“賈母”這種每回都有的詞DF 值很大IDF 接近 0權(quán)重被壓得很低自然排不上號(hào)。但《紅樓夢(mèng)》的文風(fēng)造成了一個(gè)現(xiàn)象很多高頻動(dòng)詞在全書(shū)里分布均勻比如“笑道”“不敢”“起身”這些詞在 TF 那一步得分不低但 IDF 會(huì)把它拉下來(lái)。真正能體現(xiàn)“分回焦點(diǎn)”的往往是具體名詞——人名、物品、事件詞。比如“猜謎”“螃蟹”“詩(shī)社”這類(lèi)在某幾回里高度集中IDF 會(huì)給它們很高的權(quán)重。2.2 中文分詞難點(diǎn)古白話、人名歧義和詞表構(gòu)建中文和英文最大的不同是分詞。英文天然按空格切詞中文需要先分詞分詞結(jié)果直接決定了關(guān)鍵詞提取的上限。jieba 默認(rèn)詞典是面向現(xiàn)代漢語(yǔ)的處理《紅樓夢(mèng)》這種古白話會(huì)有幾個(gè)明顯問(wèn)題。其一古白話專(zhuān)名詞在默認(rèn)詞典里缺失比如“櫳翠庵”“妙玉”“蘅蕪苑”這類(lèi)地名、人名jieba 可能會(huì)切成“櫳翠”“庵”“妙玉”甚至“蘅蕪”“苑”這種碎片導(dǎo)致詞頻統(tǒng)計(jì)失真。其二同一個(gè)人名可能有多種稱(chēng)呼。賈寶玉在文里可以是“寶玉”“寶二爺”“怡紅公子”林黛玉可以是“黛玉”“林妹妹”“顰兒”。如果不去歸一化這些稱(chēng)呼會(huì)被當(dāng)成不同詞各自分散 TF 值導(dǎo)致真正的核心人物反而不突出。其三現(xiàn)代漢語(yǔ)詞匯和古白話詞匯混在一起。比如“丫鬟”在文里更多寫(xiě)成“丫頭”“吃飯”更多寫(xiě)成“用飯”如果不做針對(duì)性的詞表調(diào)整提取結(jié)果會(huì)偏向現(xiàn)代詞不夠貼合文本風(fēng)格。針對(duì)這些問(wèn)題我做了三件預(yù)處理先加載 jieba 自帶詞典再自定義追加一批《紅樓夢(mèng)》專(zhuān)有名詞包括主要人物、字、號(hào)、居所、器物名。在分詞結(jié)果上做同義詞歸一化把“寶二爺”“怡紅公子”“寶玉”統(tǒng)一映射成“寶玉”把“林妹妹”“顰兒”“黛玉”統(tǒng)一映射成“黛玉”。這里要注意不是所有詞都能粗暴合并比如“鳳姐”和“王熙鳳”在不同章節(jié)語(yǔ)境里的實(shí)體指向是同一個(gè)可以合并但“寶玉”和“通靈寶玉”指向完全不同合并了會(huì)亂套所以映射表要人工盯一遍。自定義停用詞表。除了常見(jiàn)的“的”“了”“之”還要加入大量文言虛詞和敘事套話比如“話說(shuō)”“且說(shuō)”“卻說(shuō)”“只見(jiàn)”“不知”等。這套停用詞表是我手動(dòng)從結(jié)果里一遍遍挑出來(lái)的后面會(huì)細(xì)說(shuō)。2.3 沒(méi)有絕對(duì)通用的停用詞表只有不斷迭代的停用詞表這里插一句我在項(xiàng)目里的真實(shí)感受。網(wǎng)上有很多公開(kāi)的“中文停用詞表”但直接用往往效果很一般。因?yàn)橥S迷~是高度語(yǔ)料相關(guān)的對(duì)新聞?wù)Z料有效的表放到古白話小說(shuō)里就差勁了。我的做法是先用基礎(chǔ)停用詞表跑一遍基線結(jié)果然后把每回關(guān)鍵詞里那些明顯沒(méi)信息量的詞挑出來(lái)加入停用詞表再重跑。這個(gè)迭代我做了四輪。第一輪發(fā)現(xiàn)高頻干擾詞有“一個(gè)”“如何”“只見(jiàn)”第二輪發(fā)現(xiàn)“因”“便”“方”“且”等文言連詞混進(jìn)了關(guān)鍵詞第三輪發(fā)現(xiàn)一些像“老太太”“太太”“姑娘”的稱(chēng)謂詞雖然區(qū)分度低但偶爾進(jìn) Top第四輪又清了一批“話說(shuō)”“不在話下”之類(lèi)的過(guò)門(mén)套話。迭代大概能讓結(jié)果質(zhì)量提升一個(gè)檔次。解決方法不復(fù)雜就是從輸出反推詞表但得有點(diǎn)耐心一遍全跑完再一起加不要看到一個(gè)改一次那樣效率太低。3. 實(shí)操過(guò)程數(shù)據(jù)清洗、分詞、TF-IDF 計(jì)算與結(jié)果整理3.1 數(shù)據(jù)準(zhǔn)備從原始文本到一百二十個(gè)獨(dú)立文檔這一步看起來(lái)平平無(wú)奇其實(shí)最容易出鬼。我在網(wǎng)上找的《紅樓夢(mèng)》全文 TXT 版本有各種殘次品有帶校注者注釋的有不帶注釋的有章節(jié)標(biāo)題格式不統(tǒng)一的有的還混有脂評(píng)。我的清洗流程是這樣的把原文里的空格、全角空白、換行控制符統(tǒng)一清掉只保留中文字符和少數(shù)必要標(biāo)點(diǎn)。用正則表達(dá)式按“第X回”切分文本。注意有些版本寫(xiě)作“第一回”有些寫(xiě)作“第一回 ”后面帶空格還有些寫(xiě)作“卷 第X回 ”我統(tǒng)一用re.split(r第[一二三四五六七八九十百零]回, text)但不直接 split而是先找到所有回目標(biāo)題位置再按位置切片。每回的內(nèi)容里可能殘留回目標(biāo)題和副題比如“賈雨村風(fēng)塵懷閨秀”我把它們剔除了不然回目標(biāo)題里的詞必然在關(guān)鍵詞結(jié)果里刷屏。最后一共得到 120 個(gè)文檔存成每回一個(gè) txt再讀入 pandas DataFrame列名叫 “chapter” 和 “content”。這里我順手統(tǒng)計(jì)了一下每回字?jǐn)?shù)最長(zhǎng)的一回有 9640 字最短的 4260 字字?jǐn)?shù)差距接近兩倍。這個(gè)統(tǒng)計(jì)對(duì)后面設(shè)置 TF-IDF 參數(shù)有幫助也讓我意識(shí)到長(zhǎng)度差異確實(shí)存在后面考慮要不要用歸一化策略時(shí)心里有底。3.2 分詞和文本向量化代碼實(shí)現(xiàn)與參數(shù)取舍分詞和向量化我用的是 jieba 加 sklearn 的 TfidfVectorizer。核心代碼不長(zhǎng)貼一段關(guān)鍵實(shí)現(xiàn)import jieba import pandas as pd from sklearn.feature_extraction.text import TfidfVectorizer # 假設(shè) df 已經(jīng)包含 chapter 和 content 兩列 def cut_words(text): return .join(jieba.lcut(text)) df[cut_content] df[content].apply(cut_words) vectorizer TfidfVectorizer( max_features3000, ngram_range(1, 2), stop_wordsstop_words_list, sublinear_tfTrue ) tfidf_matrix vectorizer.fit_transform(df[cut_content]) feature_names vectorizer.get_feature_names_out()幾個(gè)參數(shù)我當(dāng)時(shí)斟酌了很久逐個(gè)說(shuō)一下。max_features3000的意思是只考慮全部文檔中 DF 值最大的 3000 個(gè)詞。這能砍掉大量只出現(xiàn)一兩次的冷門(mén)詞和分詞碎片。實(shí)際上對(duì)一百二十回的小說(shuō)來(lái)說(shuō)去掉虛詞和超低頻詞之后特征詞總數(shù)大概八千左右3000 是個(gè)比較合適的截?cái)帱c(diǎn)。太小會(huì)漏掉重要事件詞太大會(huì)讓向量變得稀疏增加噪聲。ngram_range(1, 2)允許詞和相鄰詞的組合作為特征。為什么要開(kāi) bigram因?yàn)椤都t樓夢(mèng)》里很多關(guān)鍵事件是固定搭配比如“葬花”“焚稿”“螃蟹宴”單獨(dú)一個(gè)“花”或“稿”信息量太小bigram 能把這些組合詞抓出來(lái)。實(shí)測(cè)開(kāi) bigram 之后比如 第二十七回“滴翠亭楊妃戲彩蝶 埋香冢飛燕泣殘紅”核心詞里出現(xiàn)了“葬花”這個(gè)組合效果立刻比只看單個(gè)詞好。sublinear_tfTrue這個(gè)參數(shù)很多人忽略。它把 TF 做了1 log(TF)的對(duì)數(shù)放縮防止某些回里同一個(gè)詞反復(fù)出現(xiàn)導(dǎo)致 TF 值爆炸。比如某回里“酒”字出現(xiàn) 30 次不代表它比出現(xiàn) 15 次的詞重要兩倍對(duì)數(shù)放縮讓這種差距變緩更符合直覺(jué)。實(shí)測(cè)下來(lái)開(kāi)了這個(gè)參數(shù)后高頻詞不霸榜了低頻但關(guān)鍵的事件詞能浮上來(lái)。最后是 IDF 的計(jì)算方式。sklearn 默認(rèn)的 smooth_idfTrue分子分母都加平滑項(xiàng)避免除零。這個(gè)默認(rèn)值我保留沒(méi)動(dòng)因?yàn)槲臋n數(shù)固定 120平滑與否對(duì)排序影響很小。3.3 結(jié)果輸出哪些詞能成為真正的“回目之眼”計(jì)算完成后從 tfidf_matrix 里取每一行的 Top 詞輸出成表格。我把每回取前 12 個(gè)然后人工抽樣檢查了十幾個(gè)回目的效果。先看幾個(gè)示例結(jié)果回?cái)?shù)前 8 個(gè)核心關(guān)鍵詞第二十七回葬花、黛玉、滴翠亭、寶釵、彩蝶、紅玉、鳳姐、帕子第四十回劉姥姥、賈母、行令、茄鲞、鴛鴦、笑話、杯子、酒令第六十六回尤三姐、柳湘蓮、劍、鴛鴦劍、退婚、尤二姐、賈璉、定禮第九十八回黛玉、寶釵、寶玉、病、淚、斷癡情、紫鵑、魂歸、出嫁、狠心第二十七回的結(jié)果尤其漂亮“滴翠亭”和“葬花”分列兩頭正好對(duì)應(yīng)這一回的兩條主線撲蝶和葬花。第四十回“茄鲞”“行令”直接鎖定宴飲場(chǎng)面和那句著名的“老劉老劉食量大如?!?。第六十六回“尤三姐”“鴛鴦劍”“退婚”精準(zhǔn)命中剛烈女子自刎的名場(chǎng)面。第九十八回也就是“苦絳珠魂歸離恨天”那一回“魂歸”“淚”“斷癡情”排得很靠前這說(shuō)明只要預(yù)處理到位TF-IDF 完全有能力抓住敘事的情緒高潮。但也有失敗的回目。最典型的是第一回“甄士隱夢(mèng)幻識(shí)通靈 賈雨村風(fēng)塵懷閨秀”提取出的關(guān)鍵詞里全是“通靈”“頑石”“甄士隱”“賈雨村”“太虛幻境”這些超高頻概念和“懷閨秀”相關(guān)的“嬌杏”反而被淹沒(méi)了。原因也簡(jiǎn)單第一回是全書(shū)的“楔子”信息密度極高概念詞一個(gè)接一個(gè)TF-IDF 在這種超長(zhǎng)密集文本上會(huì)把每個(gè)詞都分配權(quán)重反而難以聚焦。這屬于方法本身的局限我用后處理做了修正后面細(xì)說(shuō)。3.4 同義合并和實(shí)體歸一化的實(shí)現(xiàn)細(xì)節(jié)剛才提了一嘴同義詞歸一化這里展開(kāi)說(shuō)說(shuō)。我的做法是在分詞之后、向量化之前對(duì) cut_content 的 token 序列做一次映射替換。alias_map { 寶二爺: 寶玉, 怡紅公子: 寶玉, 絳洞花主: 寶玉, 林妹妹: 黛玉, 顰兒: 黛玉, 瀟湘妃子: 黛玉, 璉二爺: 賈璉, 鳳辣子: 王熙鳳, 老祖宗: 賈母 } def normalize_tokens(tokens): return [alias_map.get(tok, tok) for tok in tokens]這個(gè)映射表要非常謹(jǐn)慎。比如“瀟湘妃子”是黛玉的號(hào)合并是合理的“瀟湘館”是住所不能合并。再有“襲人”和“花襲人”可以合并但“花氣襲人知晝暖”里的“襲人”是動(dòng)詞不能合并。雖然這句話在文中出現(xiàn)次數(shù)不多但映射表的構(gòu)建原則就是“寧可遺漏不可錯(cuò)并”錯(cuò)了會(huì)引入新的混亂。另外一些人名在不同回里以姓氏稱(chēng)謂出現(xiàn)比如“賈母”有時(shí)叫“史太君”有時(shí)叫“老太太”?!袄咸痹谫Z府語(yǔ)境里基本專(zhuān)指賈母但偶爾也指王夫人或邢夫人這種有歧義的我就不合并只保留“賈母”和“史太君”的同義映射。做完歸一化再跑 TF-IDF最直觀的變化是“寶玉”“黛玉”“王熙鳳”這些關(guān)鍵人物的排名普遍上升了因?yàn)樗鼈兊母鱾€(gè)別名 TF 值合并在一起。但同時(shí)也要防一手歸一化后某些詞的整體 DF 變大IDF 變小如果某個(gè)詞本來(lái)是某回的別名集中出現(xiàn)的比如只有某回用了“瀟湘妃子”這個(gè)號(hào)合并成“黛玉”之后反而可能被稀釋掉。所以后來(lái)我做了一個(gè)變通保留原始別名作為獨(dú)立特征但也在結(jié)果表里標(biāo)注其對(duì)應(yīng)的人名這樣既不破壞 IDF 的區(qū)分度又方便人眼理解。4. 結(jié)果解讀技巧與常見(jiàn)問(wèn)題排查實(shí)錄4.1 從關(guān)鍵詞序列反推敘事結(jié)構(gòu)三個(gè)解讀維度提取出關(guān)鍵詞之后不是結(jié)束而是開(kāi)始。單看一個(gè)回目里的關(guān)鍵詞列表容易迷失在詞海里。我總結(jié)了三個(gè)實(shí)用的解讀維度。第一是“回內(nèi)對(duì)比”。把每回關(guān)鍵詞按權(quán)重從高到低排觀察前三個(gè)詞和后幾個(gè)詞的差異。如果前三個(gè)都是人物名說(shuō)明這一回偏向“人物驅(qū)動(dòng)”主線可能?chē)@關(guān)系沖突展開(kāi)如果前三個(gè)里有大量器物名或地點(diǎn)名那這一回更可能偏向“場(chǎng)景驅(qū)動(dòng)”。比如第十七回“大觀園試才題對(duì)額”關(guān)鍵詞里“園”“匾”“對(duì)額”“賈政”“題詞”占了半席一下就能判斷這是空間與建筑描寫(xiě)為主的回目。第二是“回間對(duì)比”。把相鄰幾回的關(guān)鍵詞放一起看能看出敘事節(jié)奏的推進(jìn)。比如第六十三回到第六十五回關(guān)鍵詞從“寶玉”“夜宴”“群芳”逐步過(guò)渡到“尤二姐”“尤三姐”“賈璉”“偷娶”“退婚”這說(shuō)明敘事線從大觀園日常轉(zhuǎn)移到寧國(guó)府風(fēng)波節(jié)奏由輕松轉(zhuǎn)入緊張。這種視角對(duì)文學(xué)研究很有用可以用它來(lái)給全書(shū)做“敘事段”分割。第三是“人物關(guān)聯(lián)度”。我不止算關(guān)鍵詞還順手統(tǒng)計(jì)了在每一回里“關(guān)鍵詞與人物名是否共現(xiàn)”。具體做法很簡(jiǎn)單就是在 TF-IDF 矩陣?yán)锾舫鋈宋锩诘牧泻褪录~所在的列做相關(guān)性排序。比如第四十回里“劉姥姥”和“笑話”共現(xiàn)的權(quán)重很高可以推斷這一回的笑點(diǎn)主要由劉姥姥制造。這個(gè)方法有點(diǎn)糙但對(duì)于快速驗(yàn)證敘事直覺(jué)已經(jīng)夠用。4.2 常見(jiàn)坑位盤(pán)點(diǎn)五個(gè)幾乎必踩的問(wèn)題整個(gè)項(xiàng)目從零到一跑了七八遍踩過(guò)的坑我按頻次排了序列成表格供后來(lái)人直接避雷。問(wèn)題現(xiàn)象原因解決方案回目標(biāo)題刷屏“通靈”“懷秀”跑進(jìn)前 5切分時(shí)沒(méi)去掉回目標(biāo)題行先識(shí)別回目標(biāo)題并剔除再切正文同義詞分散權(quán)重“寶二爺”“怡紅公子”各自低頻人稱(chēng)、號(hào)太多做別名映射但要防歧義文言虛詞殘留“便”“因”“方”進(jìn)入Top詞基礎(chǔ)停用詞表不夠古白話特化四輪迭代擴(kuò)充停用詞表bigram 組合太僵“了的”“之處”這類(lèi)無(wú)效組合默認(rèn)bigram不夠或參數(shù)開(kāi)太大結(jié)合自定義詞典再開(kāi)bigram地名與人物混淆“蘅蕪苑”被切成“蘅蕪”“苑”jieba 默認(rèn)詞典缺古白話詞自定義追加專(zhuān)名維護(hù)詞典這當(dāng)中最難的其實(shí)是第一個(gè)回目標(biāo)題刷屏。我最初用正則直接按“第X回”切分沒(méi)處理標(biāo)題行結(jié)果切出來(lái)的第一回正文開(kāi)頭還是“甄士隱夢(mèng)幻識(shí)通靈”這七個(gè)字直接霸占當(dāng)回 TF 值前列。后來(lái)我把切分邏輯改成先定位所有回目標(biāo)題的起止位置然后把標(biāo)題從正文里替換為“”再分詞問(wèn)題才消失。還有一個(gè)小坑是編碼問(wèn)題。很多公開(kāi) TXT 是 GBK 編碼用 UTF-8 讀會(huì)直接亂碼。讀取時(shí)我統(tǒng)一encodinggb18030做兼容把錯(cuò)誤處理設(shè)成errorsignore然后再轉(zhuǎn)存為 UTF-8 的干凈版本。這個(gè)坑雖然小但能讓人白忙活一小時(shí)。4.3 超長(zhǎng)回目和信息密集回目的后處理方法剛才提到第一回效果不好這也是 TF-IDF 方法的典型痛點(diǎn)當(dāng)一篇文檔里的各個(gè)信息點(diǎn)都重要時(shí)TF-IDF 不知道該怎么“側(cè)重”只能平均分配權(quán)重。我試過(guò)幾種補(bǔ)救方法效果不一這里分享一條我覺(jué)得可行的路線。先做“片段滑動(dòng)窗口”把每個(gè)回目的文本按 200 字一個(gè)窗口切成段分別在窗口級(jí)做 TF-IDF再把窗口排名結(jié)果做聚類(lèi)聚合。這樣能緩解長(zhǎng)回目?jī)?nèi)部的語(yǔ)義漂移。不過(guò) 120 回的文檔量不大窗口滑動(dòng)后文檔數(shù)膨脹到上萬(wàn)我當(dāng)時(shí)為了跑得快只對(duì)個(gè)別超長(zhǎng)回目做了這個(gè)處理沒(méi)有全量啟用。再就是加一個(gè)后處理規(guī)則從候選詞里剔除“與全書(shū)所有回目共現(xiàn)頻率超過(guò) 70%”的詞。雖然這些詞的 TF 可能很高但它們不可能是某回的特征詞。比如“寶玉”在 120 回里出現(xiàn)次數(shù)為 113 回這個(gè) DF 太高了即使它是某回的關(guān)鍵人物名也不具備區(qū)分度。但這種詞又不能一概剔除因?yàn)橛行┗啬坷铩皩氂瘛贝_實(shí)是事件核心刪了就丟了主角。所以我的做法是把這些高頻詞降低權(quán)重而不是直接刪設(shè)置一個(gè)懲罰系數(shù)DF 100 的詞權(quán)重乘以 0.6DF 在 80-100 之間的乘以 0.8。實(shí)測(cè)懲罰后人物名與事件詞的平衡感更好例如第一回里名場(chǎng)面相關(guān)的“識(shí)通靈”“懷閨秀”這類(lèi)事件詞排名上升了。4.4 如何選 Top N 關(guān)鍵詞不是越多越好Top N 的數(shù)量直接關(guān)系到可讀性。我最初輸出 Top 20結(jié)果每回的關(guān)鍵詞表格長(zhǎng)得像天書(shū)第 15 個(gè)詞開(kāi)始出現(xiàn)一堆沒(méi)有敘事意義的低權(quán)重詞比如“眾人”“一面”“一面又”看著枯燥也無(wú)助于導(dǎo)讀。后來(lái)我做了個(gè)實(shí)驗(yàn)把 N 從 5 調(diào)到 20人工對(duì)比十個(gè)回目的結(jié)果發(fā)現(xiàn) 8-12 個(gè)是一個(gè)甜蜜區(qū)間。少于 8 個(gè)摘要粒度太粗比如某回只給了人物名事件動(dòng)詞沒(méi)浮出來(lái)多于 12 個(gè)噪聲明顯上升。最終我統(tǒng)一輸出每回前 10 個(gè)詞再在權(quán)重超過(guò)平均值 1.2 倍的關(guān)鍵詞里額外選 2 個(gè)時(shí)報(bào)詞匯這個(gè)策略基本穩(wěn)定。這里也要提醒N 的選取不能完全拍腦袋最好基于人工抽樣看幾回再定。不同語(yǔ)料的甜蜜區(qū)間不一樣比如分析新聞標(biāo)題可能 5 個(gè)就夠分析學(xué)術(shù)摘要15 個(gè)也合理。關(guān)鍵是每回結(jié)果的“可讀性一致”。5. 后續(xù)擴(kuò)展從單純關(guān)鍵詞到章節(jié)“迷你摘要”5.1 關(guān)鍵詞串聯(lián)成句給閱讀器用的淺層摘要TF-IDF 提取的關(guān)鍵詞是碎片如果想做成讀者可讀的迷你摘要還有一個(gè)后處理步驟。我的想法是既然每回有 10 個(gè)核心關(guān)鍵詞那就用這些詞去原文里找片段把包含 3 個(gè)及以上關(guān)鍵詞的句子抽出來(lái)按出現(xiàn)順序拼接就能得到一段原始文本構(gòu)成的迷你摘要。實(shí)操時(shí)我先按句號(hào)、問(wèn)號(hào)、感嘆號(hào)把每回切成句子然后篩出含核心關(guān)鍵詞數(shù)量 3 的句子按在原文里的順序排好。這個(gè)操作在第二十七回的效果讓我很驚訝——拼接出來(lái)的句子幾乎就是“黛玉葬花”和“寶釵撲蝶”的原始描寫(xiě)片段完全可以當(dāng)導(dǎo)讀用。sentences re.split(r[。], chapter_text) key_sentence [] for sent in sentences: hit sum(1 for word in keyword_list if word in sent) if hit 3: key_sentence.append(sent) summary 。.join(key_sentence[:5])這一招有個(gè)附帶的好處它既保留了原文文風(fēng)又能保證摘要內(nèi)容不是模型瞎編的。對(duì)文學(xué)類(lèi)文本來(lái)說(shuō)保持原汁原味非常重要。后來(lái)我用這個(gè)方法給一百二十回都生成了迷你摘要人工抽查了十回基本都能覆蓋該回主要情節(jié)。除了極個(gè)別回目因?yàn)樵拿鑼?xiě)分散、一句話里難湊三個(gè)關(guān)鍵詞摘要會(huì)拼出一些跳躍感但整體可用性已經(jīng)遠(yuǎn)超純關(guān)鍵詞列表。5.2 多方法融合TF-IDF 作為基線的意義最后說(shuō)一點(diǎn)關(guān)于方法選型的思考。TF-IDF 這個(gè)算法 1972 年就提出來(lái)了快六十年了今天看來(lái)很簡(jiǎn)單甚至有人覺(jué)得過(guò)時(shí)。拉一個(gè)詞向量模型出來(lái)可能顯得更“先進(jìn)”但我做這個(gè)項(xiàng)目的體會(huì)是在文本分析里“可解釋的簡(jiǎn)單方法”往往比“不可解釋的復(fù)雜模型”更值得先做。因?yàn)?TF-IDF 的每一步都能落到具體詞和具體權(quán)重上出了奇怪結(jié)果可以直接反查對(duì)于古代文本這種噪聲極高的語(yǔ)料尤其重要。我現(xiàn)在把整個(gè)流程沉淀成了一個(gè)復(fù)現(xiàn)性較強(qiáng)的代碼庫(kù)輸入是任意長(zhǎng)文本集合輸出是每個(gè)文檔的 Top 詞表和句子級(jí)迷你摘要。后續(xù)要做的話可以在 TF-IDF 結(jié)果之上疊加預(yù)訓(xùn)練語(yǔ)言模型做關(guān)鍵詞消歧比如把“葬花”和“黛玉”之間的語(yǔ)義關(guān)聯(lián)進(jìn)一步強(qiáng)化。不過(guò)那是更靠后的優(yōu)化方向基線階段我仍然推薦老老實(shí)實(shí)用 TF-IDF 把數(shù)據(jù)和預(yù)處理打磨干凈畢竟后面所有花哨模型的效果都建立在這一步的地基上。按我個(gè)人的經(jīng)驗(yàn)跑這類(lèi)項(xiàng)目最大的收獲反而不在算法本身而在對(duì)“文本差異”的敏感度。《紅樓夢(mèng)》每一回的語(yǔ)言風(fēng)格、敘事密度、詞場(chǎng)分布差異巨大而這些差異在 TF-IDF 的權(quán)重里體現(xiàn)得很分明??炊嗔私Y(jié)果表你對(duì)“什么詞才是區(qū)分性的”會(huì)形成直覺(jué)這直覺(jué)在做任何 NLP 項(xiàng)目時(shí)都好用。