815 文本聚类

该博客介绍了如何使用nltk和sklearn库对IMDb前100部电影的简介进行文本聚类。首先,通过读取数据、预处理(tokenize, stem, 去除停用词)、构建词汇表并使用TF-IDF转换。接着,利用K均值算法进行聚类,并计算文本间余弦距离。最后,进行可视化展示,将电影在二维平面上分布,并以簇的颜色区分。博客中还包含了每个簇的中心单词和电影数量,以及可视化过程。
摘要由CSDN通过智能技术生成

tutorial 9,咕咕咕,今天又学习了呢
文本聚类,两个任务

1.聚类前100的imdb电影(什么是imdb?好像是一个什么评分网站)
关于这个任务KEATS给我们提供了三个文件夹“title_list.txt”, “synopses_list_imdb.txt”, “genres_list.txt”。其中第一个是电影标题,第二个是每个电影的简介,第三个是每个电影的类型(好麻烦,装在一个文件夹里不好吗)
这个任务的几大要点:

  • 导入相关包并读取数据(以list类型)
  • tokenize stem and remove stop word(使用nltk.stopwords 和 nltk.SnowballStemmer)
  • 构建两个清洗函数,一个实现tokenize,另一个仅对每个token做stem(虽然她是这样说的,但她写的代码好像不是这个意思。。。)
  • 使用tf-idf转换
  • 使用K均值算法做聚类
  • 计算文本间的余弦距离
  • 得出每个簇的文本数量
  • 找到每个簇的中心
  • 输出每个簇里出现最多的6个单词

2.可视化(optional)

  • 导入MDS包
  • 拟合数据并返回embedded coordinates(什么东西,自己百度吧,不重要)
  • 将coordinates保存为dataframe并将簇分组(?)
  • 使用matplotlib绘制title和其被指定的簇

直接看代码吧,不想努力了

# -*- coding: utf-8 -*-
"""
Created on Sun Apr 17 17:25:13 2022

@author: Pamplemousse
"""

from __future__ import print_function
#朋友们注意,上面这句一定要写在第一行
import re
import nltk
import pandas as pd
from nltk.stem.snowball import SnowballStemmer
from sklearn.feature_extraction.text import TfidfVectorizer
from sklearn.metrics.pairwise import cosine_similarity
from sklearn.cluster import KMeans
import matplotlib.pyplot as plt
from sklearn.manifold import MDS

然后是任务1里的两个清洗函数

def tokenize_and_stem(text):
    tokens = [word for sent in nltk.sent_tokenize(text) for word in nltk.word_tokenize(sent)]#首先对文本做tokenize得到很多tokens
    filtered_tokens = []
    
    for token in tokens:#对每一个token
        if re.search('[a-zA-Z]', token):
        #re.search就是在第二个输入内搜素第一个输入的内容,这里的意思就是在token里搜索所有大小写字母,也就是说只要token是有字母的,判断结果就是true
            filtered_tokens.append(token)#将所有带字母的token放入filtered_tokens里面
    stems = [stemmer.stem(t) for t in filtered_tokens]
    #这个就是比下一个函数多了这一句罢了
    return stems

def tokenize_only(text):
    tokens = [word.lower() for sent in nltk.sent_tokenize(text) for word in nltk.word_tokenize(sent)]
    filtered_tokens = []
    
    for token in tokens:
        if re.search('[a-zA-Z]', token):
            filtered_tokens.append(token)
    return filtered_tokens

任务1:
读取数据:

titles = open('title_list.txt').read().split('\n')
#这里.split('\n')的意思就是说按照换行符来读,每遇到换行就读为下一个title,说以说是读了很多个strings
#读取titles,按理来说我们应该得到100个titles,但是如果在这里print(len(titles))会得到101
#有可能是100th是并列的?
titles = titles[:100]
#所以这里取前100个,就是说最后一个被我们省略了,下面genres和synopses也是同样的操作

genres = open('genres_list.txt').read().split('\n')
genres = genres[:100]

print(str(len(titles)) + ' titles')
print(str(len(genres)) + ' genres')

synopses_imdb = open('synopses_list_imdb.txt').read().split('\n BREAKS HERE')
#这里split就不仅要有一个换行符还得读到一个BREAKS HERE才能算一个数据,大家去看看txt文件就明白了
synopses_imdb = synopses_imdb[:100]
synopses = synopses_imdb

tokenize, stem and remove stop words:

ranks = []

for i in range(0,len(titles)):
    ranks.append(i)#其实ranks就是从0到99,这100个数的数组

stopwords = nltk.corpus.stopwords.words('english')
#设置stopwords

stemmer = SnowballStemmer('english')
#设置stemmer

totalvocab_stemmed = []
totalvocab_tokenized = []

for i in synopses:#对于第i篇简介
    allwords_stemmed = tokenize_and_stem(i)#第i篇简介tokenize且stem后
    totalvocab_stemmed.extend(allwords_stemmed)
    #将第i篇简介清洗后的内容加入totalvocab_stemmed里面,最后这个变量里将存的是所有简介的vocab
    
    allwords_tokenized = tokenize_only(i)#这里类似
    totalvocab_tokenized.extend(allwords_tokenized)

