知乎话题文本分词后的共词矩阵怎样引入词语距离因素

2024-1-22 16:43| 发布者: Fuller| 查看: 2833| 评论: 0

摘要: 本Notebook使用Gooseeker文本分词和情感分析软件导出的分词效果和共词矩阵excel表格,在python下对共现词的距离进行计算并对共词矩阵excel表格进行修改,修改后的表格会以一个新的名称保存在data/processed目录下。 ...

1  背景介绍

1.1  实验目的

在《使用GooSeeker分词和Gephi进行中文文本分析和社会网络分析》这篇文章中,我们使用从GooSeeker分词和情感分析软件工具生成并导出的共词矩阵“共词矩阵-知乎-二舅.xlsx”,在Gephi中导入该excel表格存储的共词矩阵,生成网络图。

Gephi提供了强大的统计、过滤和网络图的布局功能。我们可以定义一些统计指标,应用到网络图的过滤,这样可以突出观察一些信息,也可以根据某种策略对节点和边进行排布,使图形既具有特定需要的合理性,也易于视觉识别。

图1. 共词矩阵

图2. gephi布局图

1.1.1  什么是词语距离因素

上周在技术交流群里,有同学提出这样的问题:共词矩阵生成时能否考虑共现词之间的位置距离?比如2个词的距离小于某个值时才算作共现词。

那么什么是位置距离呢? 下面用一个例子说明:

对于文本:“一千个读者有一千个哈姆雷特 ,从编剧角度来谈这说的是作品内容的二次创作。二次创作不仅是作者对现实再加工,也有读者个人理解。所以多次加工后,每个人都能在二舅中找到些什么,这是二舅视频火热的原因,因为原作者蹩脚的留白,给予观众足够的空间去遐想”。

使用GooSeeker分词工具进行中文分词后,得到的以空格作为分隔符的原始词语序列为:“一千 个 读者 有 一千 个 哈姆雷特 从 编剧 角度 来 谈 这 说 的 是 作品 内容 的 二 次 创作 二 次 创作 不仅 是 作者 对 现实 再 加工 也 有 读者 个人 理解 所以 多次 加工 后 每个 人 都能 在 二舅 中 找到 些 什么 这 是 二舅 视频 火热 的 原因 因为 原 作者 蹩脚 的 留 白 给予 观众 足够 的 空间 去 遐想”

对于开头的3个词:一千,个,读者

“一千”和“个”的位置距离就是0, 而“一千”和“读者”之间的位置距离就是1。而“读者”和“蹩脚”之间的位置距离就是31。

假如我们设置本次实验中共现词之间的距离必须小于5,那么“一千”和“读者”就是共现词,而“读者”和“蹩脚”就不算共现词。

1.1.2  为什么要考虑词语距离因素

基于原始文本自动分词得到的结果,词的数量很大,很多词没有分析价值,除了增加处理的难度,而且会干扰分析结果的合理性。实际生成共现矩阵时,GooSeeker分词工具提供了人工选词的功能,可以根据不同的研究目的自行裁量使用哪些词。也提供了按词性或词频大小进行快速过滤的功能。假如2个词在人工选词或过滤阶段已经被过滤掉,那么也不会算作共现词。

但是,如果被分析的文本长短不一,甚至相差悬殊,例如,知乎网站上的回答,有些回答是一篇相当长篇幅的文章。在这样的长文本中,词语共现的可能性极大。但是,很多词其实隶属于不同的段落,为了表达不同的主题。解决这个问题的方法是可以规定不同的内容分析粒度,比如,统一使用段落粒度或者句子粒度,而不用整篇粒度。而本notebook通过规定词语距离,可以自动化地解决远距离不相关词语的剔除问题。

图3. 选词

今天实验的目的,就是怎样使用python修改GooSeeker分词工具生成并导出的共词矩阵,把2个词的位置距离考虑进来,预设某个距离值,如果2个词在某条文本中共现但是距离值大于这个预置值从共词矩阵的共现数里面减去1,小于等于这个预设距离值则共词矩阵的共现数不变。修改后的结果可以比较符合某些特定的研究目的。

