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