沒讀過《紅樓夢》也能知道前后四十回是不是一個(gè)作者寫的?很久以前,數(shù)據(jù)俠黎晨,用機(jī)器學(xué)習(xí)的算法分析了《紅樓夢》,認(rèn)為后四十回和前八十回內(nèi)容上有明顯差距。不過,數(shù)據(jù)俠樓宇卻不這么認(rèn)為,他覺得原先的判定方法不夠嚴(yán)謹(jǐn),于是他使用了無字典分詞的方式,剔除了情節(jié)對(duì)分析的影響,再次用機(jī)器學(xué)習(xí)的算法分析了這部文學(xué)名著。
構(gòu)建全文索引與全文字典
兩個(gè)月以來,我通過互聯(lián)網(wǎng)自學(xué)了一些文本處理的知識(shí),用自然語言處理和機(jī)器學(xué)習(xí)算法對(duì)《紅樓夢》進(jìn)行了一些分析。這個(gè)過程中我找到了一些有趣的發(fā)現(xiàn)。
我開始做這件事情是因?yàn)橹翱吹搅艘黄猛娴奈恼?,大概?nèi)容是,作者用“結(jié)巴分詞”這個(gè)開源軟件統(tǒng)計(jì)了紅樓夢中各詞匯的出現(xiàn)次數(shù)(也就是詞頻),然后用詞頻作為每個(gè)章回的特征,最終用“主成份分析”算法把每個(gè)章回映射到三維空間中,從而比較各個(gè)章回的用詞有多么相似。(DT君注:數(shù)據(jù)俠黎晨原文《從沒看過紅樓夢,如何用機(jī)器學(xué)習(xí)判定后40回并非曹雪芹所寫》)作者的結(jié)論是后四十回的用詞和前八十回有明顯的差距。
我覺得文章有兩個(gè)小問題:首先,作者用的結(jié)巴分詞里的詞典是根據(jù)現(xiàn)代文的語料獲得的,而《紅樓夢》是半文半白的,這樣的分詞方法準(zhǔn)確性存疑;其次,雖然作者用《三國演義》做了對(duì)比,但是依然沒有有力地證明用詞差異沒有受到情節(jié)變化的影響。于是我決定自己做一遍實(shí)驗(yàn),用無字典分詞的方法來分詞,并且嘗試剔除情節(jié)對(duì)分析的影響,看看結(jié)果會(huì)不會(huì)有所不同。
在處理文章之前,我需要建立一個(gè)全文索引。這樣是為了快速地查找原文內(nèi)容,加速后面的計(jì)算。我使用了后綴樹這個(gè)結(jié)構(gòu)作為索引,用了Ukkonen算法快速地創(chuàng)建了整篇《紅樓夢》的后綴樹(Ukkonen 算法的速度非常快,用專業(yè)的語言描述,它的時(shí)間復(fù)雜度是 O(n))。這樣我們就有了全文索引了。
接下來我們就要構(gòu)建一個(gè)字典了。
等等,我們不是要無字典分詞嗎,為什么還要制作字典?其實(shí)無字典分詞并不是完全不用字典,只是說字典是根據(jù)原文生成的,而不是提前制作的。為了進(jìn)行分詞,我們還是需要先找出文章中哪些內(nèi)容像是單詞,才能確定如何進(jìn)行切分。
那么怎么確定哪些內(nèi)容像單詞呢?最容易想到的方法就是:把所有出現(xiàn)次數(shù)高的片段都當(dāng)成單詞。聽上去很有道理,所以我們可以試一試,用后綴樹查詢紅樓夢中的所有重復(fù)的片段,然后按出現(xiàn)次數(shù)排個(gè)序:
寶玉(3983)、笑道(2458)、太太(1982)、什么(1836)、鳳姐(1741)、了一(1697)、賈母(1675)、一個(gè)(1520)、也不(1448)、夫人(1437)、黛玉(1370)、我們(1233)、那里(1182)、襲人(1144)、姑娘(1142)、去了(1090)、寶釵(1079)、不知(1074)、王夫人(1061)、起來(1059)
上面是出現(xiàn)頻率前20的片段,括號(hào)內(nèi)是出現(xiàn)次數(shù)。可以看到效果還不錯(cuò),很多片段都是單詞。然而,排在第六名的“了一”明明不是個(gè)單詞,出現(xiàn)次數(shù)卻比賈母還要高??梢娺@樣的篩選方法還是有一定問題的。而且,這樣被誤當(dāng)成單詞的片段還有很多,例如“了的”、“的一”之類的。
為了排除這樣的組合,我們可以用“凝固度”來進(jìn)行進(jìn)一步地篩選。凝固度可以排除單字的頻率對(duì)組合頻率的影響。經(jīng)過實(shí)驗(yàn),我發(fā)現(xiàn)整體效果還是不錯(cuò)
的。
DT君注:凝固度指的是,一個(gè)片段出現(xiàn)的頻率比左右兩部分分別出現(xiàn)的頻率的乘積高出多少倍。值得注意的是,頻率表示的是出現(xiàn)的比例,而頻數(shù)表示的是出現(xiàn)的次數(shù)。凝固度的思想是,如果片段實(shí)際出現(xiàn)的概率比被隨機(jī)組合出來的概率高出很多倍,就說明這樣的組合應(yīng)該不是意外產(chǎn)生的,而是有一些關(guān)聯(lián)的。這個(gè)關(guān)聯(lián)很可能就是因?yàn)檫@個(gè)片段是一個(gè)不可分割的整體,也就是單詞。
然而凝固度也有一定的問題。我們會(huì)發(fā)現(xiàn)還是有很多片段是半個(gè)詞,且也具有很高的凝固度。例如:“香院”(完整的詞應(yīng)該是“梨香院”)、“太太太太”(完整的詞應(yīng)該是“老太太太太”)。
想想也有道理,這些片段雖然是半個(gè)詞,但是它們確實(shí)也跟完整的單詞一樣是“凝固”在一起的。所以,光看凝固度是不夠的,還要通過上下文判斷這個(gè)詞是否完整。
為了排除掉不完整的單詞,我們可以使用自由度來繼續(xù)過濾,自由度描述的是一個(gè)片段相鄰的字有多么的不固定,一個(gè)真正的詞應(yīng)該相互之間的聯(lián)系應(yīng)
該是獨(dú)特的,不太會(huì)出現(xiàn)上文說的情況。也就是說如果片段的自由度比較高,就說明這個(gè)詞應(yīng)該是完整的。
DT君注:自由度的思想是,如果一個(gè)組合是一個(gè)不完整的單詞,那么它總是作為完整單詞的一部分出現(xiàn),所以相鄰的字就會(huì)比較固定。比如說,“香院”在原文中出現(xiàn)了 23 次,而“梨香院”出現(xiàn)了 22 次,也就是說“梨”在“香院”的左邊一起出現(xiàn)的頻率高達(dá) 95.7%,所以我們有把握認(rèn)為”香院”不是完整的單詞。而自由度描述的就是一個(gè)片段的相鄰字有多么的多樣、不固定。
有了這些明確的評(píng)判標(biāo)準(zhǔn),我們就可以把單詞篩選出來了。我最終選擇的判斷標(biāo)準(zhǔn)是:出現(xiàn)次數(shù)大于等于5,且凝固度、左側(cè)自由度、右側(cè)自由度都大于1。然而這個(gè)標(biāo)準(zhǔn)還是太寬松了。于是,我又設(shè)計(jì)了一個(gè)公式,把這些數(shù)據(jù)綜合起來:
也就是說,我簡單粗暴地把凝固度和自由度乘了起來,作為每個(gè)片段的分?jǐn)?shù)。這樣只要其中一個(gè)標(biāo)準(zhǔn)的值比較低,總分就會(huì)比較低。于是我的判斷標(biāo)準(zhǔn)里又多了一條:總分還要大于等于100。
經(jīng)過層層遴選之后,單詞表初步成型了。我從最終結(jié)果中隨機(jī)抽取了100個(gè)條目,其中有47個(gè)是希望得到的單詞:這意味單詞表的正確率只有一半左右。不過,在錯(cuò)誤的條目里,很多條目的切分其實(shí)是正確的,只是有好幾個(gè)詞粘到了一起。所以其實(shí)我們沒有必要通過調(diào)高篩選標(biāo)準(zhǔn)的方法來進(jìn)行更嚴(yán)格的過濾了。隨后分詞算法將會(huì)解決單詞沒有被切開的問題。
此外,根據(jù)字典的正確率和字典的大小,我計(jì)算出紅樓夢的詞匯量大概是 1.6 萬。
維特比算法找出最具效率的分詞方案
之前在篩選單詞的時(shí)候,思路就是用各種各樣的數(shù)值標(biāo)準(zhǔn)進(jìn)行判斷。而對(duì)于“分詞”這個(gè)看似更加困難的問題,思路也是類似的:制定一個(gè)評(píng)價(jià)切分方案的評(píng)分標(biāo)準(zhǔn),然后找出評(píng)分最高的切分方案。
評(píng)分標(biāo)準(zhǔn)是什么呢?最簡單的標(biāo)準(zhǔn)就是,把切分之后每個(gè)片段是單詞的概率都乘起來,作為這個(gè)切分方案正確的概率,也就是評(píng)分標(biāo)準(zhǔn)。我們假設(shè),一個(gè)片段是單詞的概率,就是這個(gè)片段在原文中的出現(xiàn)頻率。
有了評(píng)分標(biāo)準(zhǔn)之后,還有一個(gè)問題:如何找出分?jǐn)?shù)最高的切分方案呢?肯定不能一個(gè)一個(gè)地嘗試每一種方案,不然速度實(shí)在是太慢了。我們可以用一個(gè)數(shù)學(xué)方法來簡化計(jì)算:維特比算法。
維特比算法本質(zhì)上就是一個(gè)動(dòng)態(tài)規(guī)劃算法。它的想法是這樣的:對(duì)于句子的某個(gè)局部來說,這一部分的最佳切分方案是固定的,不隨上下文的變化而變化;如果把這個(gè)最佳切分方案保存起來,就能減少很多重復(fù)的計(jì)算。我們可以從第一個(gè)字開始,計(jì)算前兩個(gè)字,前三個(gè)字,前四個(gè)字……的最佳切分方案,并且把這些方案保存起來。
因?yàn)槲覀兪且来斡?jì)算的,所以每當(dāng)增加一個(gè)字的時(shí)候,我們只要嘗試切分最后一個(gè)單詞的位置就可以了。這個(gè)位置前面的內(nèi)容一定是已經(jīng)計(jì)算過的,所以通過查詢之前的切分方案即可計(jì)算出分?jǐn)?shù)。
在構(gòu)造單詞表的時(shí)候,我計(jì)算了每個(gè)片段有多么像單詞,也就是分?jǐn)?shù)。然而,后面的分詞算法只考慮了片段出現(xiàn)的頻率,而沒有用到片段的分?jǐn)?shù)。于是,我簡單粗暴地把片段的分?jǐn)?shù)加入到了算法中:把片段的頻率乘上片段的分?jǐn)?shù),作為加權(quán)了的頻率。這樣那些更像單詞的片段具有更高的權(quán)重,就更容易被切分出來了。
還有一個(gè)小優(yōu)化。我們知道,一般中文單詞的長度不會(huì)超過四個(gè)字,因此在程序枚舉切分方法的時(shí)候,只需要嘗試最后四個(gè)切分位置就可以了。這樣就把最長的切分片段限制在了四個(gè)字以內(nèi),而且對(duì)于長句子來說也減少了很多不必要的嘗試。
抽查程序運(yùn)行結(jié)果后發(fā)現(xiàn),最終程序分詞算法的準(zhǔn)確率是85.71%(意義是程序切開的位置有多少是應(yīng)該切開的),召回率是75.00%(意義是應(yīng)該切開的位置有多少被程序切開了)。這個(gè)結(jié)果看上去不是很高,因?yàn)榇蟛糠珠_源的分詞軟件準(zhǔn)確率都能達(dá)到90%以上,甚至能達(dá)到97%以上。不過,畢竟我用的是無字典的分詞,而且算法也比較簡單,所以我還是比較滿意的。
但是其中詩詞的分詞更難一些,準(zhǔn)確率相比其他部分低了10%左右。這也在情理之中,因?yàn)樵娫~中有很多不常用詞,有些詞甚至只出現(xiàn)過一次,所以電腦很難從統(tǒng)計(jì)數(shù)據(jù)中發(fā)掘信息。
統(tǒng)計(jì)結(jié)果說:賈府的人很愛“笑”
完成分詞以后,詞頻統(tǒng)計(jì)就非常簡單了。我們只需要根據(jù)分詞結(jié)果把片段切分開,去掉長度為一的片段(也就是單字),然后數(shù)一下每一種片段的個(gè)數(shù)就可以了。
這是出現(xiàn)次數(shù)排名前20的單詞:
寶玉(3940)、笑道(2314)、鳳姐(1521)、什么(1432)、賈母(1308)、襲人(1144)、一個(gè)(1111)、黛玉(1102)、我們(1068)、王夫人(1059)、如今(1016)、寶釵(1014)、聽了(938)、出來(934)、老太太(908)、你們(890)、去了(879)、怎么(867)、太太(856)、姑娘(856)
通過分詞后的詞頻,我們發(fā)現(xiàn)《紅樓夢》中的人物戲份由多到少依次是寶玉、鳳姐、賈母、襲人、黛玉、王夫人和寶釵。然而,這個(gè)排名是有問題的,因?yàn)?rdquo;林黛玉”這個(gè)詞的出現(xiàn)次數(shù)還有267次,需要加到黛玉的戲份里,所以其實(shí)黛玉的戲份比襲人多。
同理,“老太太”一般是指賈母,所以賈母的戲份加起來應(yīng)該比鳳姐多。正確的排名應(yīng)該是寶玉、賈母、鳳姐、黛玉、襲人、王夫人和寶釵。
此外,我們還發(fā)現(xiàn)《紅樓夢》中的人物很愛笑,因?yàn)槌巳嗣酝獬霈F(xiàn)次數(shù)最多的單詞就是“笑道” : )
我把完整的詞頻表做成了一個(gè)網(wǎng)頁,感興趣的話可以去看一下:紅樓詞表。
終于做完了分詞,又離目標(biāo)靠近了一大步?,F(xiàn)在,我可以用之前看到的那篇文章里提到的PCA算法來分析章回之間的差異了。不過在此之前,我想先反思一下,到底應(yīng)該用哪些詞的詞頻來進(jìn)行分析?
在很多用PCA分析《紅樓夢》的博文里,大家都是用出現(xiàn)頻率最高的詞來分析的。然而問題是,萬一頻率最高的詞是和情節(jié)變化相關(guān)的呢?為了剔除情節(jié)變化的影響,我決定選出詞頻隨情節(jié)變化最小的單詞來作為每一章的特征。而我衡量詞頻變化的方法就是統(tǒng)計(jì)單詞在每一回的詞頻,然后計(jì)算標(biāo)準(zhǔn)方差。為了消除單詞的常用程度對(duì)標(biāo)準(zhǔn)方差的影響,我把標(biāo)準(zhǔn)方差除以該單詞在每一回的平均頻數(shù),得到修正后的方差,然后利用這個(gè)標(biāo)準(zhǔn)來篩選特征詞。
最終,我選擇了詞頻變化最小的50個(gè)詞作為特征,每個(gè)詞的修正后標(biāo)準(zhǔn)方差都小于0.85。理論上,有了特征之后,我們就可以比較各個(gè)章節(jié)的相似性了。然而問題是,現(xiàn)在我們有50個(gè)特征,也就是說現(xiàn)在的數(shù)據(jù)空間是 50 維的,這對(duì)于想象四維空間都難的人類來說是很難可視化的。對(duì)于高維數(shù)據(jù)的可視化問題來說,PCA是一個(gè)很好用的數(shù)學(xué)工具。
我利用PCA,把五十個(gè)詞的詞頻所構(gòu)成的五十個(gè)維度壓縮到二維平面上。把壓縮后的數(shù)據(jù)點(diǎn)畫出來,發(fā)現(xiàn)是這個(gè)樣子:
(圖片說明:圖中每個(gè)圓圈代表一個(gè)回目。圓圈內(nèi)是回目編號(hào),從 1 開始計(jì)數(shù)。紅色圓圈是 1-40 回,綠色圓圈是 41-80回,藍(lán)色圓圈是 81-120 回。)
八十回以后的內(nèi)容(藍(lán)色)大部分都集中在左下角的一條狹長的區(qū)域內(nèi),很明顯地和其他章回區(qū)分開來了!莫非《紅樓夢》的最后 40 回真的不是同一個(gè)作者寫的?!
別著急,分析還沒結(jié)束。PCA的一個(gè)很重要的優(yōu)點(diǎn)就是,它的分析結(jié)果具有很強(qiáng)的可解釋性,因?yàn)槲覀兛梢灾烂恳粋€(gè)原始特征在壓縮后的特征中的權(quán)重。從上圖中可以看到,后40回的主要區(qū)別在于成分二(component 2)的數(shù)值。因此我們可以看一看每一個(gè)詞的詞頻在成分2中的權(quán)重排名(括號(hào)內(nèi)為權(quán)重):
笑道(0.883)、我們(0.141)、一個(gè)(0.133)、你們(0.128)、兩個(gè)(0.113)、說著(0.079)、咱們(0.076)、這個(gè)(0.063)、聽了(0.052)、還有(0.046)、一面(0.045)、來了(0.037)、都是(0.032)、不過(0.028)、去了(0.027)、又不(0.025)、出去(0.021)、這樣(0.018)、如今(0.016)、這里(0.016)、還不(0.011)、見他(0.011)、出來(0.010)、就是(0.010)、一時(shí)(0.008)、起來(0.005)、只見(0.002)、不是(0.002)、下回分解(0.000)、不得(-0.001)、也不(-0.001)、話說(-0.002)、的人(-0.005)、不知(-0.007)、那里(-0.009)、叫他(-0.011)、不敢(-0.011)、自己(-0.011)、不能(-0.017)、什么(-0.019)、所以(-0.020)、只是(-0.023)、知道(-0.026)、進(jìn)來(-0.036)、說道(-0.046)、怎么(-0.050)、只得(-0.056)、沒有(-0.077)、聽見(-0.092)、寶玉(-0.312)
我發(fā)現(xiàn),“笑道”這個(gè)詞不僅是除了人名以外出現(xiàn)次數(shù)最多的單詞,而且在PCA結(jié)果中的權(quán)重也異常地高(0.88),甚至超過了“寶玉”的權(quán)重的絕對(duì)值(0.31)!為了搞明白這個(gè)詞為什么有這么大的權(quán)重,我把“笑道”的詞頻變化畫了出來:
(圖片說明:圖中橫坐標(biāo)是章回編號(hào),縱坐標(biāo)是“笑道”的詞頻)
可以發(fā)現(xiàn),“笑道”的詞頻是先增加再減少的,這不禁讓我聯(lián)想到了賈府興衰的過程。莫非“笑道”的詞頻和賈府的發(fā)展?fàn)顩r有關(guān)?
有趣的是,“笑道”的詞頻頂峰出現(xiàn)在第50回左右,而有些人從劇情的角度分析認(rèn)為賈府的鼎盛時(shí)期開始于第48、49回,恰好重合。
也許“笑道”這一看似平常的詞匯確實(shí)側(cè)面反應(yīng)了賈府的興衰史呢。雖然因果關(guān)系有待考證,不過想想也有一點(diǎn)道理,畢竟只有日子過的好的時(shí)候人們才會(huì)愛笑。
“笑道”這個(gè)詞似乎和情節(jié)的關(guān)系比較大,并且嚴(yán)重影響到了我們的分析。此外,“寶玉”作為一個(gè)人名,它的權(quán)重的絕對(duì)值也比較大,也可能是受到了情節(jié)的影響。因此,我決定把這兩個(gè)詞“拉黑”,用剩下的48個(gè)詞的詞頻做特征,再次進(jìn)行PCA分析。
我發(fā)現(xiàn)這樣修改特征之后,后40回確實(shí)已經(jīng)不像之前那么聚集了,不過還是可以看出一點(diǎn)聚集的趨勢。這說明之前PCA結(jié)果確實(shí)因?yàn)?ldquo;笑道”而受到了劇情的干擾。
而去掉“笑道以后四十回依然有聚集的趨勢,說明去掉干擾后這些章回還是有一定的相似性的。所以,我有點(diǎn)把握認(rèn)為《紅樓夢》前八十回和后四十回的用詞是有一些差異的。不過因?yàn)殡y以完全排除劇情的影響,所以我也還不敢下定論。
雖然沒有完全解決紅樓夢的作者是不是同一個(gè)人的問題,不過這個(gè)過程中誤打誤撞產(chǎn)生的發(fā)現(xiàn)也是挺有意思的,比如“笑道”的詞頻變化和賈府興衰史的有趣重合。更重要的是,看似枯燥的數(shù)學(xué)公式可以做出這些好玩的分析。