1.2  本notebook内容:

  • 使用GooSeeker文本分词和情感分析软件将文本进行分词并进行人工选词和过滤。
  • 开启共词匹配功能。
  • 导出“分词效果表”,“共词矩阵表”。
  • 使用python对导出的“共词矩阵表”进行修改后保存到一个新的excel表格中,基于预设的某个词语距离值,大于这个预置值从共词矩阵的共现数里面减去,小于等于这个预设距离值的共现词保留。

1.3  实验数据来源

使用知乎_独立话题动态内容采集这个快捷采集,爬取知乎上关于二舅话题的讨论,导出excel。把该excel导入GooSeeker文本分词和情感分析软件

1.4  实验数据预处理:中文文本分词和选词

将采集得到的知乎二舅话题的excel,导入到Gooseeker文本分词和情感分析软件,经过自动分词,人工选词和过滤,共词匹配后,导出“分词效果表”,“共词矩阵表”:

图4. 分词工具导出

1.5  本notebook使用方法

基本操作顺序是:

  1. GooSeeker分词和文本分析软件上创建分析任务并导入包含原始内容的excel
  2. 在自动分词完成后进行人工选词,然后导出“分词效果表”,“共词矩阵表”
  3. 将导出的选词匹配表放在本notebook的data/raw文件夹中
  4. 从头到尾执行本notebook的单元
  5. 在本notebook的data/processed文件夹中新生成“共词矩阵已参考距离值-xxxxxx.xlsx”,该表和原有的“共词矩阵表”结构一样,不过矩阵中的数值经过了修改。

注意:每个notebook项目目录都预先规划好了,具体参看Jupyter Notebook项目目录规划参考。如果要做多个分析项目,把整个模板目录专门拷贝一份给每个分析项目。

2  准备程序环境

导入必要的Python程序包,设定要分析的文件名变量。在这一系列notebook中,我们都使用以下变量对应GooSeeker分词结果表:

  • file_word_freq:词频表
  • file_seg_effect: 分词效果表
  • file_word_choice_matrix: 选词矩阵表
  • file_word_choice_match: 选词匹配表
  • file_word_choice_result: 选词结果表
  • file_co_word_matrix: 共词矩阵表
  • file_co_occ_matrix_modified:共词矩阵引入词语距离后的表

2.1  导入程序包并声明变量

import pandas as pd

import os

import numpy as np

import networkx as nx

import matplotlib.pyplot as plt

import pylab

"""

from sklearn.cluster import KMeans, MiniBatchKMeans

from sklearn.decomposition import PCA

from sklearn.decomposition import TruncatedSVD

from sklearn.feature_extraction.text import TfidfVectorizer

from sklearn.feature_extraction.text import HashingVectorizer

from sklearn.feature_extraction.text import TfidfTransformer

from sklearn.pipeline import make_pipeline

from sklearn.preprocessing import Normalizer

from sklearn import metrics

"""

from time import time

import datetime

%xmode Verbose

import warnings

warnings.filterwarnings("ignore", category=DeprecationWarning)

# 存原始数据的目录

raw_data_dir = os.path.join(os.getcwd(), '..\\..\\data\\raw')

# 存处理后的数据的目录

processed_data_dir = os.path.join(os.getcwd(), '..\\..\\data\\processed')

filename_temp = pd.Series(['分词效果-','共词矩阵-'])

输出结果可能是:

Exception reporting mode: Verbose

2.2  检测data\raw目录下是否有分词效果表

以下的演示以GooSeeker分词和文本分析软件生成的分词效果和共词矩阵excel表为例,需要把分词效果和共词矩阵excel表放到本notebook的data/raw文件夹下,如果没有数据表,下面代码执行后将提示“不存在”

# 0:'词频', 1:'分词效果', 2:'选词矩阵', 3:'选词匹配', 4:'选词结果', 5:'共词矩阵'

file_seg_effect = ''

print(raw_data_dir + '\r\n')

for item_filename in os.listdir(raw_data_dir):

    if filename_temp[0] in item_filename:

        file_seg_effect = item_filename

        continue

if file_seg_effect:

    print("分词效果excel表:", "data\\raw\\", file_seg_effect)

else:

    print("分词效果excel表:不存在")

输出结果可能是:

C:\Users\work\workspace_219\notebook\知乎话题文本分词后的共词矩阵怎样添加词语距离条件\notebook\eda\..\..\data\raw

