使用神經網路生成一段宋史列傳

起因和目標

作為一個歷史愛好者(如果算得上的話),一直想可以用機器學習的相關知識在歷史方面做些有意思的事情。最近在讀苗書梅老師的《宋代官員選任與管理制度》並參閱宋史的人物列傳時,突然想到是否可以搞一個神經網路模型,模仿宋史人物傳記,自動生成一篇風格相似的文字。

其實這在自然語言處理領域不是什麼新鮮的問題了,但恐怕還沒有人將其用在除詩歌外的古文上——畢竟,在國際會議和期刊上發論文,用中文古詩恐怕就已經很小眾了,再搞出一個人工智慧領域的國外大佬們從未聽說過的「中國古代紀傳體史書訓練集」,恐怕是把自己的論文往死了朝reject上逼吧。當然我並不是搞自然語言處理的,甚至連搞機器學習的都算不上,因此只能憑著自己對一些模型一點點的粗淺理解嘗試這一任務。

那麼為什麼選擇宋史列傳作為目標風格的文字呢?主要是因為拜入宋以來統治者極力推行的以文治國和周密的官員除授和遷轉制度所賜,宋史中很多人物,除宗室后妃、開國勛貴和武人內侍外,大多數都是下面這個畫風:

王堯臣,字伯庸,應天府虞城人。舉進士第一,授將作監丞、通判湖州……

掌禹錫,字唐卿,許州郾城人。中進士第,為道州司理參軍……

包拯,字希仁,廬州合肥人也。始舉進士,除大理評事,出知建昌縣……

豐稷,字相之,明州鄞人。登第,為谷城令……

也就是列出名字、表字、籍貫、出身,初授何官,本官和差遣分別是什麼——雖然後面一般各有差異,但前面大體形式相似。當然許多人,尤其是許多名臣不是這個畫風,比如司馬光開頭主要說他愛讀《左氏春秋》和砸缸的故事,范仲淹說的是他小時候生活困難隨母改嫁的故事。不過從比例上講,還是上面這種格式佔大多數。這就讓我們看到找出一個特定模式的可能。

當然,回到我們最初的目標,想生成一篇完全合乎語法的傳記,憑當前的技術水平是不太現實的。我們不妨把目標降低為以下幾條:

  • 生成的文字不需要很長,太長對於任何模型的訓練效率都會是一個難題,而且生成文字的有效信息也不會較短文更多。我們希望生成結果達到宋史中每個人列傳第一段的平均字數即可(經統計約為70字);
  • 生成文字一般是「某某某,字某某,某某某某人。中進士第,除某某官」這樣的格式,但不必嚴格遵守;
  • 由於我們不使用任何分詞方法,因而希望生成的文字中包含一些「合乎情理」的自創詞語,最典型的是一些官名、地名(如某州某縣)。

直觀上,上面這些特徵應該可以通過現在已有的模型捕獲到。

明確了我們的目標,下面就應該提取數據和選擇合適的模型了。

數據提取

宋史列傳共有255篇,除去開頭的后妃、宗室,以及最後的世家、外國、蠻夷外,大部分皆為入仕官員的生平記載,且多為數人一傳。許多宰執名臣往往長篇累牘地介紹其生平言行甚至奏疏內容,而許多人僅僅介紹其歷任官職和品行良莠,因而長度差距很大。

在進行數據處理時,我們以表字為信息,共抽取出1809人傳記的第一段,並隨機選取1500人作為訓練集,其餘作為驗證集。抽取的數據中共有3604字(包括標點符號),作為字典。這裡我們並未區分宗室后妃與一般官員,也並沒有區分文字和標點,並且將長度的處理推遲到將數據輸入模型訓練時(丟棄小於70的樣例,並將超過70的樣例截斷為70)。從某種角度講,這樣也可以為我們的模型引入一定的噪音,藉此觀察模型本身的魯棒性。

使用RNN的嘗試

循環神經網路(Recurrent Neural Network, RNN for short)作為一種通過當前輸入和狀態確定輸出的神經網路,除了可以用於一般的分類任務外,最著名功能恐怕就是用於生成各種類型的時序數據了。尤其是Alex Graves 2013年那篇論文中給出的LSTM單元生成手寫體文字的例子,展示了在各種生成時序數據任務的場景中使用RNN的可能。