使用tf_idf 转换

vocab_frame = pd.DataFrame({'words': totalvocab_tokenized}, index = totalvocab_stemmed)
#这一步就是构建一个vocab_frame,每一行的索引就是stemmed单词,而这个df只存了一列数据,是被命名为words的没有stemmed的单词

tfidf_vectorizer = TfidfVectorizer(max_df=0.8, max_features=200000, min_df=0.2, stop_words='english', use_idf=True, tokenizer=tokenize_and_stem, ngram_range=(1,3))
#构建转换方式,参数的意思前面可能基本都讲过,其他几个没讲过的也不是很重要的样子

tfidf_matrix = tfidf_vectorizer.fit_transform(synopses)#转换得到矩阵
print(tfidf_matrix.shape)#输出矩阵的大小

terms = tfidf_vectorizer.get_feature_names()#这里就是得到我们用来表示文章的词

这里的输出是(100,372),说明我们用了372个词来代表每一篇文章

计算文本建的余弦距离:

dist = 1 - cosine_similarity(tfidf_matrix)#余弦距离计算方式
print(dist)#输出余弦距离
print(dist.shape)

这里我们可以想到对角线上的数据应该是0,应该自己和自己的相似度是1,所以距离是0,但是我们的第一个输出是这样的:
[[-2.22044605e-16 7.39843068e-01 7.42136807e-01 … 5.88978770e-01
5.90809208e-01 1.00000000e+00]
[ 7.39843068e-01 -2.22044605e-16 6.88096748e-01 … 6.47278782e-01
6.75535808e-01 1.00000000e+00]
[ 7.42136807e-01 6.88096748e-01 -2.22044605e-16 … 7.10236540e-01
7.05472992e-01 1.00000000e+00]

[ 5.88978770e-01 6.47278782e-01 7.10236540e-01 … 0.00000000e+00
3.37913228e-01 1.00000000e+00]
[ 5.90809208e-01 6.75535808e-01 7.05472992e-01 … 3.37913228e-01
-2.22044605e-16 1.00000000e+00]
[ 1.00000000e+00 1.00000000e+00 1.00000000e+00 … 1.00000000e+00
1.00000000e+00 1.00000000e+00]]
可以看到对角线是不是0,其实这是可以解释的,因为我们的电脑双精度存储的问题,有些本来应该是0的数据会变成一个非常小的数据,就比如说-2.22044605e-16本来应该是0,但是这里我们可以看到他后面是e-16这是什么意思呢,就是10的-16次方,所以这是个很小的数,它就是0,我很难给你们解释清楚,这是电脑的问题

然后,第二个输出是(100,100),这个很合理嘛,因为是100个电影之间的相互关系嘛

使用K均值算法做聚类:

km = KMeans(n_clusters=num_clusters)#KMeans模型
km.fit(tfidf_matrix)#拟合模型

clusters = km.labels_.tolist()
#得出每一个电影的cluster分类,同一个簇的电影cluster的标识相同

输出每个簇的数量

films = { 'title': titles, 'rank': ranks, 'synopsis': synopses, 'cluster': clusters, 'genre': genres }
#构建一个字典,每一项包含title, rank, synopsis, cluster和genre,不明白可以print(films)一下

frame = pd.DataFrame(films, index = [clusters] , columns = ['rank', 'title', 'cluster', 'genre'])
#将字典转换为DataFrame,其中cluster一项的数据作为每一行的索引,其余4项作为内容

frame['cluster'].value_counts()#以frame的索引计算同一索引的数量,即计算每个簇的电影数,这里得到一个输出

得到如下输出:

1 37
0 24
4 18
3 15
2 6
Name: cluster, dtype: int64
可以看出电脑把五个簇简单地标记为01234,其中电影数量最多的是1号簇。。。

找到每个簇的中心

grouped = frame['rank'].groupby(frame['cluster'])
#这里就是相当于把排名拿出来,同一个簇的排名放在一起
grouped.mean()#然后对每个簇的排名做个均值

可以得到如下结果:
cluster
0 44.208333
1 49.513514
2 63.833333
3 36.733333
4 62.388889
Name: rank, dtype: float64

看得出来,我们第3个簇的电影综合排名很高

输出每个簇top6的单词

print("Top terms per cluster:")
print()
order_centroids = km.cluster_centers_.argsort()[:,::-1]#这里就是一个排序的操作,降序
for i in range(num_clusters):#第i个簇
    print("Cluster %d words:" %i, end='')
    for ind in order_centroids[i,:6]:#第i个簇的前6个index
        print(' %s' %vocab_frame.loc[terms[ind].split(' ')].values.tolist()[0][0].encode('utf-8', 'ignore'), end=',')
    print()
    print()
    print("Cluster %d titles:" %i, end='')
    for title in frame.loc[i]['title'].values.tolist():#输出第i个簇的所有电影title
        print(' %s,' %title, end='')
    print()
    print()

