• 相似文本聚类与调参


    📢作者: 小小明-代码实体

    📢博客主页:https://blog.csdn.net/as604049322

    📢欢迎点赞 👍 收藏 ⭐留言 📝 欢迎讨论!

    在这里插入图片描述

    之前我在《批量模糊匹配的三种方法》一文中讲述了如何匹配最相似文本的方法,其中使用Gensim进行批量模糊匹配,是使用了稀疏的词向量计算相似度,速度相对前面的方法极快。

    去年我有使用sklearn做过文本聚类,今天我就给大家演示一下如何在一大堆文本中自动寻找出相似的文本进行聚类,主要思路有:

    1. 将每个文本进行分词
    2. 根据词频或TF-IDF生成词向量
    3. 使用DBSCAN聚类算法对词向量矩阵计算余弦相似度并连接聚类

    之前使用Gensim计算出的词频向量无法直接作为sklearn库的输入,需要进行如下转换为专门的稀疏矩阵对象:

    from scipy import sparse
    
    data, rows, cols = [], [], []
    for i, row in enumerate(data_corpus):
        for e, c in row:
            rows.append(i)
            cols.append(e)
            data.append(c)
    data = sc.csr_matrix((data, (rows, cols)))
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    为了方便,今天词向量和聚类算法都使用sklearn库,不再使用Gensim库。

    首先我们读取测试数据:

    import pandas as pd
    import numpy as np
    import jieba
    
    df = pd.read_csv("所有客户.csv", encoding="gbk")
    
    • 1
    • 2
    • 3
    • 4
    • 5

    中文分词

    分词的好坏会直接影响后续词向量的表现,我们可以根据数据集的情况自定义词汇,例如我根据前20条数据的情况增加相应的药店品牌:

    words = ["元岗", "铭心堂", "金健民", "祺和", "钜富", "杏园春", "天平民康"]
    for word in words:
        jieba.add_word(word)
    
    • 1
    • 2
    • 3

    如果我们需要分词的数据集存在停用词,可以使用如下代码将所有非中文字符(不含几个生僻字)删除:

    df.user = df.user.str.replace("[^一-龟]+", "", regex=True)
    
    • 1

    然后执行分词并查看结果:

    data_split_word = df.user.apply(jieba.lcut).apply(" ".join)
    data_split_word.head(20)
    
    • 1
    • 2
    0             [珠海, 广药, 康鸣, 医药, 有限公司]
    1                   [深圳市, 宝安区, 中心医院]
    2              [中山, 火炬, 开发区, 伴康, 药店]
    3                [中山市, 同方, 医药, 有限公司]
    4         [广州市, 天河区, 元岗, 金健民, 医药, 店]
    5            [广州市, 天河区, 元岗, 居健堂, 药房]
    6             [广州市, 天河区, 元岗, 润佰, 药店]
    7             [广州市, 天河区, 元岗, 协心, 药房]
    8             [广州市, 天河区, 元岗, 心怡, 药店]
    9            [广州市, 天河区, 元岗, 永亨堂, 药店]
    10        [广州市, 天河区, 员村, 德晖, 中, 西药店]
    11    [广州市, 天河区, 员村, 东兴, 堂昌, 乐园, 药店]
    12        [广州市, 天河区, 员村, 合家欢, 大, 药房]
    13        [广州市, 天河区, 员村, 慷乐, 医药, 商店]
    14        [广州市, 天河区, 员村, 为民, 永康, 药店]
    15        [广州市, 天河区, 珠江, 新城, 钜富, 药店]
    16        [广州市, 天河区, 珠江, 新城, 祺和, 药店]
    17       [广州市, 天河区, 珠江, 新城, 杏园春, 药店]
    18            [广州市, 天河, 沙河, 云芝, 中药店]
    19               [广州市, 天河, 天平民康, 药店]
    Name: user, dtype: object
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21

    当前前20条数据勉强算是分词达标。

    当然明显药房和药店这些词的含义相同,如果我需要进一步减少影响,完全可以批量将其替换为一致的词汇。

    创建词频向量

    然后我们需要使用sklearn建立词频向量:

    from sklearn.feature_extraction.text import CountVectorizer
    
    count = CountVectorizer()
    title_vec = count.fit_transform(data_split_word)
    
    • 1
    • 2
    • 3
    • 4

    注意:如果我们需要使用TF-IDF模型当某个词在多个文本中出现时降低其权重,直接将CountVectorizer修改为TfidfVectorizer即可。

    title_vec是个稀疏矩阵:<43487x24882 sparse matrix of type '' with 224221 stored elements in Compressed Sparse Row format>

    可以看看稀疏矩阵的结果:

    print(title_vec[:2])
    
    • 1
      (0, 17283)	1
      (0, 10626)	1
      (0, 11177)	1
      (0, 5944)	1
      (0, 13505)	1
      (1, 16416)	1
      (1, 9475)	1
      (1, 2833)	1
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    SciPy 稀疏矩阵:https://www.runoob.com/scipy/scipy-sparse-matrix.html

    DBSCAN原理简介

    img

    DBSCAN的核心参数是eps和min_samples,eps表示上图圆的半径,如果两个词向量的距离小于eps就会被连接在一起,而一个点能否成为中心点取决于圆内的点是否超过min_samples。

    如何计算两个词向量之间的距离呢?sklearn内置了各种公式。

    各类距离公式可查阅:https://xiao-xiaoming.github.io/DataMiningGuide/#/chapter-2

    下面编写一个方法用于测试各种公式计算距离:

    from sklearn.metrics import pairwise_distances
    
    
    def calc_distance(i, j, metric="l1"):
        x, y = title_vec[i].toarray(), title_vec[j].toarray()
        print(i, j, data_split_word[i], "|", data_split_word[j],
              pairwise_distances(x, y, metric=metric)[0][0])
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    先测试曼哈顿距离:

    for i, j in combinations(range(3, 11), 2):
        calc_distance(i, j)
    
    • 1
    • 2
    3 4 中山市 同方 医药 有限公司 | 广州市 天河区 元岗 金健民 医药 店 7.0
    3 5 中山市 同方 医药 有限公司 | 广州市 天河区 元岗 居健堂 药房 9.0
    3 6 中山市 同方 医药 有限公司 | 广州市 天河区 元岗 润佰 药店 9.0
    3 7 中山市 同方 医药 有限公司 | 广州市 天河区 元岗 协心 药房 9.0
    3 8 中山市 同方 医药 有限公司 | 广州市 天河区 元岗 心怡 药店 9.0
    3 9 中山市 同方 医药 有限公司 | 广州市 天河区 元岗 永亨堂 药店 9.0
    3 10 中山市 同方 医药 有限公司 | 广州市 天河区 员村 德晖 中 西药店 9.0
    4 5 广州市 天河区 元岗 金健民 医药 店 | 广州市 天河区 元岗 居健堂 药房 4.0
    4 6 广州市 天河区 元岗 金健民 医药 店 | 广州市 天河区 元岗 润佰 药店 4.0
    4 7 广州市 天河区 元岗 金健民 医药 店 | 广州市 天河区 元岗 协心 药房 4.0
    4 8 广州市 天河区 元岗 金健民 医药 店 | 广州市 天河区 元岗 心怡 药店 4.0
    4 9 广州市 天河区 元岗 金健民 医药 店 | 广州市 天河区 元岗 永亨堂 药店 4.0
    4 10 广州市 天河区 元岗 金健民 医药 店 | 广州市 天河区 员村 德晖 中 西药店 6.0
    5 6 广州市 天河区 元岗 居健堂 药房 | 广州市 天河区 元岗 润佰 药店 4.0
    5 7 广州市 天河区 元岗 居健堂 药房 | 广州市 天河区 元岗 协心 药房 2.0
    5 8 广州市 天河区 元岗 居健堂 药房 | 广州市 天河区 元岗 心怡 药店 4.0
    5 9 广州市 天河区 元岗 居健堂 药房 | 广州市 天河区 元岗 永亨堂 药店 4.0
    5 10 广州市 天河区 元岗 居健堂 药房 | 广州市 天河区 员村 德晖 中 西药店 6.0
    6 7 广州市 天河区 元岗 润佰 药店 | 广州市 天河区 元岗 协心 药房 4.0
    6 8 广州市 天河区 元岗 润佰 药店 | 广州市 天河区 元岗 心怡 药店 2.0
    6 9 广州市 天河区 元岗 润佰 药店 | 广州市 天河区 元岗 永亨堂 药店 2.0
    6 10 广州市 天河区 元岗 润佰 药店 | 广州市 天河区 员村 德晖 中 西药店 6.0
    7 8 广州市 天河区 元岗 协心 药房 | 广州市 天河区 元岗 心怡 药店 4.0
    7 9 广州市 天河区 元岗 协心 药房 | 广州市 天河区 元岗 永亨堂 药店 4.0
    7 10 广州市 天河区 元岗 协心 药房 | 广州市 天河区 员村 德晖 中 西药店 6.0
    8 9 广州市 天河区 元岗 心怡 药店 | 广州市 天河区 元岗 永亨堂 药店 2.0
    8 10 广州市 天河区 元岗 心怡 药店 | 广州市 天河区 员村 德晖 中 西药店 6.0
    9 10 广州市 天河区 元岗 永亨堂 药店 | 广州市 天河区 员村 德晖 中 西药店 6.0
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28

    image-20220803224943924

    从目前的数据来看,可以认为曼哈顿距离在4以内的认为相似。

    for i, j in combinations(range(3, 11), 2):
        calc_distance(i, j, "l2")
    
    • 1
    • 2

    而欧几里得距离下,可以认为距离在2以内的相似:

    image-20220803225527716

    而余弦相似度距离下或许应该设置距离0.4以内认为相似:

    image-20220803225751804

    DBSCAN聚类

    今天我们就使用余弦相似度进行聚类:

    from sklearn.cluster import DBSCAN
    
    model = DBSCAN(eps=0.4, min_samples=5, metric="cosine")
    model.fit(title_vec)
    df['label'] = model.labels_
    
    • 1
    • 2
    • 3
    • 4
    • 5

    结果中标签-1未找到相似文本的节点,标签0往往表示大量的数据都被聚类到了这里面:

    print("未被聚类:", df.query("label==-1").shape[0])
    print("都被聚在一起:", df.query("label==0").shape[0])
    print("正常的聚类:", df.query("label not in (-1,0)").shape[0])
    print("产生类别数:", df.label.nunique()-2)
    
    • 1
    • 2
    • 3
    • 4
    未被聚类: 5727
    都被聚在一起: 35840
    正常的聚类: 1920
    产生类别数: 203
    
    • 1
    • 2
    • 3
    • 4

    我们可以不断调整eps和metric得到更准确的结果。下面我们预览一下成功聚类的部分的聚类结果:

    for label, df_split in df.query("label not in (-1,0)").groupby("label"):
        print(label, df_split.user.to_list())
        if label > 50:
            break
    
    • 1
    • 2
    • 3
    • 4

    image-20220803231959828

    个人觉得结果还算达标~

    当然,这种文本聚类问题不可能100%结果完全准确,这需要大家根据数据集的情况,细化到每个步骤去调整,最好能够自定义距离计算公式(metric参数支持传入自动化函数)~

    相关参考:

    使用sklearn处理经纬度的三种距离计算与地图可视化

    https://xxmdmst.blog.csdn.net/article/details/117307759

  • 相关阅读:
    接口流量突增,如何做好性能调优?
    Spring Boot中的SSE与缓存集成:使用Redis加速事件推送
    DVWA之SQL注入
    [Docker]二.Docker 镜像,仓库,容器介绍以及详解
    TCP/IP五层协议栈(3)
    如何在Excel中自动创建报告 Excel中自动创建报告的方法
    Spring教程
    Linux系统使用Docker安装DockerUI并实现远程管理本地容器无需公网IP
    Flutter “Hello world“ 程代码
    蚁剑加密 WebShell 过杀软
  • 原文地址:https://blog.csdn.net/as604049322/article/details/126150893