這篇文章不涉及RNN的基本原理,只是從選擇數(shù)據(jù)集開始,到最后生成文本,展示一個RNN使用實例的過程。
對于深度學(xué)習(xí)的應(yīng)用者,最應(yīng)該關(guān)注的除了算法和模型,還應(yīng)該關(guān)注如何預(yù)處理好自己的數(shù)據(jù),合理降噪,以及如何在數(shù)據(jù)量不同的情況下選擇合理的超參,來達到最理想的訓(xùn)練結(jié)果。
在經(jīng)過近三個月的學(xué)習(xí)之后,我打算使用Tensorflow,創(chuàng)建一個LSTM RNN模型,使用中文小說作為數(shù)據(jù)源,訓(xùn)練RNN來生成中文小說文本。平時做練習(xí)訓(xùn)練數(shù)據(jù)都是英文,我想看看換成中文之后會是什么結(jié)果,能不能寫出一些語義通順的句子。
數(shù)據(jù)選取的是起點中文網(wǎng)的獲獎歷史小說『寒門首輔』,作者一袖乾坤。
整個notebook在我的github上,感興趣的同學(xué)可以下載使用,不斷嘗試訓(xùn)練自己喜歡的風(fēng)格的小說生成器。強烈建議大家使用notebook進行嘗試,可以實時看到每一步的輸出,學(xué)習(xí)效果更好。
以下就是整個應(yīng)用過程。
來試試用起點中文網(wǎng)的歷史小說『寒門首輔(一袖乾坤 著)』來做訓(xùn)練數(shù)據(jù),看看這個RNN網(wǎng)絡(luò)能產(chǎn)生一些什么樣子的文本。 嘗試過程中必遇到問題,也借此加深一些對RNN的理解。
首先我從網(wǎng)上下載到了『寒門首輔』的txt版本,打開時候發(fā)現(xiàn)有很多空行,還包含了很多不必要的鏈接,看起來是這樣的。
預(yù)處理一下數(shù)據(jù)。
import helper
讀入數(shù)據(jù)
dir = './data/寒門首輔.txt'
text = helper.load_text(dir)
設(shè)置一下要用多少個字來訓(xùn)練,方便調(diào)試。這里先用100000字進行訓(xùn)練
num_words_for_training = 100000
text = text[:num_words_for_training]
看看有多少行
lines_of_text = text.split('\n')
print(len(lines_of_text))
Out: 4329
先看看前15行是什么內(nèi)容
print(lines_of_text[:15])
Out: ['《寒門首輔》', '作者:一袖乾坤', '', '內(nèi)容簡介: 弘治五年,四海靖平。徐溥春風(fēng)得意當(dāng)了一朝首輔,李東陽初出茅廬做了會試考官。劉健熬成了文淵閣大學(xué)士,謝遷尚未入閣成就賢相美名。楊廷和奉旨參修《憲宗實錄》,劉大夏一把火燒了《鄭和海圖》。王陽明抱著書本埋頭苦讀準(zhǔn)備著即將到來的鄉(xiāng)試,弘治皇帝與張皇后悠然自得的逗弄著繞膝玩耍的萌娃正德...... 群賢畢至,少長咸集。在這個大師云集,名臣輩出的美好時代,春風(fēng)迷醉的余姚城里出身貧寒的穿越少年謝慎登高遠望,心中已經(jīng)埋下了夢想。 誰言寒門再難出貴子,我便要入一入內(nèi)閣,做一做首輔,提兩壺美酒,擁一方佳人。 世人有云:謝閣老一只禿筆安社稷,一張薄紙定乾坤。無人不知謝文正,無人不曉謝余姚...... ', '寒門首輔txt下載:http://www.80txt.com/txtxz/63028/down.html', '寒門首輔最新章節(jié):http://www.80txt.com/txtxz/63028/down.html', '================================', '免責(zé)聲明:寒門首輔txt全集下載由80TXT電子書(WwW.80txt.com)書友收集整理自網(wǎng)絡(luò),版權(quán)歸原作者所有,僅作學(xué)習(xí)交流使用,不可用于任何商業(yè)途徑,如非免費資源,請在試用之后24小時內(nèi)立即刪除,如果喜歡該資源請購買正版謝謝合作;如不慎該資源侵犯了您的權(quán)利,請麻煩通知我及時刪除,謝謝!', '================================', 'A:【更多精彩好書,更多原創(chuàng)TXT手機電子書,我們因你而專業(yè),TXT格式電子書下載 請登陸 80TXT電子書 --www.80txt.com】', 'B: 如果發(fā)現(xiàn)未完....登錄(80TXT小說網(wǎng))www.80txt.com 提供24小時循環(huán)更新!!', '', '', '', '章節(jié)目錄 第一章 積善之家']
把『章節(jié)目錄』之前的行全部砍掉,一大堆沒用的東西。
lines_of_text = lines_of_text[14:]
再來看看,第一行應(yīng)該就進入正題了。
print(lines_of_text[:5])
Out: ['章節(jié)目錄 第一章 積善之家', '', ' 弘治五年,春和景明。[求書網(wǎng)qiushu.cc更新快,網(wǎng)站頁面清爽,廣告少,無彈窗,最喜歡這種網(wǎng)站了,一定要好評]', '', ' 浙江承宣布政使司,紹興府,余姚縣。']
我查看了一下,這個小說一共有129萬字左右。
先把空行去掉吧。去掉空行之后應(yīng)該就只有一半左右的行數(shù)了。
lines_of_text = [lines for lines in lines_of_text if len(lines) > 0]
print(len(lines_of_text))
Out: 2158
打印前20行看看什么情況
print(lines_of_text[:20])
Out: ['章節(jié)目錄 第一章 積善之家', ' 弘治五年,春和景明。[求書網(wǎng)qiushu.cc更新快,網(wǎng)站頁面清爽,廣告少,無彈窗,最喜歡這種網(wǎng)站了,一定要好評]', ' 浙江承宣布政使司,紹興府,余姚縣。', ' 縣城里靠近城隍廟的一處小巷口,一個年約十二,身著淡藍色粗布長衫,頭戴黑色幞頭的少年望著不遠處熙熙攘攘的人群不發(fā)一言。', ' 他叫謝慎,是土生土長的余姚人。但是也不盡然,因為他的靈魂來自后世,是穿越而來奪舍附著在這個與他同名同姓的少年身上。事情的經(jīng)過他并不是很清楚,只知道這個少年應(yīng)該是不慎落水,被人救上后就一直昏迷不醒,奄奄一息,直到自己穿越才鳩占鵲巢,成了這具身體的主人。', ' 不過有些奇特的是,謝慎還兼有原先身體里的一部分記憶,他拼命檢索這才對身處的環(huán)境有了一個大概的認識。', ' 如今是弘治五年,當(dāng)今天子朱祐樘立志中興,招賢納士,敕令各省提學(xué)官巡視省內(nèi)各府、州、縣學(xué),選拔參加鄉(xiāng)試的人選。今年是鄉(xiāng)試之年,明年則是會試,殿試連著兩場大試,是出進士的年份。朝為田舍郎,暮登天子堂,在明朝讀書自然是最有前途的事情。不過這似乎和他沒有什么關(guān)系,雖然原先的謝慎也算是個讀書人,可卻并沒有功名在身,最多只能算一個半吊子童生。', ' 謝慎前世可是苦修明史的研究生,雖然主攻方向是嘉靖萬歷兩朝,但對弘治朝多少也有些了解。浙江一直是科舉強省,在弘治朝也是絕對的霸主,而紹興府則是浙江省中出進士最多的府。mianhuatang.cc [棉花糖小說網(wǎng)]若要落在縣一級,那魁首非余姚莫屬。山陰,會稽兩縣加在一起,所中的進士數(shù)目都不比余姚的多。這么說來,余姚絕對是科舉的死亡之組了。王陽明,謝遷,王華,一想到余姚出的那些名人,謝慎便免不了自嘲。', ' 謝慎雖然前世苦修明史,對八股文也有所研究,但要讓他在明代科場競爭最恐怖的余姚縣脫穎而出,確實有些艱難。', ' 可當(dāng)他垂下頭看了看自己瘦弱的身體,不免搖了搖頭,長嘆了一聲。', ' 不科舉還能干什么呢,就他這副竹竿子般的身體,肩不能抗,手不能提,殺雞沒準(zhǔn)還被雞調(diào)戲......', ' 何況在這個萬般皆下品惟有讀書高的年代,不科舉就只能為民,不管是務(wù)農(nóng)還是經(jīng)商終歸都是被官府壓著,沒有出頭的機會。大明朝那個著名的沈萬三不就是最好的例子,家財萬貫富可敵國還不是隨意一個罪名就成了別人茶余飯后唏噓慨嘆的談資?', ' 讀書,還是得讀書,便前方是刀山火海硬著頭皮也得上。他才十二歲,還有可塑性......', ' “小郎,你怎么在這兒呢,快快隨我回家去。你落水后被救起身子本就虛弱,若是在此時再染上了風(fēng)寒,那可了不得。你嫂嫂給你特地煮了雞湯,你多喝上幾碗也好補補身子。”', ' 一個中年男子的聲音打斷了謝慎的沉思,他抬了抬頭見來人是自己的胞兄謝方,心中不由得泛起一抹暖意。', ' 謝家門丁不旺,到了他們這一代只有他和謝方二人,不過這個便宜大哥似乎對他還不錯,吃穿用度上絕不短著缺著。', ' 謝方在余姚縣城里開著一家茶鋪,將每年收上的茶葉運到縣城售賣,質(zhì)地更好的一些會有人收走在府城售賣。', ' 明代浙江紹興府各縣幾乎都種植茶葉。最著名的要數(shù)杭州府錢塘縣的龍井茶以及寧波府象山縣的珠山茶。至于余姚雖然也種植茶樹,但其出產(chǎn)的茶葉并不像其種植棉花所產(chǎn)的“浙花”那么出名。', ' 不過似乎余姚本地的百姓很喜歡這種清淡的姚江茶,其在本縣的銷路并不差。', ' 靠著辛勤的工作,謝方也算撐起了一個家。在謝方的一再堅持下,謝慎從小便被送去開蒙,如今已經(jīng)學(xué)了六年了。不過原先的謝慎最多只能算是中上之資,跟神童絕對沾不上邊,照著原有軌跡發(fā)展下去,能不能取得秀才功名都是一個問題。']
下一步,把每行里面的『空格』,『[]里的內(nèi)容』,『<>里的內(nèi)容』都去掉。
# 去掉每行首尾空格
lines_of_text = [lines.strip() for lines in lines_of_text]
看下情況如何,打印前20句話。
print(lines_of_text[:20])
Out: ['章節(jié)目錄 第一章 積善之家', '弘治五年,春和景明。[求書網(wǎng)qiushu.cc更新快,網(wǎng)站頁面清爽,廣告少,無彈窗,最喜歡這種網(wǎng)站了,一定要好評]', '浙江承宣布政使司,紹興府,余姚縣。', '縣城里靠近城隍廟的一處小巷口,一個年約十二,身著淡藍色粗布長衫,頭戴黑色幞頭的少年望著不遠處熙熙攘攘的人群不發(fā)一言。', '他叫謝慎,是土生土長的余姚人。但是也不盡然,因為他的靈魂來自后世,是穿越而來奪舍附著在這個與他同名同姓的少年身上。事情的經(jīng)過他并不是很清楚,只知道這個少年應(yīng)該是不慎落水,被人救上后就一直昏迷不醒,奄奄一息,直到自己穿越才鳩占鵲巢,成了這具身體的主人。', '不過有些奇特的是,謝慎還兼有原先身體里的一部分記憶,他拼命檢索這才對身處的環(huán)境有了一個大概的認識。', '如今是弘治五年,當(dāng)今天子朱祐樘立志中興,招賢納士,敕令各省提學(xué)官巡視省內(nèi)各府、州、縣學(xué),選拔參加鄉(xiāng)試的人選。今年是鄉(xiāng)試之年,明年則是會試,殿試連著兩場大試,是出進士的年份。朝為田舍郎,暮登天子堂,在明朝讀書自然是最有前途的事情。不過這似乎和他沒有什么關(guān)系,雖然原先的謝慎也算是個讀書人,可卻并沒有功名在身,最多只能算一個半吊子童生。', '謝慎前世可是苦修明史的研究生,雖然主攻方向是嘉靖萬歷兩朝,但對弘治朝多少也有些了解。浙江一直是科舉強省,在弘治朝也是絕對的霸主,而紹興府則是浙江省中出進士最多的府。mianhuatang.cc [棉花糖小說網(wǎng)]若要落在縣一級,那魁首非余姚莫屬。山陰,會稽兩縣加在一起,所中的進士數(shù)目都不比余姚的多。這么說來,余姚絕對是科舉的死亡之組了。王陽明,謝遷,王華,一想到余姚出的那些名人,謝慎便免不了自嘲。', '謝慎雖然前世苦修明史,對八股文也有所研究,但要讓他在明代科場競爭最恐怖的余姚縣脫穎而出,確實有些艱難。', '可當(dāng)他垂下頭看了看自己瘦弱的身體,不免搖了搖頭,長嘆了一聲。', '不科舉還能干什么呢,就他這副竹竿子般的身體,肩不能抗,手不能提,殺雞沒準(zhǔn)還被雞調(diào)戲......', '何況在這個萬般皆下品惟有讀書高的年代,不科舉就只能為民,不管是務(wù)農(nóng)還是經(jīng)商終歸都是被官府壓著,沒有出頭的機會。大明朝那個著名的沈萬三不就是最好的例子,家財萬貫富可敵國還不是隨意一個罪名就成了別人茶余飯后唏噓慨嘆的談資?', '讀書,還是得讀書,便前方是刀山火海硬著頭皮也得上。他才十二歲,還有可塑性......', '“小郎,你怎么在這兒呢,快快隨我回家去。你落水后被救起身子本就虛弱,若是在此時再染上了風(fēng)寒,那可了不得。你嫂嫂給你特地煮了雞湯,你多喝上幾碗也好補補身子。”', '一個中年男子的聲音打斷了謝慎的沉思,他抬了抬頭見來人是自己的胞兄謝方,心中不由得泛起一抹暖意。', '謝家門丁不旺,到了他們這一代只有他和謝方二人,不過這個便宜大哥似乎對他還不錯,吃穿用度上絕不短著缺著。', '謝方在余姚縣城里開著一家茶鋪,將每年收上的茶葉運到縣城售賣,質(zhì)地更好的一些會有人收走在府城售賣。', '明代浙江紹興府各縣幾乎都種植茶葉。最著名的要數(shù)杭州府錢塘縣的龍井茶以及寧波府象山縣的珠山茶。至于余姚雖然也種植茶樹,但其出產(chǎn)的茶葉并不像其種植棉花所產(chǎn)的“浙花”那么出名。', '不過似乎余姚本地的百姓很喜歡這種清淡的姚江茶,其在本縣的銷路并不差。', '靠著辛勤的工作,謝方也算撐起了一個家。在謝方的一再堅持下,謝慎從小便被送去開蒙,如今已經(jīng)學(xué)了六年了。不過原先的謝慎最多只能算是中上之資,跟神童絕對沾不上邊,照著原有軌跡發(fā)展下去,能不能取得秀才功名都是一個問題。']
可以看到空格都沒了。下一步用正則去掉『[]』和『<>』中的內(nèi)容,像上面的什么『[棉花糖小說網(wǎng)]』這些的,后面還有一些是包含在『<>』里的,一并去掉。
import re
# 生成一個正則,負責(zé)找『[]』包含的內(nèi)容
pattern = re.compile(r'\[.*\]')
# 將所有指定內(nèi)容替換成空
lines_of_text = [pattern.sub("", lines) for lines in lines_of_text]
打印看效果
print(lines_of_text[:20])
Out: ['章節(jié)目錄 第一章 積善之家', '弘治五年,春和景明。', '浙江承宣布政使司,紹興府,余姚縣。', '縣城里靠近城隍廟的一處小巷口,一個年約十二,身著淡藍色粗布長衫,頭戴黑色幞頭的少年望著不遠處熙熙攘攘的人群不發(fā)一言。', '他叫謝慎,是土生土長的余姚人。但是也不盡然,因為他的靈魂來自后世,是穿越而來奪舍附著在這個與他同名同姓的少年身上。事情的經(jīng)過他并不是很清楚,只知道這個少年應(yīng)該是不慎落水,被人救上后就一直昏迷不醒,奄奄一息,直到自己穿越才鳩占鵲巢,成了這具身體的主人。', '不過有些奇特的是,謝慎還兼有原先身體里的一部分記憶,他拼命檢索這才對身處的環(huán)境有了一個大概的認識。', '如今是弘治五年,當(dāng)今天子朱祐樘立志中興,招賢納士,敕令各省提學(xué)官巡視省內(nèi)各府、州、縣學(xué),選拔參加鄉(xiāng)試的人選。今年是鄉(xiāng)試之年,明年則是會試,殿試連著兩場大試,是出進士的年份。朝為田舍郎,暮登天子堂,在明朝讀書自然是最有前途的事情。不過這似乎和他沒有什么關(guān)系,雖然原先的謝慎也算是個讀書人,可卻并沒有功名在身,最多只能算一個半吊子童生。', '謝慎前世可是苦修明史的研究生,雖然主攻方向是嘉靖萬歷兩朝,但對弘治朝多少也有些了解。浙江一直是科舉強省,在弘治朝也是絕對的霸主,而紹興府則是浙江省中出進士最多的府。mianhuatang.cc 若要落在縣一級,那魁首非余姚莫屬。山陰,會稽兩縣加在一起,所中的進士數(shù)目都不比余姚的多。這么說來,余姚絕對是科舉的死亡之組了。王陽明,謝遷,王華,一想到余姚出的那些名人,謝慎便免不了自嘲。', '謝慎雖然前世苦修明史,對八股文也有所研究,但要讓他在明代科場競爭最恐怖的余姚縣脫穎而出,確實有些艱難。', '可當(dāng)他垂下頭看了看自己瘦弱的身體,不免搖了搖頭,長嘆了一聲。', '不科舉還能干什么呢,就他這副竹竿子般的身體,肩不能抗,手不能提,殺雞沒準(zhǔn)還被雞調(diào)戲......', '何況在這個萬般皆下品惟有讀書高的年代,不科舉就只能為民,不管是務(wù)農(nóng)還是經(jīng)商終歸都是被官府壓著,沒有出頭的機會。大明朝那個著名的沈萬三不就是最好的例子,家財萬貫富可敵國還不是隨意一個罪名就成了別人茶余飯后唏噓慨嘆的談資?', '讀書,還是得讀書,便前方是刀山火海硬著頭皮也得上。他才十二歲,還有可塑性......', '“小郎,你怎么在這兒呢,快快隨我回家去。你落水后被救起身子本就虛弱,若是在此時再染上了風(fēng)寒,那可了不得。你嫂嫂給你特地煮了雞湯,你多喝上幾碗也好補補身子。”', '一個中年男子的聲音打斷了謝慎的沉思,他抬了抬頭見來人是自己的胞兄謝方,心中不由得泛起一抹暖意。', '謝家門丁不旺,到了他們這一代只有他和謝方二人,不過這個便宜大哥似乎對他還不錯,吃穿用度上絕不短著缺著。', '謝方在余姚縣城里開著一家茶鋪,將每年收上的茶葉運到縣城售賣,質(zhì)地更好的一些會有人收走在府城售賣。', '明代浙江紹興府各縣幾乎都種植茶葉。最著名的要數(shù)杭州府錢塘縣的龍井茶以及寧波府象山縣的珠山茶。至于余姚雖然也種植茶樹,但其出產(chǎn)的茶葉并不像其種植棉花所產(chǎn)的“浙花”那么出名。', '不過似乎余姚本地的百姓很喜歡這種清淡的姚江茶,其在本縣的銷路并不差。', '靠著辛勤的工作,謝方也算撐起了一個家。在謝方的一再堅持下,謝慎從小便被送去開蒙,如今已經(jīng)學(xué)了六年了。不過原先的謝慎最多只能算是中上之資,跟神童絕對沾不上邊,照著原有軌跡發(fā)展下去,能不能取得秀才功名都是一個問題。']
『[]』的內(nèi)容已經(jīng)沒了。下一步去掉『<>』中的內(nèi)容,方法同上。
# 將上面的正則換成負責(zé)找『<>』包含的內(nèi)容
pattern = re.compile(r'<.*>')
# 將所有指定內(nèi)容替換成空
lines_of_text = [pattern.sub("", lines) for lines in lines_of_text]
下一步,把每句話最后的『......』換成『。』。
# 將上面的正則換成負責(zé)找『......』包含的內(nèi)容
pattern = re.compile(r'\.+')
# 將所有指定內(nèi)容替換成空
lines_of_text = [pattern.sub("。", lines) for lines in lines_of_text]
打印看效果
print(lines_of_text[:20])
Out: ['章節(jié)目錄 第一章 積善之家', '弘治五年,春和景明。', '浙江承宣布政使司,紹興府,余姚縣。', '縣城里靠近城隍廟的一處小巷口,一個年約十二,身著淡藍色粗布長衫,頭戴黑色幞頭的少年望著不遠處熙熙攘攘的人群不發(fā)一言。', '他叫謝慎,是土生土長的余姚人。但是也不盡然,因為他的靈魂來自后世,是穿越而來奪舍附著在這個與他同名同姓的少年身上。事情的經(jīng)過他并不是很清楚,只知道這個少年應(yīng)該是不慎落水,被人救上后就一直昏迷不醒,奄奄一息,直到自己穿越才鳩占鵲巢,成了這具身體的主人。', '不過有些奇特的是,謝慎還兼有原先身體里的一部分記憶,他拼命檢索這才對身處的環(huán)境有了一個大概的認識。', '如今是弘治五年,當(dāng)今天子朱祐樘立志中興,招賢納士,敕令各省提學(xué)官巡視省內(nèi)各府、州、縣學(xué),選拔參加鄉(xiāng)試的人選。今年是鄉(xiāng)試之年,明年則是會試,殿試連著兩場大試,是出進士的年份。朝為田舍郎,暮登天子堂,在明朝讀書自然是最有前途的事情。不過這似乎和他沒有什么關(guān)系,雖然原先的謝慎也算是個讀書人,可卻并沒有功名在身,最多只能算一個半吊子童生。', '謝慎前世可是苦修明史的研究生,雖然主攻方向是嘉靖萬歷兩朝,但對弘治朝多少也有些了解。浙江一直是科舉強省,在弘治朝也是絕對的霸主,而紹興府則是浙江省中出進士最多的府。mianhuatang。cc 若要落在縣一級,那魁首非余姚莫屬。山陰,會稽兩縣加在一起,所中的進士數(shù)目都不比余姚的多。這么說來,余姚絕對是科舉的死亡之組了。王陽明,謝遷,王華,一想到余姚出的那些名人,謝慎便免不了自嘲。', '謝慎雖然前世苦修明史,對八股文也有所研究,但要讓他在明代科場競爭最恐怖的余姚縣脫穎而出,確實有些艱難。', '可當(dāng)他垂下頭看了看自己瘦弱的身體,不免搖了搖頭,長嘆了一聲。', '不科舉還能干什么呢,就他這副竹竿子般的身體,肩不能抗,手不能提,殺雞沒準(zhǔn)還被雞調(diào)戲。', '何況在這個萬般皆下品惟有讀書高的年代,不科舉就只能為民,不管是務(wù)農(nóng)還是經(jīng)商終歸都是被官府壓著,沒有出頭的機會。大明朝那個著名的沈萬三不就是最好的例子,家財萬貫富可敵國還不是隨意一個罪名就成了別人茶余飯后唏噓慨嘆的談資?', '讀書,還是得讀書,便前方是刀山火海硬著頭皮也得上。他才十二歲,還有可塑性。', '“小郎,你怎么在這兒呢,快快隨我回家去。你落水后被救起身子本就虛弱,若是在此時再染上了風(fēng)寒,那可了不得。你嫂嫂給你特地煮了雞湯,你多喝上幾碗也好補補身子。”', '一個中年男子的聲音打斷了謝慎的沉思,他抬了抬頭見來人是自己的胞兄謝方,心中不由得泛起一抹暖意。', '謝家門丁不旺,到了他們這一代只有他和謝方二人,不過這個便宜大哥似乎對他還不錯,吃穿用度上絕不短著缺著。', '謝方在余姚縣城里開著一家茶鋪,將每年收上的茶葉運到縣城售賣,質(zhì)地更好的一些會有人收走在府城售賣。', '明代浙江紹興府各縣幾乎都種植茶葉。最著名的要數(shù)杭州府錢塘縣的龍井茶以及寧波府象山縣的珠山茶。至于余姚雖然也種植茶樹,但其出產(chǎn)的茶葉并不像其種植棉花所產(chǎn)的“浙花”那么出名。', '不過似乎余姚本地的百姓很喜歡這種清淡的姚江茶,其在本縣的銷路并不差。', '靠著辛勤的工作,謝方也算撐起了一個家。在謝方的一再堅持下,謝慎從小便被送去開蒙,如今已經(jīng)學(xué)了六年了。不過原先的謝慎最多只能算是中上之資,跟神童絕對沾不上邊,照著原有軌跡發(fā)展下去,能不能取得秀才功名都是一個問題。']
最后,還是把每句話里面包含的空格,都轉(zhuǎn)換成『,』,就像『章節(jié)目錄 第一章』,換成『章節(jié)目錄,第一章』,感覺這一步可有可無了。
# 將上面的正則換成負責(zé)找行中的空格
pattern = re.compile(r' +')
# 將所有指定內(nèi)容替換成空
lines_of_text = [pattern.sub(",", lines) for lines in lines_of_text]
print(lines_of_text[:20])
Out: ['章節(jié)目錄,第一章,積善之家', '弘治五年,春和景明。', '浙江承宣布政使司,紹興府,余姚縣。', '縣城里靠近城隍廟的一處小巷口,一個年約十二,身著淡藍色粗布長衫,頭戴黑色幞頭的少年望著不遠處熙熙攘攘的人群不發(fā)一言。', '他叫謝慎,是土生土長的余姚人。但是也不盡然,因為他的靈魂來自后世,是穿越而來奪舍附著在這個與他同名同姓的少年身上。事情的經(jīng)過他并不是很清楚,只知道這個少年應(yīng)該是不慎落水,被人救上后就一直昏迷不醒,奄奄一息,直到自己穿越才鳩占鵲巢,成了這具身體的主人。', '不過有些奇特的是,謝慎還兼有原先身體里的一部分記憶,他拼命檢索這才對身處的環(huán)境有了一個大概的認識。', '如今是弘治五年,當(dāng)今天子朱祐樘立志中興,招賢納士,敕令各省提學(xué)官巡視省內(nèi)各府、州、縣學(xué),選拔參加鄉(xiāng)試的人選。今年是鄉(xiāng)試之年,明年則是會試,殿試連著兩場大試,是出進士的年份。朝為田舍郎,暮登天子堂,在明朝讀書自然是最有前途的事情。不過這似乎和他沒有什么關(guān)系,雖然原先的謝慎也算是個讀書人,可卻并沒有功名在身,最多只能算一個半吊子童生。', '謝慎前世可是苦修明史的研究生,雖然主攻方向是嘉靖萬歷兩朝,但對弘治朝多少也有些了解。浙江一直是科舉強省,在弘治朝也是絕對的霸主,而紹興府則是浙江省中出進士最多的府。mianhuatang。cc,若要落在縣一級,那魁首非余姚莫屬。山陰,會稽兩縣加在一起,所中的進士數(shù)目都不比余姚的多。這么說來,余姚絕對是科舉的死亡之組了。王陽明,謝遷,王華,一想到余姚出的那些名人,謝慎便免不了自嘲。', '謝慎雖然前世苦修明史,對八股文也有所研究,但要讓他在明代科場競爭最恐怖的余姚縣脫穎而出,確實有些艱難。', '可當(dāng)他垂下頭看了看自己瘦弱的身體,不免搖了搖頭,長嘆了一聲。', '不科舉還能干什么呢,就他這副竹竿子般的身體,肩不能抗,手不能提,殺雞沒準(zhǔn)還被雞調(diào)戲。', '何況在這個萬般皆下品惟有讀書高的年代,不科舉就只能為民,不管是務(wù)農(nóng)還是經(jīng)商終歸都是被官府壓著,沒有出頭的機會。大明朝那個著名的沈萬三不就是最好的例子,家財萬貫富可敵國還不是隨意一個罪名就成了別人茶余飯后唏噓慨嘆的談資?', '讀書,還是得讀書,便前方是刀山火海硬著頭皮也得上。他才十二歲,還有可塑性。', '“小郎,你怎么在這兒呢,快快隨我回家去。你落水后被救起身子本就虛弱,若是在此時再染上了風(fēng)寒,那可了不得。你嫂嫂給你特地煮了雞湯,你多喝上幾碗也好補補身子。”', '一個中年男子的聲音打斷了謝慎的沉思,他抬了抬頭見來人是自己的胞兄謝方,心中不由得泛起一抹暖意。', '謝家門丁不旺,到了他們這一代只有他和謝方二人,不過這個便宜大哥似乎對他還不錯,吃穿用度上絕不短著缺著。', '謝方在余姚縣城里開著一家茶鋪,將每年收上的茶葉運到縣城售賣,質(zhì)地更好的一些會有人收走在府城售賣。', '明代浙江紹興府各縣幾乎都種植茶葉。最著名的要數(shù)杭州府錢塘縣的龍井茶以及寧波府象山縣的珠山茶。至于余姚雖然也種植茶樹,但其出產(chǎn)的茶葉并不像其種植棉花所產(chǎn)的“浙花”那么出名。', '不過似乎余姚本地的百姓很喜歡這種清淡的姚江茶,其在本縣的銷路并不差。', '靠著辛勤的工作,謝方也算撐起了一個家。在謝方的一再堅持下,謝慎從小便被送去開蒙,如今已經(jīng)學(xué)了六年了。不過原先的謝慎最多只能算是中上之資,跟神童絕對沾不上邊,照著原有軌跡發(fā)展下去,能不能取得秀才功名都是一個問題。']
貌似還忘了一個要處理的,我們看看最后20行的情況。(如果你是用全文本來訓(xùn)練,最后很多行文本中會包括\r這樣的特殊符號,要去掉。這里只用了100000字,所以看不到有\(zhòng)r的情況。)。如果有\\r
的情況,用下面的方式去掉。
# 將上面的正則換成負責(zé)找句尾『\\r』的內(nèi)容
pattern = re.compile(r'\\r')
# 將所有指定內(nèi)容替換成空
lines_of_text = [pattern.sub("", lines) for lines in lines_of_text]
到這里數(shù)據(jù)就處理完了。再看看有多少行數(shù)據(jù)
print(len(lines_of_text))
Out: 2158
因為模型只認識數(shù)字,不認識中文,所以將文字對應(yīng)到數(shù)字,分別創(chuàng)建文字對應(yīng)數(shù)字和數(shù)字對應(yīng)文字的兩個字典
def create_lookup_tables(input_data):
vocab = set(input_data)
# 文字到數(shù)字的映射
vocab_to_int = {word: idx for idx, word in enumerate(vocab)}
# 數(shù)字到文字的映射
int_to_vocab = dict(enumerate(vocab))
return vocab_to_int, int_to_vocab
創(chuàng)建一個符號查詢表,把逗號,句號等符號與一個標(biāo)志一一對應(yīng),用于將『我。』和『我』這樣的類似情況區(qū)分開來,排除標(biāo)點符號的影響。
def token_lookup():
symbols = set(['。', ',', '“', "”", ';', '!', '?', '(', ')', '——', '\n'])
tokens = ["P", "C", "Q", "T", "S", "E", "M", "I", "O", "D", "R"]
return dict(zip(symbols, tokens))
預(yù)處理一下數(shù)據(jù),并保存到磁盤,一遍下次直接讀取。
helper.preprocess_and_save_data(''.join(lines_of_text), token_lookup, create_lookup_tables)
讀取我們需要的數(shù)據(jù)。
int_text, vocab_to_int, int_to_vocab, token_dict = helper.load_preprocess()
檢查改一下當(dāng)前Tensorflow的版本以及是否有GPU可以使用
import problem_unittests as tests
from distutils.version import LooseVersion
import warnings
import tensorflow as tf
import numpy as np
# Check TensorFlow Version
assert LooseVersion(tf.__version__) >= LooseVersion('1.0'), 'Please use TensorFlow version 1.0 or newer'
print('TensorFlow Version: {}'.format(tf.__version__))
# Check for a GPU
if not tf.test.gpu_device_name():
warnings.warn('No GPU found. Please use a GPU to train your neural network.')
else:
print('Default GPU Device: {}'.format(tf.test.gpu_device_name()))
Out: TensorFlow Version: 1.0.0
Default GPU Device: /gpu:0
如果沒有GPU可以使用,看不到第二行輸出,而會是一個警告。
這里的數(shù)據(jù)量還是很大的,129萬左右個字符。建議使用GPU來訓(xùn)練。或者可以修改代碼,只用一小部分?jǐn)?shù)據(jù)來訓(xùn)練,節(jié)省時間。
正式進入創(chuàng)建RNN的階段了。
我們的RNN不是原始RNN了,中間使用到LSTM
和word2vec
的功能。下面將基于Tensorflow,創(chuàng)建一個帶2層LSTM層的RNN網(wǎng)絡(luò)來進行訓(xùn)練。
首先設(shè)置一下超參。
# 訓(xùn)練循環(huán)次數(shù)
num_epochs = 200
# batch大小
batch_size = 256
# lstm層中包含的unit個數(shù)
rnn_size = 512
# embedding layer的大小
embed_dim = 512
# 訓(xùn)練步長
seq_length = 30
# 學(xué)習(xí)率
learning_rate = 0.003
# 每多少步打印一次訓(xùn)練信息
show_every_n_batches = 30
# 保存session狀態(tài)的位置
save_dir = './save'
創(chuàng)建輸入,目標(biāo)以及學(xué)習(xí)率的placeholder
def get_inputs():
# inputs和targets的類型都是整數(shù)的
inputs = tf.placeholder(tf.int32, [None, None], name='inputs')
targets = tf.placeholder(tf.int32, [None, None], name='targets')
learning_rate = tf.placeholder(tf.float32, name='learning_rate')
return inputs, targets, learning_rate
創(chuàng)建rnn cell
,使用lstm cell
,并創(chuàng)建相應(yīng)層數(shù)的lstm層
,應(yīng)用dropout
,以及初始化lstm層
狀態(tài)。
def get_init_cell(batch_size, rnn_size):
# lstm層數(shù)
num_layers = 2
# dropout時的保留概率
keep_prob = 0.8
# 創(chuàng)建包含rnn_size個神經(jīng)元的lstm cell
cell = tf.contrib.rnn.BasicLSTMCell(rnn_size)
# 使用dropout機制防止overfitting等
drop = tf.contrib.rnn.DropoutWrapper(cell, output_keep_prob=keep_prob)
# 創(chuàng)建2層lstm層
cell = tf.contrib.rnn.MultiRNNCell([drop for _ in range(num_layers)])
# 初始化狀態(tài)為0.0
init_state = cell.zero_state(batch_size, tf.float32)
# 使用tf.identify給init_state取個名字,后面生成文字的時候,要使用這個名字來找到緩存的state
init_state = tf.identity(init_state, name='init_state')
return cell, init_state
創(chuàng)建embedding layer
,提升效率
def get_embed(input_data, vocab_size, embed_dim):
# 先根據(jù)文字?jǐn)?shù)量和embedding layer的size創(chuàng)建tensorflow variable
embedding = tf.Variable(tf.random_uniform((vocab_size, embed_dim)), dtype=tf.float32)
# 讓tensorflow幫我們創(chuàng)建lookup table
return tf.nn.embedding_lookup(embedding, input_data)
創(chuàng)建rnn
節(jié)點,使用dynamic_rnn
方法計算出output
和final_state
def build_rnn(cell, inputs):
'''
cell就是上面get_init_cell創(chuàng)建的cell
'''
outputs, final_state = tf.nn.dynamic_rnn(cell, inputs, dtype=tf.float32)
# 同樣給final_state一個名字,后面要重新獲取緩存
final_state = tf.identity(final_state, name="final_state")
return outputs, final_state
用上面定義的方法創(chuàng)建rnn
網(wǎng)絡(luò),并接入最后一層fully_connected layer
計算rnn的logits
def build_nn(cell, rnn_size, input_data, vocab_size, embed_dim):
# 創(chuàng)建embedding layer
embed = get_embed(input_data, vocab_size, rnn_size)
# 計算outputs 和 final_state
outputs, final_state = build_rnn(cell, embed)
# remember to initialize weights and biases, or the loss will stuck at a very high point
logits = tf.contrib.layers.fully_connected(outputs, vocab_size, activation_fn=None,
weights_initializer = tf.truncated_normal_initializer(stddev=0.1),
biases_initializer=tf.zeros_initializer())
return logits, final_state
那么大的數(shù)據(jù)量不可能一次性都塞到模型里訓(xùn)練,所以用get_batches
方法一次使用一部分?jǐn)?shù)據(jù)來訓(xùn)練
def get_batches(int_text, batch_size, seq_length):
# 計算有多少個batch可以創(chuàng)建
n_batches = (len(int_text) // (batch_size * seq_length))
# 計算每一步的原始數(shù)據(jù),和位移一位之后的數(shù)據(jù)
batch_origin = np.array(int_text[: n_batches * batch_size * seq_length])
batch_shifted = np.array(int_text[1: n_batches * batch_size * seq_length + 1])
# 將位移之后的數(shù)據(jù)的最后一位,設(shè)置成原始數(shù)據(jù)的第一位,相當(dāng)于在做循環(huán)
batch_shifted[-1] = batch_origin[0]
batch_origin_reshape = np.split(batch_origin.reshape(batch_size, -1), n_batches, 1)
batch_shifted_reshape = np.split(batch_shifted.reshape(batch_size, -1), n_batches, 1)
batches = np.array(list(zip(batch_origin_reshape, batch_shifted_reshape)))
return batches
創(chuàng)建整個RNN網(wǎng)絡(luò)模型
# 導(dǎo)入seq2seq,下面會用他計算loss
from tensorflow.contrib import seq2seq
train_graph = tf.Graph()
with train_graph.as_default():
# 文字總量
vocab_size = len(int_to_vocab)
# 獲取模型的輸入,目標(biāo)以及學(xué)習(xí)率節(jié)點,這些都是tf的placeholder
input_text, targets, lr = get_inputs()
# 輸入數(shù)據(jù)的shape
input_data_shape = tf.shape(input_text)
# 創(chuàng)建rnn的cell和初始狀態(tài)節(jié)點,rnn的cell已經(jīng)包含了lstm,dropout
# 這里的rnn_size表示每個lstm cell中包含了多少的神經(jīng)元
cell, initial_state = get_init_cell(input_data_shape[0], rnn_size)
# 創(chuàng)建計算loss和finalstate的節(jié)點
logits, final_state = build_nn(cell, rnn_size, input_text, vocab_size, embed_dim)
# 使用softmax計算最后的預(yù)測概率
probs = tf.nn.softmax(logits, name='probs')
# 計算loss
cost = seq2seq.sequence_loss(
logits,
targets,
tf.ones([input_data_shape[0], input_data_shape[1]]))
# 使用Adam提督下降
optimizer = tf.train.AdamOptimizer(lr)
# 裁剪一下Gradient輸出,最后的gradient都在[-1, 1]的范圍內(nèi)
gradients = optimizer.compute_gradients(cost)
capped_gradients = [(tf.clip_by_value(grad, -1., 1.), var) for grad, var in gradients if grad is not None]
train_op = optimizer.apply_gradients(capped_gradients)
開始訓(xùn)練模型
# 獲得訓(xùn)練用的所有batch
batches = get_batches(int_text, batch_size, seq_length)
# 打開session開始訓(xùn)練,將上面創(chuàng)建的graph對象傳遞給session
with tf.Session(graph=train_graph) as sess:
sess.run(tf.global_variables_initializer())
for epoch_i in range(num_epochs):
state = sess.run(initial_state, {input_text: batches[0][0]})
for batch_i, (x, y) in enumerate(batches):
feed = {
input_text: x,
targets: y,
initial_state: state,
lr: learning_rate}
train_loss, state, _ = sess.run([cost, final_state, train_op], feed)
# 打印訓(xùn)練信息
if (epoch_i * len(batches) + batch_i) % show_every_n_batches == 0:
print('Epoch {:>3} Batch {:>4}/{} train_loss = {:.3f}'.format(
epoch_i,
batch_i,
len(batches),
train_loss))
# 保存模型
saver = tf.train.Saver()
saver.save(sess, save_dir)
print('Model Trained and Saved')
將使用到的變量保存起來,以便下次直接讀取。
helper.save_params((seq_length, save_dir))
下次使用訓(xùn)練好的模型,從這里開始就好
import tensorflow as tf
import numpy as np
import helper
import problem_unittests as tests
_, vocab_to_int, int_to_vocab, token_dict = helper.load_preprocess()
seq_length, load_dir = helper.load_params()
要使用保存的模型,我們要講保存下來的變量(tensor)通過指定的name
獲取到
def get_tensors(loaded_graph):
inputs = loaded_graph.get_tensor_by_name("inputs:0")
initial_state = loaded_graph.get_tensor_by_name("init_state:0")
final_state = loaded_graph.get_tensor_by_name("final_state:0")
probs = loaded_graph.get_tensor_by_name("probs:0")
return inputs, initial_state, final_state, probs
def pick_word(probabilities, int_to_vocab):
chances = []
for idx, prob in enumerate(probabilities):
if prob >= 0.05:
chances.append(int_to_vocab[idx])
rand = np.random.randint(0, len(chances))
return str(chances[rand])
使用訓(xùn)練好的模型來生成自己的小說
# 生成文本的長度
gen_length = 500
# 文章開頭的字,指定一個即可,這個字必須是在訓(xùn)練詞匯列表中的
prime_word = '章'
loaded_graph = tf.Graph()
with tf.Session(graph=loaded_graph) as sess:
# 加載保存過的session
loader = tf.train.import_meta_graph(load_dir + '.meta')
loader.restore(sess, load_dir)
# 通過名稱獲取緩存的tensor
input_text, initial_state, final_state, probs = get_tensors(loaded_graph)
# 準(zhǔn)備開始生成文本
gen_sentences = [prime_word]
prev_state = sess.run(initial_state, {input_text: np.array([[1]])})
# 開始生成文本
for n in range(gen_length):
dyn_input = [[vocab_to_int[word] for word in gen_sentences[-seq_length:]]]
dyn_seq_length = len(dyn_input[0])
probabilities, prev_state = sess.run(
[probs, final_state],
{input_text: dyn_input, initial_state: prev_state})
pred_word = pick_word(probabilities[dyn_seq_length - 1], int_to_vocab)
gen_sentences.append(pred_word)
# 將標(biāo)點符號還原
novel = ''.join(gen_sentences)
for key, token in token_dict.items():
ending = ' ' if key in ['\n', '(', '“'] else ''
novel = novel.replace(token.lower(), key)
novel = novel.replace('\n ', '\n')
novel = novel.replace('( ', '(')
print(novel)
這是我用100000個字訓(xùn)練200個循環(huán)之后產(chǎn)生的文本
章,正后一屁股文。在這一幫寒門謝氏,但揚聲修呼。拉他的手藝推著而出了院門,不是四門中,謝慎注意一息,剛這日一旁不何從世家。王守文在竹門的王守仁作寫來已經(jīng)站候,幾十板子,想要去布包嗎,不得我到了縣尊讓謝慎心里一詩會就想在一個木籃子,他也算孔教諭都拱問而即便只有在這些學(xué)生,他需要受呼謝方四書一次。這樣的人早,只得一個人約的回景,絕不會謝慎便在縣學(xué)里加靠考下。如果不睡在,謝方和劉老夫子捋了下樓的時間有些啊。“這個王守仁的人情出了。”謝慎一邊又要通過身后。曹主簿才不是一個不會再為何事情。他這個時文讀洞人要有何證不像提一跳的,大哥謝慎還是想聽有人擔(dān)心的。謝慎也不能掉以輕早一只有意授嗎?”謝丕笑了點頭,目送著二人,柔聲道:“本然比何好關(guān),這才會出他什么可是了心門,你一直不必再拘束。他若不想一個自己拿到官府,他們卻是三十大板,會有考好時文,他的境遇更發(fā)生了拉近。他自己不能在縣試取什么,留是功名,他需要你還有一段時時也能做上。他這個時候出城真也得不太一合寫,畢竟余姚名學(xué)說的青石頭最多一貫情,但布置的學(xué)生自稱是為自己的。但果然謝慎是想不到四門謝氏的嫡公子那里不遠,一種五家小王守文的部二。那樣俊望的計”這下
可以看到模型已經(jīng)能產(chǎn)生一些有意義的詞語了,比如『寒門』,『手藝』,『已經(jīng)』,『布置』等。但是還沒有形成通順的句子。
這個RNN的架構(gòu)遵照了
Input -> LSTM -> Dropout -> LSTM -> Dropout -> Fully Connected
這樣的形式。
在使用完10萬個字進行訓(xùn)練之后,我嘗試使用整個數(shù)據(jù)集也就是129萬個字符去訓(xùn)練,但是受困于計算能力有限,沒有完成(我在亞馬遜云的EC2內(nèi)存和GPU有限,無法完成這么大數(shù)據(jù)量的訓(xùn)練,說白了是沒錢哈哈~)。大家有資源的可以嘗試。
有不足的地方請大家留言或者ISSUE,我也還在不斷學(xué)習(xí)成長中,歡迎交流。
EDIT 6月12日:
前面一次訓(xùn)練只使用了100000的詞匯來訓(xùn)練,之后我申請到了亞馬遜云一個p2.16xlarge的實例來訓(xùn)練全部數(shù)據(jù),使用如下的超參。
# 訓(xùn)練循環(huán)次數(shù)
num_epochs = 200
# batch大小
batch_size = 256
# lstm層中包含的unit個數(shù)
rnn_size = 1024
# embedding layer的大小
embed_dim = 1500
# 訓(xùn)練步長
seq_length = 60
# 學(xué)習(xí)率
learning_rate = 0.001
經(jīng)過200個循環(huán),loss
大概降到了1.3
。參數(shù)還需要進一步調(diào)整,小于0.5
的loss
才接近理想。不過1.3
的loss
可以生成一些通順的語句了,比之前只有詞語的情況進步了不少。
章,如果百年世家不想出現(xiàn)的這些士子,一個孩子多那么夸,竟是得好不起的。謝慎不由得感慨道:“怎么,有幾日潞安百姓謝某不過府面子的外轉(zhuǎn),實話足夠啊。?”我又說說沒有顧大才吧;我這次便是舍頭大明皇帝去打啊, 我現(xiàn)在還沒出息,你們要不去。陳虎兒見謝慎面色古怪了,可也不知道該怎么做點出真手錯了。就是說,這個譚芳還不得不起自己,也沒有多想著謝慎這般拘束他們的表演。不曾想這世上也太不避嫌了。謝方和大人平日閑來得到唐寅身來到西湖身上闖乘,王守仁自然也就沒有考慮。回來再四不徐徐芊芊,喝水直是君業(yè),想想他也是可以將直接的成本。不過有些事謝慎不能再找個面子,看看明軍心中的架勢。這些百姓不是個外人。真想知行來是不會有人拿出來好的,若不是她不選識及防之力就可以讓水蕓姑娘相互邀請一家。那些倒也沒有在他揚名上啊,而這是讀書人對太子的才子加足了的。他不需要太多的道理,只不過他還想不到京師生員上面臨時起云,怎么可能拒絕低調(diào),真得一起吃酒啊。慎賢弟,怎么,本
像『我現(xiàn)在還沒出息』,『王守仁自然也就沒有考慮』,『他不需要太多的道理』,都是通順且有意義的短句了。不過短句前后,還是沒有形成語義的連貫。1.3
的loss
還是太高。有時間再繼續(xù)調(diào)整吧~