聚类简单实现对《红楼梦》是否同一作者的分析(上)

时间:2021-12-10 16:57:14

第一次看到老师给的题目的时候,我就很怀疑:通过每个章回虚词出现的频率(频数),就能判别是否是同一作者?

大概出现的虚词:

cell=['之','其','或','亦','方','于','即','皆','因','仍','故','尚','乃','呀','吗','咧','罢咧','啊','罢','罢了','么','呢','了','的','着','一','不','把','让','向','往','是','在','别','好','可','便','就','但','越','再','更','比','很','偏','儿']

当然,首先考虑到:比如说“之”这个字,就有很多用法,1 助词 2动词 3代词,而动词是不属于虚词的,但是怎么判断文章中出现的”之“是不是虚词呢?好像得用到语义分析,但是某文智系统都不靠谱:

聚类简单实现对《红楼梦》是否同一作者的分析(上)

就算了吧,就简单实现一下对特定的这45个字(词)的每章回出现的频率(频数)进行统计就完事了。

先挖个坑,如果以后有时间的话,研究研究文本的特征挖掘,写个[下](估计是不太可能了)。


首先,读入txt文件。[该txt中一行代表一个段落]

file=open('redmansiondream.txt',encoding="ansi")
file_context=file.readlines()

这样就把txt中的信息存到列表file_context里了,每个内容代表一个段落中所有的文字。

其次,使用正则表达式分割file_context,本代码采用的是判断”第XXX回“出现的段号,即file_context中内容为"第XXX回"的下标,并把每回的开始段号和结束段号存储在二维数组中。

(ps:习惯了C和mat的for循环  python的for不太习惯 所以循环都是用的while)

t=0 #第t回
#提取每个章回的开始结束段号
while i<len(file_context):
    key=re.compile(u'第[\u4e00-\u9fa5]{0,6}回') #正则表达式查找字符串‘第X回’ 其中X长度为[0,6]
    pa=re.findall(key,file_context[i])
    if (len(pa)==1) & (file_context[i][0]=='第')& (len(file_context[i])<30):#因内容中有提及之前章回之嫌,判断字符串是否位于段首
        t=t+1
        index[t-1][0]=i#开始段号
        if t!=1:
            index[t-2][1]=i #结束段号
    i=i+1
index[119][1]=len(file_context)-2 #最后一回段号

这样,我们就得出了每一回的起始段号和结束段号,就相当于实现了”分割“。

之后,对于每一回,分别计算出45个指定的字出现的次数,并储存在二维数组中。

data1 储存频率,data2储存频数

i=0
while i<120:
    start=index[i][0]+1
    end=index[i][1]
    num=0;#统计该章回总共字数 回车符不计
    while start<end:#统计该章回每一段的词频
        num=num+len(file_context[int(start)])-1;
        start=start+1
        c=0
        while c<len(cell):
            data2[i][c]=data2[i][c]+len(re.findall(cell[c],file_context[int(start)])) #匹配得到的次数累加
            c=c+1
    #得出的结果  比如 '罢'的次数中包括'罢咧'的次数 应减减去
    k1=0
    while k1<len(cell):
        k2=0
        while k2<len(cell):
            if k1!=k2:
                if (cell[k1] in cell[k2])==True: #判断是否是子串 如果是  前者的值变为前者的值减后者的值
                        data2[i][k1]=data2[i][k1]-data2[i][k2]
            k2=k2+1
        k1=k1+1
    #求频率
    k=0
    while k<len(cell):
        data1[i][k]=data2[i][k]*1000/num; #数值太小 统一乘以1000
        k=k+1;
    i=i+1

从而得出频率和频数。

首先使用K-means算法进行聚类:

自己写的k-means(有点长乱,可以略过):

#2_means-----(频数版)
k1=np.zeros(46)
k2=np.zeros(46)
i=0
#随机初始化2类均值 直接指定第11段为一类 101段为2类
while i<len(cell):
    k1[i]=data2[10][i]
    k2[i]=data2[100][i]
    i=i+1
