计划写一下本学期选修的《机器学习与商业数据挖掘》课程的一系列实战笔记,本篇是第四节课的内容——聚类。
聚类
聚类分析(cluster analysis)是常见的数据挖掘手段,其主要假设是数据间存在相似性,数据集合之间存在差异性。而相似性是有价值的,因此可以被用于探索数据中的特性以产生价值。
聚类是一种无监督的算法,输入一段未被标记的数据,聚类通过使组内距离最小+组间距离最大实现组的划分。
kmeans算法
•事先定义数据要分为几类(K)
•两个原则:每个cluster的中心是这个cluster所有点平均值、每个点距离其自身cluster内的中心的距离比别的cluster中心的距离要近
•数学定义:
算法思路:
•先随机几个初始点为中心点。
•然后遍历所有点,所有点都找到各自的nearest central point。
•重新计算中心点的位置。
•迭代进行。
因此我们注意!
•K-means 可能到不了全局最优点(局部最优)
•必须事先指定有多少类
•K-means 是线性划分
实战
目标:数据挖掘验证《红楼》前八十回,后四十回以及《史记》三者的差别
输入:红楼梦txt,史记十二本纪txt
算法思路:1.特征提取。2.向量化。3.kmeans聚类
TF-IDF:一种统计方法,用以评估一字词对于一个文件集或一个语料库中的其中一份文件的重要程度。字词的重要性随着它在文件中出现的次数成正比增加,但同时会随着它在语料库中出现的频率成反比下降。(百科)
K-means算法:是很典型的基于距离的聚类算法,采用距离作为相似性的评价指标,即认为两个对象的距离越近,其相似度就越大。该算法认为簇是由距离靠近的对象组成的,因此把得到紧凑且独立的簇作为最终目标。(百科)
1.特征提取与向量化
# coding=utf-8
import re
import jieba
import jieba.posseg
import jieba.analyse
import numpy as np
import scipy as sp
import pandas as pd
import matplotlib.pyplot as plt
from sklearn import feature_extraction
from sklearn.cluster import KMeans
from sklearn.feature_extraction.text import TfidfTransformer
from sklearn.feature_extraction.text import CountVectorizer
from sklearn.decomposition import PCA
from sklearn.metrics import accuracy_score
from scipy.stats import mode # 本课程的重要package
# 数据预处理操作:分词,词性筛选
def dataPrepos(text):
l = []
# 定义选取词的词性为:动词、形容词、副词、介词、连词、助词、叹词、临时词
pos = ['v', 'a', 'd', 'p', 'c', 'u', 'e', 'l']
seg = jieba.posseg.cut(text) # 分词
for i in seg:
if i.word not in l and i.flag in pos: # 去重 + 词性筛选
# print i.word
l.append(i.word)
return l
def add_to_dict(characters_names):
for name in names:
jieba.add_word(name) # 把人名输入jieba词汇表
# 将红楼梦的章节分隔成不同的字符串并分别储存,使用前请先排除文档中出现的第某回的干扰
def chapsplit(text):
corpus = [] # 将所有文档输出到一个list中,一行就是一个文档
reg = "第[一二三四五六七八九十百]+回"
txt = re.split(reg, text)
txt = [i for i in txt if len(i) > 200]
for i in range(120):
text = dataPrepos(txt[i]) # 文本预处理
text = " ".join(text) # 连接成字符串,空格分隔
corpus.append(text)
return corpus
# tf-idf获取文本词汇的weight
def tfidf(corpus):
# 1、构建词频矩阵,将文本中的词语转换成词频矩阵
vectorizer = CountVectorizer()
X = vectorizer.fit_transform(corpus) # 词频矩阵,a[i][j]:表示j词在第i个文本中的词频
# 2、统计每个词的tf-idf权值
transformer = TfidfTransformer()
tfidf = transformer.fit_transform(X)
# 3、获取词袋模型中的关键词
word = vectorizer.get_feature_names()
# 4、获取tf-idf矩阵,a[i][j]表示j词在i篇文本中的tf-idf权重
weight = tfidf.toarray()
return weight
if __name__ == "__main__":
with open('高祖本纪.txt', encoding="utf-8") as f:
text = [f.read()]
with open('吕太后本纪.txt', encoding="utf-8") as f:
text = [f.read()]+text
with open('秦本纪.txt', encoding="utf-8") as f:
text = [f.read()]+text
with open('秦始皇本纪.txt', encoding="utf-8") as f:
text = [f.read()]+text
with open('五帝本纪.txt', encoding="utf-8") as f:
text = [f.read()]+text
with open('夏本纪.txt', encoding="utf-8") as f:
text = [f.read()]+text
with open('周本纪.txt', encoding="utf-8") as f:
text = [f.read()]+text
with open('殷本纪.txt', encoding="utf-8") as f:
text = [f.read()]+text
with open('孝文本纪.txt', encoding="utf-8") as f:
text = [f.read()]+text
with open('孝武本纪.txt', encoding="utf-8") as f:
text = [f.read()]+text
with open('孝景本纪.txt', encoding="utf-8") as f:
text = [f.read()]+text
with open('项羽本纪.txt', encoding="utf-8") as f:
text = [f.read()]+text
totalcorpus = [] # 将所有文档输出到一个list中,一行就是一个文档
for i in range(12):
text1 = dataPrepos(text[i]) # 文本预处理
text1 = " ".join(text1) # 连接成字符串,空格分隔
totalcorpus.append(text1)
with open('StoneStory.txt', encoding="utf-8") as f:
text = f.read()
with open('names.txt', encoding="utf-8") as f:
names = [line.strip('\n')
for line in f.readlines()] # 去除换行符
for name in names:
jieba.add_word(name) # 把人名输入jieba词汇表
totalcorpus = chapsplit(text)+totalcorpus # 章节分隔后并合并史记和红楼梦
weight = tfidf(totalcorpus)
具体算法思路不一一讲解,值得讨论的是几个小的处理。
1. 目前,用于文本关键词提取的主要方法有四种:基于TF-IDF的关键词抽取、基于TextRank的关键词抽取、基于Word2Vec词聚类的关键词抽取,以及多种算法相融合的关键词抽取。笔者最开始选择的是Word2vec的方法。但是在写代码的过程中发觉从词向量化向文章向量化的转化处理方法较为复杂且多样,诸如对文章中所有词的word vector求平均,获得sentence embedding等等,鉴于作用时间有限......于是还是选择了常用的tfidf的算法。
2.不去停用词:代码的思路是调用红楼的动词、形容词、副词、介词、连词、助词、叹词、临时词来计算weight,这是因为前后章节的出场人物不同,情节不同,即使是同一个作者来写,最后聚类的结果也很有可能是阳性的,而同一个作者使用的虚词,形容词等词汇,例如的,地,得等。即使情节不同,也不应有较大的差别。
2.PCA降维画画图
# 二维后图像化看一看,好像挺明显了
pca = PCA(2) # 降维到二维
projected = pca.fit_transform(weight)
print(weight.shape)
print(projected.shape)
label = 80*[0]+40*[1]+12*[2] #定义label,分别表示前八十回,后四十回和史记十二篇
plt.figure(facecolor="white")
plt.scatter(projected[:, 0], projected[:, 1],
c=label, edgecolor='none', alpha=1,
cmap=plt.cm.get_cmap('Spectral_r'))
plt.xlabel('component 1')
plt.ylabel('component 2')
plt.colorbar();
# print(pca.explained_variance_)
#output
#(132, 15772)
#(132, 2)未聚类的结果,已经很明显了吧!
紫色的为前八十回,黄色的为后四十回,红色的为史记。
3.kmeans聚类与评价
# kmeans聚类结果
plt.figure(facecolor="white")
labels = KMeans(3, random_state=0).fit_predict(projected) # 聚为三类
plt.scatter(projected[:, 0], projected[:, 1], c=labels,
s=50, cmap='viridis');聚类结果很好
# 假设文本为三类,前八十回,后四十回和史记十二篇
# 对有label的数据可以进行计算accuracy,先按照正确的label对cluster的名字改变
label=np.array(label) #转为array,便于使用mode函数
kmeans = KMeans(n_clusters=3, random_state=0)
clusters = kmeans.fit_predict(projected)
labels = np.zeros_like(clusters)
for i in range(3):
mask = (clusters == i)
labels[mask] = mode(label[mask])[0]
# Compute the accuracy
accuracy_score(label, labels)
#output
#0.9772727272727273
accuracy rate达到了98%,分类非常精准。
结果
红楼后四十回很明显与前八十回不同,这样看来,确系伪作。
讨论
以医学举例,我们也许会好奇那些孩子吃某种药的效果好,哪些孩子吃某种药的效果差。(有label)我们可能有一些让我们能完成分类的特征(也许有好几百维甚至好几千维)。如何能根据这些个体特征来尝试分类,并给出individual treatment,这也许可以尝试聚类的方法。
因为自己也是初学python和机器学习,水平有限,如有代码冗余或者思路错误烦请指出,也欢迎留言交流~