這裡我們採用狀態和輸出維度均為32的LSTM單元,並在LSTM單元的輸出上增加一層線性層和softmax,最終生成在數據集字典的3604個字元上的概率分布,與樣本給出下一單詞的one hot 表示計算交叉熵作為損失函數。訓練過程中通過計算在驗證集上的損失函數觀察演算法進行情況。

模型的其他方面則先後經過了一些調整:最開始輸入為當前字元的one hot 表示,使用的訓練演算法為簡單的梯度下降。實驗發現這樣的設定使得無話怎麼調整learning rate和batch size,損失函數的減小都很緩慢;嘗試在輸出層增加了一個隱層,甚至把梯度搞成零了(使用LSTM還遇到這樣的事,實在是無比尷尬)。

最終的版本是將輸入改成了可訓練的embedding,並將訓練演算法換成了Adam,這樣才看到損失函數開始比較大幅度地降低:

梯度下降和Adam在驗證集上的損失函數承受演算法進行的比較。可以看到三種不同learning rate的梯度下降均不如Adam演算法——learning rate = 1.0時的梯度下降的速度遠沒有Adam快,且其抖動程度已經不忍直視;更大的learning rate完全無法收斂。

由此也可以看出,雖然目前對神經網路各種基於梯度的優化方法在實際應用中的效果還有一些不同觀點,但就這個例子而言,Adam演算法確實要優於簡單的梯度下降。

下面給出了RNN模型輸出的五條結果,其中粗體部分為是和我們最初的目標一致的內容:

1. 興德之字叔公,餘杭州都人知贛州教授。丁沉邑數冥儀。父士圃,以慶喻簿。四年,試至京西轉運判官,出歷平州南、差令,調清陽軍節度判官,召試秘書

2. 國自彥,字鎮規,從石守虞熹。滑豐華,保陸運軍判官鹽鐵判官丞通判密縣熙寧七年,通學一自為文書歸。淳熙十人。齊兕以環蔭聞歸時,而前秘書

3. 劉驤,字履舉,務大寧人。見,慷慨自嚴於子。再二年進士,校部員外郎邪挑。從夷祖繁課中,改勇之,時其世之曰:「康守;崇射曰:「庄殺矩,戰死火,民

4. 婺舜眉,字鴻直,吳相蒲安人擢秘閣校理赴官。時以杲有近學,謂以心疾正為當蔭補上儒聞,恢以家憂,轉集叛,鄭江即陟,復遣讀書賦,撰親賊,以

5. 少格夫,字齊發,幽州成人。權故千節以輸之論矢行蜀,死留老黃、雍丘殿直太學,李宗曰:「至膳部「權學帷,周師次充,舅氏所貴都下、輯醫。侍留履

可以看出,我們最開始的幾點目標基本上達到了:

  • 幾乎每一條都是「某某某,字某某,某某某某人」的結構,第一條還記住了部分人物列傳中在表字前未加逗號的格式
  • 出現了許多官職,其中既有實際存在的官職(如秘閣校理),還有許多隨機生成的虛構官職(如校部員外郎、清陽軍節度判官),也有清陽軍這樣的虛構地名;其中一些除授、出任官職的動詞,如知、擢、試、通判也被」理解「出來。有意思的是「雍丘殿直太學」這個聽起來像館閣一樣的職位,似乎將太學設定成了和學士同一層次概念的官位;而「鹽鐵判官丞」聽起來像是鹽鐵判官的副手。
  • 另外,一些其它的模式,如曰字後接冒號和雙引號也被學習出來。

SeqGAN的嘗試

說起生成模型,不能不提近幾年流行的生成對抗網路(Generative Adversarial Network, GAN for short)。儘管最初的GAN是為生成連續變數(如圖片像素的色彩空間)設計的,但近幾年也有不少關於如何用GAN生成文字序列這類離散變數的工作。其中去年AAAI 的一篇SeqGAN,採用了將增強學習(Reinforcement Learning, RL for short)和GAN結合起來的方法,在人工和實際的數據集上都取得了較好的結果。

