import numpy as np
# 引入numpy 科学计算的包,起个别名叫np
import jieba
# 引入jieba分词器
from sklearn.feature_extraction.text import TfidfVectorizer
# 将文本数据转换为数值特征矩阵, 2种文本特征提取方式
# TF-IDF(词频-逆文档频率) / 词袋模型(Bag-of-Words)
def cosine_similarity(vecA, vecB):
# 余弦测距的公式 向量点积 除以 向量各自范数的积
return np.dot(vecA, vecB) / (np.linalg.norm(vecA) * np.linalg.norm(vecB))
def rand_centroids(data, k):
# data.shape (10, 38)
# len(data) 多少行(一个数字),在这个数字最大范围内,随机k个数字出来(很好理解)
indices = np.random.choice(len(data), k, replace=False)
# 假设 len(data)=10, k=3, indices = [2、6、7] / [1、2、9] / [0、1、8] / [2、3、8] 随机3个下标出来
centroids = data[indices]
# 那么centroids 就是总数据中的随机三条数据(3, 38)3行38个特征,很好理解
return centroids / np.linalg.norm(centroids, axis=1, keepdims=True)
def kmeans_spherical(X, k, max_iter=100):
# 第一步,先初始化质心,(3, 38)3条数据,每条38个特征
centroids = rand_centroids(X, k)
# 然后一遍一遍的比较每个数据到每个中心点的距离
for _ in range(max_iter):
# 10行38列 点乘 38行3列(数据中心被放倒了嘛(转置)) 等于 【10行3列】
# 第i条数据和第k个中心的点的相似度
# 若都已经归一化,那点乘等价于余弦相似度 similarities[i,j] = X[i]⋅centroids[j] = cos(θ)
similarities = np.dot(X, centroids.T)
# labels这个变量里存在每条数据距离哪个中心点最近的信息(list数据)
# (10行3列) 把每一行中最大的那一项的 列下标 取出来 [0 0 2 1 0 1 0 0 0 0]
# 第1条离第1个中心点近,第2条离第1个中心点近,第3条离第3个中心点近...
labels = np.argmax(similarities, axis=1)
# 更新质心
# 当前条数据会与每一个中心点比较
# 把所有数据(10条)距离当前中心点距离是否最近,转换成以布尔数组的表现形式
# (labels == i)[False False False False True False False True True False]
# 在全量数据中拿出距离当前中心点(k)最近的数据集
# X[labels == i] 的shape 是(x行,38列)
# .mean axios=0, 以列取平均(取特征的平均,那不就是中心点嘛)
new_centroids = np.array([X[labels == i].mean(axis=0) for i in range(k)])
# 均值计算之后重新归一化质心
new_centroids /= np.linalg.norm(new_centroids, axis=1, keepdims=True)
# 检查收敛 判断两个数组是否在允许的误差范围内近似相等
if np.allclose(centroids, new_centroids):
#相等就退出
break
# 否则更新质心
centroids = new_centroids
return centroids, labels
if __name__ == '__main__':
# 更清晰的训练数据
corpus = [
'罗森便利店周年庆大促销', # 便利店类
'全家便利店推出新便当', # 便利店类
'7-11便利店咖啡买一送一', # 便利店类
'欧冠决赛巴黎对拜仁', # 足球类
'英超联赛最新积分榜', # 足球类
'NBA季后赛勇士晋级西部决赛', # 篮球类
'便利店加盟优惠政策发布', # 便利店类
'世界杯预选赛亚洲区赛程', # 足球类
'CBA总决赛辽宁夺冠', # 篮球类
'便利店冷藏商品管理指南' # 便利店类
]
# 中文分词处理
jieba.add_word('便利店') # 确保"便利店"作为一个整体分词
corpus = [' '.join(jieba.lcut(doc)) for doc in corpus]
# 从jieba分词器里面出来,每条文本都被空格分词了
# [
# '罗森 便利店 周年 庆 大 促销',
# '全家 便利店 推出 新 便当',
# '7 - 11 便利店 咖啡 买一送一',
# '欧冠 决赛 巴黎 对 拜仁',
# '英超 联赛 最新 积分榜',
# 'NBA 季后赛 勇士 晋级 西部 决赛',
# '便利店 加盟 优惠政策 发布',
# '世界杯 预选赛 亚洲区 赛程',
# 'CBA 总决赛 辽宁 夺冠',
# '便利店 冷藏 商品 管理 指南'
# ]
# TF-IDF向量化
tfidf = TfidfVectorizer()
X = tfidf.fit_transform(corpus).toarray()
# 数据通过TF-IDF向量化后,变成了38个特征(38列)
# X_simulated = np.array([
# [0.12, 0., 0., 0., 0.89, ...], 文档1:"罗森便利店周年庆大促销" → "便利店"词权重大
# [0., 0.34, 0., 0., 0.76, ...], 文档2:"全家便利店推出新便当" → "便利店"、"便当"相关
# [0., 0., 0., 0., 0.62, ...], 文档3:其他便利店内容
# [0., 0., 0.91, 0., 0., ...], 文档4:"欧冠决赛巴黎对拜仁" → "欧冠"词权重大
# [0., 0., 0., 0.45, 0., ...], 文档5:"英超联赛最新积分榜" → "英超"相关
# [0., 0., 0., 0., 0., ...], 文档6:篮球相关内容(其他特征有值)
# [0.08, 0., 0., 0., 0.53, ...], 文档7:便利店加盟
# [0., 0., 0.72, 0., 0., ...], 文档8:世界杯预选赛
# [0., 0., 0., 0., 0., ...], 文档9:CBA总决赛
# [0., 0.23, 0., 0., 0.41, ...] 文档10:便利店冷藏
# ])
# 计划分成3类
k = 3
centroids, labels = kmeans_spherical(X, k)
# 打印聚类结果
print("聚类结果:")
for i in range(k):
print(f"\n簇{i}包含文档:")
for idx, (doc, label) in enumerate(zip(corpus, labels)):
if label == i:
print(f"{idx + 1}. {doc}")