重做紅樓夢的數據分析-判斷前80回後40回是否一個人寫的

重做紅樓夢的數據分析-判斷前80回後40回是否一個人寫的

寫在前面,本文為學習筆記,在代碼上並非原創。

紅樓夢的數據分析已經有許多人做過,結論也各不相同。

我在知乎上看到兩篇帖子:

1. 通過數據挖掘能分析《紅樓夢》各回的真偽嗎?

2. 用機器學習判定紅樓夢後40回是否曹雪芹所寫

覺得很有意思,於是用自己的方法重做了一次

環境配置:

我主要使用的編程環境是Jupyter Notebook 4.2.1,因為可以調整每一個代碼塊,方便

糾錯什麼的。

然後我們得用到一個中文分詞工具 - Jieba, 是由百度工程師Sun Junyi開發的

之後我們還得用到一些做機器學習/數據挖掘的標準包:numpy, matplotlib 和 sklearn

數據準備:

用爬蟲思想,我去這個網站扒下來紅樓夢全集,然後剪掉中間所有的換行符,使得每一回只

占文檔中的一行。這樣的話,方便接下來讀取。

直接上代碼:

一、導入各種需要的包

# -*-coding:utf-8 -*-import urllibimport urllib2import refrom bs4 import BeautifulSoup as bsbook = []for i in range(120): print("處理第{}回...".format(i+1)) if i+1<10: url = "http://www.purepen.com/hlm/00{}.htm".format(i+1) elif i+1 < 100: url = "http://www.purepen.com/hlm/0{}.htm".format(i+1) else: url = "http://www.purepen.com/hlm/{}.htm".format(i+1) request = urllib2.Request(url) response = urllib2.urlopen(request) bsObj = bs(response.read().decode(gb18030)) #注意原網頁的codec是哪一種 chapter = bsObj.table.font.contents[0] book.append(chapter)

下面是結果:

之後把全文存進一個txt文件:

with open(紅樓夢.txt, w) as f: f.write(codecs.BOM_UTF8) for chap in book: s = chap.encode(utf-8).strip() f.write("".join(s.split())) f.write(
)

數據ready,可以開始進行處理了

處理:

直接上代碼:

一、導入各種需要的包

import numpy as npimport matplotlib.pyplot as pltfrom mpl_toolkits.mplot3d import Axes3D # 因為後面會用到3d作圖import operator# 下面是機器學習包from sklearn.cross_validation import train_test_split from sklearn.grid_search import GridSearchCVfrom sklearn.svm import SVCfrom sklearn.metrics import classification_reportfrom sklearn.decomposition import PCA# Jiebaimport jieba

二、讀取文件並分詞

with open(紅樓夢.txt) as f: all_chaps = [chap.decode(utf8) for chap in f.readlines()]# 給整本書分詞dictionary = []for i in range(120): print "處理第{}回".format(i+1) words = list(jieba.cut(all_chaps[i])) dictionary.append(words)

三、Flatten數組 (中文是』攤平』? 哈哈)

tmp = [item for sublist in dictionary for item in sublist] # 攤平dictionary = tmp

四、 給每一回貼上標籤

# 給每一回貼上標籤for i in range(120): if i < 80: all_chaps[i] = [all_chaps[i],1] else: all_chaps[i] = [all_chaps[i],0]content = [row[0] for row in all_chaps]label = [row[1] for row in all_chaps]

五、找出每一回均出現的詞

之所以要這麼做,是因為有一些很常出現的角色名在後四十回因為劇情原因不再出現了。在整個分析中我們注重對於文言虛詞和其他連接詞的分析,因為這樣更能體現出寫作者的個人風格。另外,這也是為什麼我們沒有在Jieba里加入角色名稱的字典,因為沒有這個必要。

# 找出每一回均有出現的詞from progressbar import ProgressBar # 顯示進度pbar =ProgressBar()wordineverychap = []length = len(dictionary)print "共有{}個詞".format(length)for word in pbar(dictionary): n = 0 for text in content: if word in text: n+=1 if n==120: wordineverychap.append(word)

六、合併虛詞,以防虛詞被過濾掉

這裡用的虛詞是直接從維基百科上抄下來的,一共20個左右,所以也並不麻煩。

with open(xuci.txt) as f: xuci = [word.decode(utf8).strip() for word in f.readlines()] for word in xuci: if word not in wordineverychap: wordineverychap.append(word)

七、過濾重複的詞語,並去掉標點符號

selected_words = list(set(wordineverychap))# 人工處理, 刪除標點符號for w in selected_words: print w

計算結果是一共有125個詞語

八、給每個詞語計數 並 排序

wordT = []countT = []table = {}chapNo = 1for chap in content: sub_table = {} for word in uw: sub_table[word.decode(utf8)] = chap.count(word.decode(utf8)) table[chapNo] = sub_table chapNo+=1import operatortable_sorted = []for idx in table: sub_table_sorted = sorted(table[idx].items(),key=operator.itemgetter(1),reverse=True) table_sorted.append(sub_table_sorted)