分词效果excel表: data\raw\ 分词效果-知乎-二舅.xlsx

2.3  检测data\raw目录下是否有共词矩阵表

以下的演示以GooSeeker分词和文本分析软件生成的分词效果和共词矩阵excel表为例,需要把分词效果和共词矩阵excel表放到本notebook的data/raw文件夹下,如果没有数据表,下面代码执行后将提示“不存在”

# 0:'词频', 1:'分词效果', 2:'选词矩阵', 3:'选词匹配', 4:'选词结果', 5:'共词矩阵'

file_co_occ_matrix = ''

file_co_occ_matrix_modified = ''

print(raw_data_dir + '\r\n')

for item_filename in os.listdir(raw_data_dir):

    if filename_temp[1] in item_filename:

        file_co_occ_matrix = item_filename

        continue

if file_co_occ_matrix:

    print("共词矩阵excel表:", "data\\raw\\", file_co_occ_matrix)

    file_co_occ_matrix_modified = file_co_occ_matrix.replace('共词矩阵-','共词矩阵已引入距离值-')

else:

    print("共词矩阵excel表:不存在")

输出结果可能是:

C:\Users\work\workspace_219\notebook\知乎话题文本分词后的共词矩阵怎样添加词语距离条件\notebook\eda\..\..\data\raw

共词矩阵excel表: data\raw\ 共词矩阵-知乎-二舅.xlsx

3  定义词语位置距离阀门值

如果某段文中2个词语的位置距离高于该阀门值,则不计入共现次数

k_word_distance = 5

4  读入分词效果表并观察

4.1  读取分词效果表

df_file_seg_effect = pd.read_excel(os.path.join(raw_data_dir, file_seg_effect))

4.2  查看前10行数据

使用df.head()函数查看dataframe的前n行数据,可以看到数据有5列:原数据,分词数据,关键词,序号,发布时间。

其中“分词数据”就包含了中文分词后最完整的词语序列。

df_file_seg_effect.head(10)

5  读入共词矩阵表并观察

5.1  读取共词矩阵表

df_file_co_occ_matrix = pd.read_excel(os.path.join(raw_data_dir, file_co_occ_matrix))

5.2  查看前10行数据

使用df.head()函数查看dataframe的前n行数据,可以看到该excel表中存储的是列名和行名相同的矩阵数据。

df_file_co_occ_matrix.head(10)

5.3  测试:显示“世界”*“二舅” 这2个词的共现次数

df_file_co_occ_matrix.iloc[0,2]

输出结果是:

74

6  基于词语位置距离修改共词矩阵表

6.1  创建函数cal_word_distance(text, word1, word2)

函数功能:

  • 在入参文本text中查找词语word1和词语word2的位置,并计算位置距离

函数返回值:

  • -1:表示2个词在该text文本中没有共现关系
  • 大于等于0的整数:该值为2个词的位置距离

注意:为了简化运算,本函数在计算2个词的位置距离时,不考虑同一个词语出现多次的情况,统一以该词在文本中第一次出现的位置作为该词的位置。如果要考虑多次出现的情况,应该修改下面的代码,找出来距离最小的情形,将最小距离与阈值进行比较。

def cal_word_distance(text, word1, word2):

    text_array = text.split(' ')

    word_distance = -1

    word1_pos = -1

    word2_pos = -1

    if word1 in text and word2 in text:

        for i,item in enumerate(text_array):

            if item == word1 and word1_pos == -1:

                word1_pos = i

            if item == word2 and word2_pos == -1:

                word2_pos = i

            if word1_pos > -1 and word2_pos > -1:

                word_distance = word1_pos - word2_pos if  word1_pos > word2_pos else word2_pos - word1_pos

                break

    return word_distance

6.2  复制dataframe

下面的修改都在复制的dataframe上做,dataframe名字中去掉_file_,以示区别。保留两个dataframe是为了后面画图对比。

df_co_occ_matrix = df_file_co_occ_matrix.copy(deep=True)

6.3  取所有共现词的列表

word_list = df_co_occ_matrix['Unnamed: 0'].tolist()

word_list[:5]

输出结果是:

['世界', '二舅', '现实', '时候', '故事']