直接看结果吧

Top terms per cluster:

Cluster 0 words: b’family’, b’love’, b’war’, b’married’, b’life’, b’leave’,

Cluster 0 titles: The Godfather, Schindler’s List, Gone with the Wind, Citizen Kane, The Godfather: Part II, Forrest Gump, The Sound of Music, A Streetcar Named Desire, The Philadelphia Story, An American in Paris, The Best Years of Our Lives, My Fair Lady, Doctor Zhivago, The Apartment, The Pianist, Goodfellas, The King’s Speech, A Place in the Sun, Midnight Cowboy, Annie Hall, Out of Africa, Good Will Hunting, Giant, The Grapes of Wrath,

Cluster 1 words: b’tells’, b"n’t", b’car’, b’says’, b’asks’, b’leave’,

Cluster 1 titles: The Shawshank Redemption, Raging Bull, Casablanca, One Flew Over the Cuckoo’s Nest, The Wizard of Oz, Titanic, Psycho, Sunset Blvd., Vertigo, On the Waterfront, West Side Story, E.T. the Extra-Terrestrial, 2001: A Space Odyssey, Singin’ in the Rain, It’s a Wonderful Life, Some Like It Hot, Unforgiven, Rocky, To Kill a Mockingbird, Butch Cassidy and the Sundance Kid, The French Connection, Rain Man, Tootsie, Fargo, The Green Mile, Close Encounters of the Third Kind, Nashville, The Graduate, American Graffiti, Pulp Fiction, The Maltese Falcon, A Clockwork Orange, Taxi Driver, Double Indemnity, Rear Window, The Third Man, North by Northwest,

Cluster 2 words: b’water’, b’mr.’, b’father’, b’police’, b’tells’, b’landing’,

Cluster 2 titles: Chinatown, Jaws, The Exorcist, Mr. Smith Goes to Washington, The African Queen, Rebel Without a Cause,

Cluster 3 words: b’commanding’, b’soldiers’, b’killed’, b’men’, b’office’, b’army’,

Cluster 3 titles: Lawrence of Arabia, Star Wars, The Silence of the Lambs, The Bridge on the River Kwai, Dr. Strangelove or: How I Learned to Stop Worrying and Love the Bomb, Apocalypse Now, The Lord of the Rings: The Return of the King, From Here to Eternity, Saving Private Ryan, Raiders of the Lost Ark, Patton, The Treasure of the Sierra Madre, Platoon, Dances with Wolves, The Deer Hunter,

Cluster 4 words: b’son’, b’father’, b’warns’, b’eye’, b’only’, b’death’,

Cluster 4 titles: 12 Angry Men, Amadeus, Gandhi, Gladiator, Ben-Hur, Braveheart, The Good, the Bad and the Ugly, High Noon, All Quiet on the Western Front, City Lights, It Happened One Night, Terms of Endearment, Shane, Network, Stagecoach, Mutiny on the Bounty, Wuthering Heights, Yankee Doodle Dandy,

好,任务2
可视化

MDS()#一个降维工具,方便我们把100维的余弦距离转换为2为数据,以实现可视化

mds = MDS(n_components=2,dissimilarity="precomputed", random_state=1)
#n_components就是要得到的维度
pos = mds.fit_transform(dist)#使用我们之前计算的dist来拟合MDS
#pos是一个(100,2)的数组

xs, ys = pos[:,0], pos[:,1]#pos的第一项就是电影的x轴位置,第二项就是y轴位置

cluster_colors = {0: '#1b9e77', 1: '#d95f02', 2: '#7570b3', 3: '#e7298a', 4: '#66a61e'}
#给每一个簇定义一个颜色

cluster_names = cluster_names = {0: 'Family, home, war', 
                 1: 'Police, killed, murders', 
                 2: 'Father, New York, brothers', 
                 3: 'Dance, singing, love', 
                 4: 'Killed, soldiers, captain'}
#给每个簇定义一个名称

df = pd.DataFrame(dict(x=xs, y=ys, label=clusters, title=titles))
#把每一个电影的二维位置、簇标识和标题存在一个dataframe里面

groups = df.groupby('label')#得到不同的簇

fig, ax = plt.subplots(figsize=(17,9))
ax.margins(0.05)

for name, group in groups:#对每个簇逐一绘制legend
    ax.plot(group.x, group.y, marker='o', linestyle='', ms=12, label=cluster_names[name], color=cluster_colors[name], mec='none')
    ax.set_aspect('auto')
    ax.tick_params(axis='x',
                   which='both',
                   bottom='off',
                   top='off',
                   labelbottom='off')
    ax.tick_params(axis='y',
                   which='both',
                   left='off',
                   top='off',
                   labelleft='off')
ax.legend(numpoints=1)

for i in range(len(df)):
    ax.text(df.loc[i]['x'], df.loc[i]['y'], df.loc[i]['title'], size=8)
#画出每个点

plt.show()
plt.close()

在这里插入图片描述

饿了,下班!

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

端午节放纸鸢

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值