九、把數據存在csv里,以免不小心關掉程序後不用重新計算

# 任務:把數據存到csv里import unicodecsv as csv# 寫入第一行和第一列f1 = open(cipin.csv, w+)writer = csv.writer(f1, encoding=utf8, delimiter=,)first_row = [] # A1留空for i in range(120): first_row.append(第{}回.format(i+1))writer.writerow(first_row) for word in selected_words: row = [word] for i in range(120): row.append(table[i+1][word.decode(utf8)]) writer.writerow(row)f1.close()

十、把數據向量化

# 任務:把數據向量化 all_vectors = []for i in range(120): chap_vector = [] for word in selected_words: chap_vector.append(table[i+1][word.decode(utf8)]) all_vectors.append(chap_vector)

十一、把高維向量壓縮為3維向量,方便作圖

這裡我們使用PCA(Principal Component Analysis),就是一種把高維度向量變成低維度向量的演算法。比如我們現在每一回就有125維,無法作圖。這個演算法,像它的名字一樣,會採集最重要的向量,然後壓縮成到我們所需要的維數(3維)

#設置PCA的目標維數並創建一個modelpca = PCA(n_components=3)#Feed我們的向量,進行訓練pca.fit(all_vectors)#取得目標向量z = pca.fit_transform(all_vectors)#取得前八十回的向量xs_a = [row[0] for row in z[:80]]ys_a = [row[1] for row in z[:80]]zs_a = [row[2] for row in z[:80]]#取得後四十回的向量xs_b = [row[0] for row in z[-40:]]ys_b = [row[1] for row in z[-40:]]zs_b = [row[2] for row in z[-40:]]#創建一個新的圖表fig = plt.figure()ax = fig.add_subplot(111, projection=3d)#繪圖ax.scatter(xs_a, ys_a, zs_a, c=r, marker=o)ax.scatter(xs_b, ys_b, zs_b, c=b, marker=^)plt.show()

這就是繪製出來的圖表:

每一個點表示一回,紅色的點表示的是前八十回,藍色的點表示的是後四十回。從該圖我們可以發現,前八十回和後四十回的寫作者用詞習慣有可觀察到的不同,所以由此我們可以大膽的說,前後的寫作者是不同的!

為了準確,我們還可以做一組對比試驗,這次我們分別畫出前四十回 ,中間四十回 和 後四十回:

#前四十回xs_a = [row[0] for row in z[:40]]ys_a = [row[1] for row in z[:40]]zs_a = [row[2] for row in z[:40]]#中間四十回xs_b = [row[0] for row in z[40:80]]ys_b = [row[1] for row in z[40:80]]zs_b = [row[2] for row in z[40:80]]#最後四十回xs_c = [row[0] for row in z[-40:]]ys_c = [row[1] for row in z[-40:]]zs_c = [row[2] for row in z[-40:]]ax.scatter(xs_a, ys_a, zs_a, c=b, marker=o)ax.scatter(xs_b, ys_b, zs_b, c=y, marker=^)ax.scatter(xs_c, ys_c, zs_c, c=r, marker=o)plt.show()

畫出的圖表是這樣:

藍色的是前四十回,綠色的是中間四十回,紅色的是後四十回。在這個圖裡我們也能看到前四十回和中間四十回重合了很多,而後四十回相對獨立。

十三、用機器學習的思路處理

簡單的說,就是我們把前八十回和後四十回分別做標註,用『1』表示屬於前八十回,『0』表示屬於後四十回。接著我們從前八十回中抽16回,後四十回中抽8回用作訓練樣本,剩下的用作測試樣本。如果訓練出來的模型成功從預測樣本中預測出是否屬於前八十回,就代表我們的想法是對的—–前八十回和後四十回的用詞習慣的確不同。

上代碼:

label = []for i in range(120): if i<80: label.append(1) else: label.append(0)# 分出訓練和測試樣本x_train, x_test, y_train, y_test = train_test_split(all_vectors, label, test_size=0.8)# 使用GridSearch找到合適的參數params = [{C:[1,5,10,50,100,250,500]}]grid = GridSearchCV(SVC(kernel=linear),params,cv=10)# 訓練!grid.fit(x_train,y_train)# 預測y_pred = grid.predict(x_test)# 顯示預測結果print(classification_report(y_test, y_pred))

最後我們的預測結果是這樣的:

prediction precision recallf1-score support00.850.97 0.90 2910.980.93 0.9567avg/total0.940.94 0.9496

就結果而言,我們的模型比較準確的預測了測試樣本屬於哪個分類,說明我們的直觀判斷,可能是對的。

撒花~

歡迎轉載


推薦閱讀:

ch5 離散型概率分布
分析泰坦尼克號遇難數據 - 張然
(13)Python初入坑之時間序列基礎內容
Kaggle項目泰坦尼克號生存預測
美國嬰兒名字分析-不要再用英語教材裡面的英文名了!!!

TAG:數據分析 | 紅樓夢小說 | 機器學習 |