6.4  修改共词矩阵的共现次数

方法是:针对每一对共现词,计算该对词在每串原文的分词结果里的距离,如果距离大于k_word_distance,则该对词的共现次数减1。这个计算比较花时间,请耐心等待。

# 从选词list逐个取出第1个词

print('修改开始',datetime.datetime.now())

for word1 in word_list:

    # 第1个词在选词list中的index

    word1_loc = word_list.index(word1)

    # 从选词list逐个取出第2个词

    for word2 in word_list:

        # 第2个词在选词list中的index

        word2_loc = word_list.index(word2)

        if word1 == word2:

            # 设置词1和词2相同的点的共现数为0

            df_co_occ_matrix.iloc[word1_loc,word2_loc + 1] = 0

        else:

            # 循环遍历所有文本的分词数据,计算2个词的距离

            for text in df_file_seg_effect['分词数据']:

                # print('分词数据:',text)

                if len(str(text)) > 0:

                    if cal_word_distance(str(text),word1,word2) > k_word_distance:

                        df_co_occ_matrix.iloc[word1_loc,word2_loc + 1] = df_co_occ_matrix.iloc[word1_loc,word2_loc + 1] -1

                        # df_co_occ_matrix.iloc[word2_loc,word1_loc + 1] = df_co_occ_matrix.iloc[word2_loc,word1_loc + 1] -1 

            

print('修改完成',datetime.datetime.now())          

输出结果是:

修改开始 2024-01-22 11:22:00.833321

修改完成 2024-01-22 11:24:33.753902

6.5  保存修改后的共词矩阵

保存到文件中,后续分析可以使用。比如,接下来我们会用Gephi导入共词矩阵,并且对比修改前的共词矩阵和修改后的共词矩阵的网络图的区别。

df_co_occ_matrix.to_excel(os.path.join(processed_data_dir, file_co_occ_matrix_modified))

7  用networkx画网络图

7.1  提取字段名

将用于给graph的node命名

coword_names = df_file_co_occ_matrix.columns.values[1:]

print("There are ", len(coword_names), " words")

coword_names

7.2  为原始共词矩阵画图

7.2.1  生成矩阵数据结构

# 使用astype函数对数据类型进行转换,否则,下面画图的时候可能会报错

array_co_word_matrix_file = df_file_co_occ_matrix.values[:, 1:].astype(float)

array_co_word_matrix_file


word_num_file = len(array_co_word_matrix_file)

word_num_file

输出结果是:

133

7.2.2  从NumPy数组生成networkx图

参看networkx文档,有专门的函数从其他数据结构直接生成graph


graph_co_word_matrix_file = nx.from_numpy_array(array_co_word_matrix_file)

print(nx.info(graph_co_word_matrix_file))

#graph_co_word_matrix.edges(data=True)

输出结果是:

Name: 

Type: Graph

Number of nodes: 133

Number of edges: 7843

Average degree: 117.9398

7.2.3  设置中文字体

因为含有中文,pyplot画图有可能会显示下面的错误信息:

C:\ProgramData\Anaconda3\lib\site-packages\matplotlib\backends\backend_agg.py:238: RuntimeWarning: Glyph 32993 missing from current font. font.set_text(s, 0.0, flags=flags)

这是因为找不到中文字体,所以在图上的中文显示不出来,为了解决pyplot显示找不到字体的问题,参看glyph-23130-missing-from-current-font,先做如下设置。

#plt.rcParams['font.sans-serif']=['SimHei']

# 上面一行在macOS上没有效果,所以,使用下面的字体

plt.rcParams['font.sans-serif'] = ['Arial Unicode MS']

plt.rcParams['axes.unicode_minus'] = False

7.2.4  给node加上label

如果不加label,画出来的图上的每个节点只是一个编号,加上label可以看到节点对应的词。

根据get_node_attributes,查看现在的note labels

coword_labels_file = nx.get_node_attributes(graph_co_word_matrix_file,'labels')

coword_labels_file

输出结果是:

{}

根据How-do-I-label-a-node-using-networkx-in-python,重新命名labels

for idx, node in enumerate(graph_co_word_matrix_file.nodes()): 

    print("idx=", idx, "; node=", node)

    coword_labels_file[node] = coword_names[idx]