classi=np.zeros(120) #每一回章所属于的类别 取值 1或 2
while True:
    ret=1 #判断是否退出循环的条件
    d=0
    while d<len(data2):
        c=0
        dis1=0 
        dis2=0
        while c<len(cell):
            dis1+=(data2[d][c]-k1[c])*(data2[d][c]-k1[c])
            dis2+=(data2[d][c]-k2[c])*(data2[d][c]-k2[c])
            c=c+1
        dis1=math.sqrt(dis1)
        dis2=math.sqrt(dis2)
        class_old=classi[d]
        if dis1<=dis2:
            classi[d]=1
        else:
            classi[d]=2
        if class_old!=classi[d]:
            ret=0
        d=d+1
    #重新计算两类均值 先清0
    num1=0
    num2=0
    com=0
    i=0
    while i<len(cell):
        k1[i]=0
        k2[i]=0
        i=i+1
    
    while com<len(classi):
        if classi[com]==1:
            num1+=1
            cop=0
            while cop<len(k1):
                k1[cop]+=data2[com][cop];
                cop+=1
            
        else:
            num2+=1
            cop=0
            while cop<len(k1):
                k2[cop]+=data2[com][cop];
                cop+=1
        com+=1
    cop=0
    while cop<len(cell):
        k1[cop]=math.sqrt(k1[cop])
        k2[cop]=math.sqrt(k2[cop])
        cop+=1
    if ret==1:
        break
    
    
i=0


直接使用sklearn自带的包:
from sklearn.cluster import KMeans
model=KMeans(n_clusters=2)
s=model.fit(data2)
print(model.labels_)

最后两者分别得出的结果如下:

聚类简单实现对《红楼梦》是否同一作者的分析(上)

聚类简单实现对《红楼梦》是否同一作者的分析(上)

总体看来这两个类分布的错落有致,无法区分(我就说怎么可能靠谱)

试试用决策树分类的效果:令前10个为1类(label 0),后10个为2类(label 1),对中间100回进行预测:

from sklearn.ensemble import RandomForestRegressor  
X_train=np.concatenate((data2[:20],data2[100:]),0)
Y_train=np.array([0,0,0,0,0,0,0,0,0,0,1,1,1,1,1,1,1,1,1,1,])
rf=RandomForestRegressor(46)
rf.fit(X_train,Y_train)
X_test=np.concatenate((data2[10:50],data2[50:110]),0)
Y_test=rf.predict(X_test)
i=0
while i<len(Y_test):
    Y_test[i]=round(Y_test[i])
    i=i+1
结果如下:

聚类简单实现对《红楼梦》是否同一作者的分析(上)

貌似效果和K-means差不多。


然后再试试层次聚类的效果:

import scipy.cluster.hierarchy as sch
import matplotlib.pylab as plt
disMat = sch.distance.pdist(data2,'euclidean') 
Z=sch.linkage(disMat,method='average') 
P=sch.dendrogram(Z)
plt.savefig('C:/Users/71405/Desktop/大数据分析/14/plot_dendrogram.png')

结果:聚类简单实现对《红楼梦》是否同一作者的分析(上)

(啥都看不清。。。。)

就这样了,效果很差,以后有时间研究一下特征提取,来代替虚词词频。

完整的越到后越懒得写注释的代码:

# -*- coding: utf-8 -*-
"""
Created on Thu May 31 08:24:07 2018

@author: Type真是太帅了
"""
import re
import numpy as np
import math
data1=np.zeros((120,46)) #储存120回中的46个虚词的出现频率
data2=np.zeros((120,46)) #储存120回中的46个虚词的出现频数
index=np.zeros((120,2)) #统计每回开始的段号和结束的段号(均不包括)
cell=['之','其','或','亦','方','于','即','皆','因','仍','故','尚','乃','呀','吗','咧','罢咧','啊','罢','罢了','么','呢','了','的','着','一','不','把','让','向','往','是','在','别','好','可','便','就','但','越','再','更','比','很','偏','儿']
file=open('redmansiondream.txt',encoding="ansi")
file_context=file.readlines() #把所有内容读入到file_context里 
i=1
t=0 #第t回
#提取每个章回的开始结束段号
while i<len(file_context):
    key=re.compile(u'第[\u4e00-\u9fa5]{0,6}回') #正则表达式查找字符串‘第X回’ 其中X长度为[0,6]
    pa=re.findall(key,file_context[i])
    if (len(pa)==1) & (file_context[i][0]=='第')& (len(file_context[i])<30):#因内容中有提及之前章回之嫌,判断字符串是否位于段首
        t=t+1
        index[t-1][0]=i#开始段号
        if t!=1:
            index[t-2][1]=i #结束段号
    i=i+1
