第8课:动手实战基于 ML 的中文短文本聚类

关于文本聚类,我曾在 Chat《NLP 中文文本聚类之无监督学习》中介绍过,文本聚类是将一个个文档由原有的自然语言文字信息转化成数学信息,以高维空间点的形式展现出来,通过计算哪些点距离比较近,从而将那些点聚成一个簇,簇的中心叫做簇心。一个好的聚类要保证簇内点的距离尽量的近,但簇与簇之间的点要尽量的远。

如下图,以 K、M、N 三个点分别为聚类的簇心,将结果聚为三类,使得簇内点的距离尽量的近,但簇与簇之间的点尽量的远。

enter image description here

开发环境,我们选择:

  1. Windows 系统
  2. Python 3.6
  3. Jupyter Notebook

本文继续沿用上篇文本分类中的语料来进行文本无监督聚类操作。

整个过程分为以下几个步骤

  • 语料加载
  • 分词
  • 去停用词
  • 抽取词向量特征
  • 实战 TF-IDF 的中文文本 K-means 聚类
  • 实战 word2Vec 的中文文本 K-means 聚类

下面开始项目实战。

1. 首先进行语料加载,在这之前,引入所需要的 Python 依赖包,并将全部语料和停用词字典读入内存中。

第一步,引入依赖库,有随机数库、jieba 分词、pandas 库等:

    import random
    import jieba
    import pandas as pd
    import numpy as np
    from sklearn.feature_extraction.text import TfidfTransformer
    from sklearn.feature_extraction.text import TfidfVectorizer
    import matplotlib.pyplot as plt
    from sklearn.decomposition import PCA
    from sklearn.cluster import KMeans
    import gensim
    from gensim.models import Word2Vec
    from sklearn.preprocessing import scale
    import multiprocessing

第二步,加载停用词字典,停用词词典为 stopwords.txt 文件,可以根据场景自己在该文本里面添加要去除的词(比如冠词、人称、数字等特定词):

    #加载停用词
    stopwords=pd.read_csv('stopwords.txt',index_col=False,quoting=3,sep="\t",names=['stopword'], encoding='utf-8')
    stopwords=stopwords['stopword'].values

第三步,加载语料,语料是4个已经分好类的 csv 文件,直接用 pandas 加载即可,加载之后可以首先删除 nan 行,并提取要分词的 content 列转换为 list 列表:

    #加载语料
    laogong_df = pd.read_csv('beilaogongda.csv', encoding='utf-8', sep=',')
    laopo_df = pd.read_csv('beilaogongda.csv', encoding='utf-8', sep=',')
    erzi_df = pd.read_csv('beierzida.csv', encoding='utf-8', sep=',')
    nver_df = pd.read_csv('beinverda.csv', encoding='utf-8', sep=',')
    #删除语料的nan行
    laogong_df.dropna(inplace=True)
    laopo_df.dropna(inplace=True)
    erzi_df.dropna(inplace=True)
    nver_df.dropna(inplace=True)
    #转换
    laogong = laogong_df.segment.values.tolist()
    laopo = laopo_df.segment.values.tolist()
    erzi = erzi_df.segment.values.tolist()
    nver = nver_df.segment.values.tolist()

2. 分词和去停用词。

第一步,定义分词、去停用词的函数,函数包含两个参数:content_lines 参数为语料列表;sentences 参数为预先定义的 list,用来存储分词后的结果:

    #定义分词函数preprocess_text
    #参数content_lines即为上面转换的list
    #参数sentences是定义的空list,用来储存分词后的数据

    def preprocess_text(content_lines, sentences):
        for line in content_lines:
            try:
                segs=jieba.lcut(line)
                segs = [v for v in segs if not str(v).isdigit()]#去数字
                segs = list(filter(lambda x:x.strip(), segs))   #去左右空格
                segs = list(filter(lambda x:len(x)>1, segs)) #长度为1的字符
                segs = list(filter(lambda x:x not in stopwords, segs)) #去掉停用词
                sentences.append(" ".join(segs))
            except Exception:
                print(line)
                continue 

第二步,调用函数、生成训练数据,根据我提供的司法语料数据,分为报警人被老公打,报警人被老婆打,报警人被儿子打,报警人被女儿打,具体如下:

    sentences = []
    preprocess_text(laogong, sentences)
    preprocess_text(laopo, sentences)
    preprocess_text(erzi, sentences)
    preprocess_text(nver, sentences)

第三步,将得到的数据集打散,生成更可靠的训练集分布,避免同类数据分布不均匀:

    random.shuffle(sentences)

第四步,我们控制台输出前10条数据,观察一下(因为上面进行了随机打散,你看到的前10条可能不一样):

    for sentence in sentences[:10]:
        print(sentenc)

得到的结果聚类和分类是不同的,这里没有标签:

enter image description here

3. 抽取词向量特征。