graph_co_word_matrix_file = nx.relabel_nodes(graph_co_word_matrix_file, coword_labels_file)

sorted(graph_co_word_matrix_file)

for idx, node in enumerate(graph_co_word_matrix_file.nodes()): 

    print("idx=", idx, "; node=", node)

7.2.5  画图

figure函数的使用方法参看pyplot官网 。其他参考资料:

pos = nx.spring_layout(graph_co_word_matrix_file)

plt.figure(1,figsize=(30,30)) 

nx.draw(graph_co_word_matrix_file, pos, node_size=10, with_labels=True, font_size=22, font_color="red")

#nx.draw(graph_co_word_matrix_file, pos, with_labels=True)

#nx.draw_networkx_labels(graph_co_word_matrix_file, pos, labels)

plt.show()

7.3  为过滤后的共词矩阵画图

根据距离过滤的时候,没有修改词语及其数量,所有,标签数组可以重用,不用再提取共词矩阵表的标签名了。

7.3.1  生成矩阵数据结构

# 使用astype函数对数据类型进行转换,否则,下面画图的时候可能会报错

array_co_word_matrix = df_co_occ_matrix.values[:, 1:].astype(float)

array_co_word_matrix

word_num = len(array_co_word_matrix)

word_num

输出结果是:

133

7.3.2  从NumPy数组生成networkx图

#graph_co_word_df = nx.from_pandas_adjacency(df_co_word_matrix)

graph_co_word_matrix = nx.from_numpy_array(array_co_word_matrix)

print(nx.info(graph_co_word_matrix))

#graph_co_word_matrix.edges(data=True)

输出结果是:

Name: 

Type: Graph

Number of nodes: 133

Number of edges: 876

Average degree:  13.1729

可以发现,节点数量没变,但是边的数量已经大大减少了。

7.3.3  给node加上label

如果不加label,画出来的图上的每个节点只是一个编号,加上label可以看到节点对应的词。

查看现在的note labels

coword_labels = nx.get_node_attributes(graph_co_word_matrix,'labels')

coword_labels

重新命名labels

for idx, node in enumerate(graph_co_word_matrix.nodes()): 

    print("idx=", idx, "; node=", node)

    coword_labels[node] = coword_names[idx]

graph_co_word_matrix = nx.relabel_nodes(graph_co_word_matrix, coword_labels)

sorted(graph_co_word_matrix)

查看一下效果

for idx, node in enumerate(graph_co_word_matrix.nodes()): 

    print("idx=", idx, "; node=", node)

7.3.4  画图

pos = nx.spring_layout(graph_co_word_matrix)

plt.figure(1,figsize=(30,30)) 

nx.draw(graph_co_word_matrix, pos, node_size=10, with_labels=True, font_size=22, font_color="red")

#nx.draw(graph_co_word_matrix, pos, with_labels=True)

#nx.draw_networkx_labels(graph_co_word_matrix, pos, labels)

plt.show()

很明显可以看到,现在的网络图清爽多了。此前我们曾经实验过进一步剪枝,以便更容易观察词语之间的关系,比如,采用最小展开树算法、计算协方差矩阵、计算皮尔森相关系数等等。可以在这个集锦中找到相关的notebook:用集搜客分词软件和Jupyter Notebook做文本分析和数据探索的案例汇总

8  总结

本Notebook回答有些同学提出这样的问题:“共词矩阵生成时能否考虑共现词之间的位置距离?比如2个词的距离小于某个值时才算作共现词。”

本Notebook使用Gooseeker文本分词和情感分析软件导出的分词效果和共词矩阵excel表格,在python下对共现词的距离进行计算并对共词矩阵excel表格进行修改,修改后的表格会以一个新的名称保存在data/processed目录下。

接下来我们会在Gephi中分别导入修改前的共词矩阵和修改后的共词矩阵表,利用Gephi更强的展现和分析能力观察在网络图层面有没有什么不同。

9  下载源代码

下载notebook源代码请进:根据词间距离条件筛选分析知乎话题文本

1

鲜花

握手

雷人

路过

鸡蛋

刚表态过的朋友 (1 人)

最新评论

GMT+8, 2024-10-11 13:23