index[119][1]=len(file_context)-2 #最后一回段号


i=0
while i<120:
    start=index[i][0]+1
    end=index[i][1]
    num=0;#统计该章回总共字数 回车符不计
    while start<end:#统计该章回每一段的词频
        num=num+len(file_context[int(start)])-1;
        start=start+1
        c=0
        while c<len(cell):
            data2[i][c]=data2[i][c]+len(re.findall(cell[c],file_context[int(start)])) #匹配得到的次数累加
            c=c+1
    #得出的结果  比如 '罢'的次数中包括'罢咧'的次数 应减减去
    k1=0
    while k1<len(cell):
        k2=0
        while k2<len(cell):
            if k1!=k2:
                if (cell[k1] in cell[k2])==True: #判断是否是子串 如果是  前者的值变为前者的值减后者的值
                        data2[i][k1]=data2[i][k1]-data2[i][k2]
            k2=k2+1
        k1=k1+1
    #求频率
    k=0
    while k<len(cell):
        data1[i][k]=data2[i][k]*1000/num; #数值太小 统一乘以1000
        k=k+1;
    i=i+1



#2_means-----(频数版)
k1=np.zeros(46)
k2=np.zeros(46)
i=0
#随机初始化2类均值
while i<len(cell):
    k1[i]=data2[10][i]
    k2[i]=data2[100][i]
    i=i+1
classi=np.zeros(120) #每一回章所属于的类别 取值 1或 2
while True:
    ret=1 #判断是否退出循环的条件
    d=0
    while d<len(data2):
        c=0
        dis1=0 
        dis2=0
        while c<len(cell):
            dis1+=(data2[d][c]-k1[c])*(data2[d][c]-k1[c])
            dis2+=(data2[d][c]-k2[c])*(data2[d][c]-k2[c])
            c=c+1
        dis1=math.sqrt(dis1)
        dis2=math.sqrt(dis2)
        class_old=classi[d]
        if dis1<=dis2:
            classi[d]=1
        else:
            classi[d]=2
        if class_old!=classi[d]:
            ret=0
        d=d+1
    #重新计算两类均值 先清0
    num1=0
    num2=0
    com=0
    i=0
    while i<len(cell):
        k1[i]=0
        k2[i]=0
        i=i+1
    
    while com<len(classi):
        if classi[com]==1:
            num1+=1
            cop=0
            while cop<len(k1):
                k1[cop]+=data2[com][cop];
                cop+=1
            
        else:
            num2+=1
            cop=0
            while cop<len(k1):
                k2[cop]+=data2[com][cop];
                cop+=1
        com+=1
    cop=0
    while cop<len(cell):
        k1[cop]=math.sqrt(k1[cop])
        k2[cop]=math.sqrt(k2[cop])
        cop+=1
    if ret==1:
        break
    
    
i=0

from sklearn.cluster import KMeans
model=KMeans(n_clusters=2)
s=model.fit(data2)
print(model.labels_)

from sklearn.ensemble import RandomForestRegressor  
X_train=np.concatenate((data2[:20],data2[100:]),0)
Y_train=np.array([0,0,0,0,0,0,0,0,0,0,1,1,1,1,1,1,1,1,1,1,])
rf=RandomForestRegressor(46)
rf.fit(X_train,Y_train)
X_test=np.concatenate((data2[10:50],data2[50:110]),0)
Y_test=rf.predict(X_test)
i=0
while i<len(Y_test):
    Y_test[i]=round(Y_test[i])
    i=i+1

import scipy.cluster.hierarchy as sch
import matplotlib.pylab as plt
disMat = sch.distance.pdist(data2,'euclidean') 
Z=sch.linkage(disMat,method='average') 
P=sch.dendrogram(Z)
plt.savefig('C:/Users/71405/Desktop/大数据分析/14/plot_dendrogram.png')