机器学习-无监督聚类-KMeans-人工智能-AI算法

92 阅读4分钟
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}")