抽取特征,将文本中的词语转换为词频矩阵,统计每个词语的 tf-idf 权值,获得词在对应文本中的 tf-idf 权重:

    #将文本中的词语转换为词频矩阵 矩阵元素a[i][j] 表示j词在i类文本下的词频
    vectorizer = TfidfVectorizer(sublinear_tf=True, max_df=0.5)
    #统计每个词语的tf-idf权值
    transformer = TfidfTransformer()
    # 第一个fit_transform是计算tf-idf 第二个fit_transform是将文本转为词频矩阵
    tfidf = transformer.fit_transform(vectorizer.fit_transform(sentences))
    # 获取词袋模型中的所有词语
    word = vectorizer.get_feature_names()
    # 将tf-idf矩阵抽取出来,元素w[i][j]表示j词在i类文本中的tf-idf权重
    weight = tfidf.toarray()
    #查看特征大小
    print ('Features length: ' + str(len(word)))

4. 实战 TF-IDF 的中文文本 K-means 聚类

第一步,使用 k-means++ 来初始化模型,当然也可以选择随机初始化,即 init="random",然后通过 PCA 降维把上面的权重 weight 降到10维,进行聚类模型训练:

    numClass=4 #聚类分几簇
    clf = KMeans(n_clusters=numClass, max_iter=10000, init="k-means++", tol=1e-6)  #这里也可以选择随机初始化init="random"
    pca = PCA(n_components=10)  # 降维
    TnewData = pca.fit_transform(weight)  # 载入N维
    s = clf.fit(TnewData)

第二步,定义聚类结果可视化函数 plot_cluster(result,newData,numClass),该函数包含3个参数,其中 result 表示聚类拟合的结果集;newData 表示权重 weight 降维的结果,这里需要降维到2维,即平面可视化;numClass 表示聚类分为几簇,绘制代码第一部分绘制结果 newData,第二部分绘制聚类的中心点:

    def plot_cluster(result,newData,numClass):
        plt.figure(2)
        Lab = [[] for i in range(numClass)]
        index = 0
        for labi in result:
            Lab[labi].append(index)
            index += 1
        color = ['oy', 'ob', 'og', 'cs', 'ms', 'bs', 'ks', 'ys', 'yv', 'mv', 'bv', 'kv', 'gv', 'y^', 'm^', 'b^', 'k^',
                 'g^'] * 3 
        for i in range(numClass):
            x1 = []
            y1 = []
            for ind1 in newData[Lab[i]]:
                # print ind1
                try:
                    y1.append(ind1[1])
                    x1.append(ind1[0])
                except:
                    pass
            plt.plot(x1, y1, color[i])

        #绘制初始中心点
        x1 = []
        y1 = []
        for ind1 in clf.cluster_centers_:
            try:
                y1.append(ind1[1])
                x1.append(ind1[0])
            except:
                pass
        plt.plot(x1, y1, "rv") #绘制中心
        plt.show()

第三步,对数据降维到2维,然后获得结果,最后绘制聚类结果图:

    pca = PCA(n_components=2)  # 输出两维
    newData = pca.fit_transform(weight)  # 载入N维
    result = list(clf.predict(TnewData))
    plot_cluster(result,newData,numClass)

第四步,得到的聚类结果图,4个中心点和4个簇,我们看到结果还比较好,簇的边界很清楚:

enter image description here

第五步,上面演示的可视化过程,降维使用了 PCA,我们还可以试试 TSNE,两者同为降维工具,主要区别在于,所在的包不同(也即机制和原理不同):

    from sklearn.decomposition import PCA
    from sklearn.manifold import TSNE

因为原理不同,导致 TSNE 保留下的属性信息,更具代表性,也即最能体现样本间的差异,但是 TSNE 运行极慢,PCA 则相对较快,下面看看 TSNE 运行的可视化结果:

    from sklearn.manifold import TSNE
    ts =TSNE(2)
    newData = ts.fit_transform(weight)
    result = list(clf.predict(TnewData))
    plot_cluster(result,newData,numClass)

得到的可视化结果,为一个中心点,不同簇落在围绕中心点的不同半径之内,我们看到在这里结果并不是很好:

enter image description here

第六步,为了更好的表达和获取更具有代表性的信息,在展示(可视化)高维数据时,更为一般的处理,常常先用 PCA 进行降维,再使用 TSNE:

    from sklearn.manifold import TSNE
    newData = PCA(n_components=4).fit_transform(weight)  # 载入N维
    newData =TSNE(2).fit_transform(newData)
    result = list(clf.predict(TnewData))
    plot_cluster(result,newData,numClass)

得到的可视化结果,不同簇落在围绕中心点的不同半径之内:

enter image description here

总结

上面通过真实小案例,对司法数据一步步实现中文短文本聚类,从优化和提高模型准确率来说,主要有两方面可以尝试:

  1. 特征向量的构建,除了词袋模型,可以考虑使用 word2vec 和 doc2vec 等;
  2. 模型上可以采用基于密度的 DBSCAN、层次聚类等算法。

如有侵权请联系QQ:758230255删除

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 205,132评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,802评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,566评论 0 338
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,858评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,867评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,695评论 1 282
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,064评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,705评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 42,915评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,677评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,796评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,432评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,041评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,992评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,223评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,185评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,535评论 2 343

推荐阅读更多精彩内容