SeqGAN里的Seq意指其生成器採用RNN網路;而其判別器採用CNN網路。這項工作的主要貢獻之一是,解決了原始的GAN的損失函數在生成離散變數的情形下很難用於生成器部分更新的問題,其方法是將這一部分替換為RL中的gradient policy更新。另一方面,SeqGAN還將判別器對整個序列(可以是不完整序列通過Monte Carlo搜索後的結果)的reward傳回到生成器,從而在一定程度上將不完成序列考慮到整個損失函數中。

由於SeqGAN公開了自己的代碼,因而使用SeqGAN就比自己從頭擼一個RNN還要簡單多了。直接用其代碼中的data loader 讀取已經抓取的數據即可。此外,由於SeqGAN代碼中使用了自己定義的一個oracle LSTM作為ground truth,並通過將生成器的輸出傳入這個LSTM,將得到的損失函數值用作監視訓練過程的指標;而使用真實數據作為輸入時,我們只能採用一種對稱的方法,即使用已有數據作為我們生成器的輸入,將得到的生成器的損失函數值(並非GAN中的損失函數,只是生成器的交叉熵)作為監視變數。SeqGAN在兩個地方用這個指標監視訓練過程:一是生成器的預訓練過程,二是整個GAN的訓練過程。在訓練的時候發現這個指標是不降反升的,很令人廢解,有哪位大神了解原因的話可以指點一下。

具體訓練過程的損失函數變化就不列出了,直接上結果:

1. 李繼鄴字明遠,尚熙寧中,不使。高進士第。熙寧中,嘉與漢、廬州。登進士第一,後直集賢院。端右司,京西後。生錢父喪。初,無濫意,父文以進。父佐

2. 楊機,字希老,得之弟子而異。漢乾道振第。歷校書員外郎,入太廟,京師子宮。奏十敗,而有焉。進士,調衛州。慶元路,抗,坐曰:「金人。父宣之,蔭軍

3. 王信,字益甫,敘丘人。進士及第,以右拾遺、集賢院,歷衛州刺史,京師二寧王密者,王斯不得。林尚書,每偉田,府稱為蘭之勢。今諸功郎,至庄親薦、江

4. 沈載,字晦老,洛陽人。彥字子,湖州人。第進士,累遷知安撫,恩西京西召文彥博、夔萬人。登第一為。真士第一。中進元年進士,調中陽軍判官,累遷右

5. 林之字,為萬人。父處讓,《周逸易》,歷吏部員外郎。周寬融,歷官軍事、並字師密,通判漢陽。入漢州,歷楊定海丞,益憲薦以貲明,而學進士,自天志獨

第一眼看上去結果和單獨使用LSTM的結果差不太多——名字、表字、籍貫和出身的格式,已有官職和炮製出的官職也都似模似樣。但仔細觀察可以看出GAN生成的結果更偏向於短句,因而語義也更清晰。

計算了一下兩個模型輸出的1000個樣例在驗證集上BLEU-1(之所以選擇BLEU-1是考慮古漢語很多單字就有完整的含義),LSTM為0.130,SeqGAN為0.167,可見還是SeqGAN要略好一些。

結尾

其實也沒什麼好總結的,非要說點兒什麼的話,就是這次直觀地感受到了循環神經網路在文字生成中(至少)可以做到哪一步,以及訓練神經網路上嘗試不同優化方法的重要性。不知道是否有相關的研究人員從事古漢語自動生成的工作(孫茂松教授是做古詩自動生成的,但不知道是否有做過其他方面古漢語的生成工作),恐怕這方面是交叉中的交叉,冷門中的冷門了。


推薦閱讀:

章惇書絕壁的故事出自哪裡?
洛陽開封哪個古迹保護好一點?
司馬光退還西夏土地,到底發生過沒有,以及如果是真實發生的,到底是哪些原因導致了宋朝做出了這個政治決定?
兩宋貨幣稅的統治術
存在不增加賦稅但增加政府收入的辦法嗎?

TAG:宋史 | 機器學習 | 深度學習DeepLearning |