重做红楼梦的数据分析-判断前80回后40回是否一个人写的

重做红楼梦的数据分析-判断前80回后40回是否一个人写的

红楼梦的数据分析已经有许多人做过,结论也各不相同。
我在知乎上看到两篇帖子:
1. 通过数据挖掘能分析《红楼梦》各回的真伪吗?
2. 用机器学习判定红楼梦后40回是否曹雪芹所写
觉得很有意思,于是用自己的方法重做了一次

环境配置:

我主要使用的编程环境是Jupyter Notebook 4.2.1,因为可以调整每一个代码块,方便
纠错什么的。
然后我们得用到一个中文分词工具 - Jieba, 是由百度工程师Sun Junyi开发的
之后我们还得用到一些做机器学习/数据挖掘的标准包:numpy, matplotlib 和 sklearn

数据准备:

用爬虫思想,我去这个网站扒下来红楼梦全集,然后剪掉中间所有的换行符,使得每一回只
占文档中的一行。这样的话,方便接下来读取。

直接上代码:
一、导入各种需要的包

# -*-coding:utf-8 -*-

import urllib
import urllib2
import re
from bs4 import BeautifulSoup as bs

book = []
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('\n')

数据ready,可以开始进行处理了

处理:

直接上代码:
一、导入各种需要的包

import numpy as np
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import Axes3D # 因为后面会用到3d作图
import operator
# 下面是机器学习包
from sklearn.cross_validation import train_test_split 
from sklearn.grid_search import GridSearchCV
from sklearn.svm import SVC
from sklearn.metrics import classification_report
from sklearn.decomposition import PCA
# Jieba
import 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 = 1
for chap in content:
    sub_table = {}
    for word in uw:
        sub_table[word.decode('utf8')] = chap.count(word.decode('utf8'))
    table[chapNo] = sub_table
    chapNo+=1

import operator
table_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的目标维数并创建一个model
pca = 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))

最后我们的预测结果是这样的:

predictionprecisionrecallf1-scoresupport
00.850.970.9029
10.980.930.9567
avg/total0.940.940.9496

就结果而言,我们的模型比较准确的预测了测试样本属于哪个分类,说明我们的直观判断,可能是对的。

撒花~

欢迎转载

  • 15
    点赞
  • 66
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
本文通过对文本人物关系、文本结构分层、作者行文风格的分析来分析中文文本。 针对问题一,我们运用聚类分析和层次分析建立模型从物理结构与逻辑结构两方面来分析文本,我们提取文本中和文本标题中的人名作为特征项,用matlab编程分别统计每个人名在各个段落中的频数。通过运用主成分分析法对文本进行的分析我们得出自变量与常数项几乎不相关,因此不需要采取主成分归分析。通过系统聚类分析,我们得到了聚类图,从中得出了主演人物之间的关系。通过层次划分,我们将样本一划分为两层,样本二划分为两层,样本三划分为两层。最后通过matlab编程统计样本中虚词的频数,并且分别对样本中虚词总体和各个虚词进行统计,运用计算风格学理论,我们得出八十回与后四十回作者的行文风格存在差异。 针对问题二,我们对聚类分析、层次划分、行文风格进行了检验。对于聚类分析的结果,我们与从对文本概述的文学概括分析得到的人物关系进行比较检验,验证了聚类分析结果是可靠性。对于层次分析,我们通过用Excel对数据做出折线图,对图形进行分析,得出与用层次分析算法得出的相同的人物关系结论。 针对问题三,我们计算了各个样本中主要人物的比重,做出了折线图,从图中我们得出了文本结构一致性的结论,体现了三个样本的相同性。通过计算同一个人物在不同样本中的频数(以黛玉为例),我们得出各个样本由于主题思想的不同主要人物也有差异。
虽然国内人民生活水平的提高,科技不断地进步,人们越来越喜欢并倾向于现代艺术,但是对传统艺术的传承和发展也受到新一代青少年的挚爱。例如《红楼梦》成为现阶段一处亮丽的风景线,由于其拥有丰富的底蕴文化,故事情节生动细腻,受到电视剧行业的多次翻拍,使得在古代小说行业具有很高的景,成为世界各国关注的焦点。自从《红楼梦》给社会带来的影响很大时,越来越多的人开始对这部作品进行细品和研究。因此新一代的我们要认真去钻研古代优秀的文化传统。在之的一项对《红楼梦》中任务的研究中,名字被更多地关注到谐音上,或关于为什么被这样命名的猜测上,或着如何构成命名的组成和分类。这些研究主要集中在静态研究的水平上,更加注重对人物内在的研究。虽然这篇文章主要基于符号学的方向,将《红楼梦》中有关人物的命名进行动态学研究,强调符号与人物之间的关系,即名称与人物之间的逻辑联系,也就是人物命名的符号性问题。《红楼梦》中对角色的描述,可谓是生动形象,每一个角色不管是贵妃丫鬟、皇贵平民都充分的流露出情感。有反叛,有封建,有怜悯,有感情,任何人都可以在剧情中感受到的情感。作者们尽其所能让读者对书中的角色评价有意义,并对现代社会进行反
好的,我可以帮你一个Python程序来统计红楼梦》中20位出场最多的人物。 首先,我们需要读取《红楼梦》的文本文件。你可以在程序中指定文本文件的路径和名称。 ```python with open('hongloumeng.txt', 'r', encoding='utf-8') as f: text = f.read() ``` 然后,我们需要用正则表达式来匹配出所有的人名。在《红楼梦》中,人名通常是以“贾”、“林”、“薛”、“王”、“史”、“傅”等姓氏开头,后面跟着两个或三个汉字。 ```python import re pattern = '[贾林薛王史傅][\u4e00-\u9fa5]{1,2}' names = re.findall(pattern, text) ``` 接下来,我们可以使用Python内置的collections.Counter类来统计人名出现的次数,并返出现次数最多的20个人名。 ```python from collections import Counter name_counts = Counter(names) top_20 = name_counts.most_common(20) ``` 最后,我们可以将统计结果输出到控制台。 ```python for i, item in enumerate(top_20): print('第%d名:%s,出现%d次' % (i+1, item[0], item[1])) ``` 完整的代码如下: ```python import re from collections import Counter with open('hongloumeng.txt', 'r', encoding='utf-8') as f: text = f.read() pattern = '[贾林薛王史傅][\u4e00-\u9fa5]{1,2}' names = re.findall(pattern, text) name_counts = Counter(names) top_20 = name_counts.most_common(20) for i, item in enumerate(top_20): print('第%d名:%s,出现%d次' % (i+1, item[0], item[1])) ``` 希望能够帮到你!

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值