Python实战:用NLTK快速搞定文本共现分析(附完整代码)
Python实战:用NLTK快速搞定文本共现分析(附完整代码)
你是否曾对一段文本中词语之间隐秘的关联感到好奇?比如,在一篇科技报道里,“人工智能”这个词出现时,周围常常伴随着“算法”、“模型”、“数据”这些词。这种词语“结伴出现”的现象,就是共现关系。对于数据分析师、产品经理、市场研究者,甚至是内容创作者来说,挖掘这种关系,能帮你从海量文本中快速洞察核心概念、发现潜在主题,甚至预测趋势。今天,我们不谈复杂的理论,直接上手Python,用NLTK这个强大的工具箱,带你一步步构建属于你自己的文本共现分析器。你会发现,原来从零散的词语到清晰的关系网络,只需要几十行清晰的代码。
1. 从零开始:理解共现分析与NLTK环境搭建
在深入代码之前,我们得先弄明白,我们到底要做什么。文本共现分析,简单说,就是统计在一段文本的特定“窗口”内,哪些词倾向于一起出现。这个“窗口”可以是一个句子、一段话,或者我们自定义的相邻几个词的范围。通过这种分析,我们可以量化词语之间的关联强度,这远比单纯统计词频(哪个词出现最多)要更有洞察力。
举个例子,分析用户对某款手机的评论。高频词可能是“手机”、“不错”、“价格”。但共现分析可能会告诉你,“电池”和“续航”经常同时出现,且与“失望”关联紧密;而“拍照”和“清晰”则与“满意”紧密相连。这立刻就将笼统的“不错”拆解成了具体的优势和痛点。
为了高效地完成这项任务,我们选择 NLTK(Natural Language Toolkit)。它是Python领域最老牌、最全面的自然语言处理库之一,特别适合教学和快速原型开发。它内置了分词、词性标注、停用词列表等众多实用工具,能让我们省去大量造轮子的时间。
注意:虽然NLTK非常适合学习和中小规模数据处理,但在处理超大规模语料库时,你可能需要考虑结合NumPy、SciPy的矩阵运算,或者转向如spaCy这类以性能见长的库。
接下来,让我们准备好战场。确保你的Python环境(建议3.7及以上版本)已经就绪,然后通过pip安装必要的库。
pip install nltk pandas numpy matplotlib seaborn
安装完成后,我们还需要下载NLTK内部的一些数据包,主要是分词器和停用词列表。打开你的Python解释器或创建一个新的脚本文件,执行以下代码:
import nltk
# 下载必要的数据包
nltk.download('punkt') # 分词器
nltk.download('stopwords') # 停用词列表
nltk.download('punkt_tab') # 新版分词器表格数据,提高分词精度和速度
punkt 是负责将句子拆分成单词(分词)的模型;stopwords 包含了多种语言(如英语、中文)的常见功能词列表,这些词(如“the”, “is”, “在”, “的”)通常对分析意义不大,可以过滤掉。punkt_tab 是punkt的补充数据,能提升分词效率。看到Successfully downloaded的提示,就说明环境配置成功了。
2. 核心实战:五步构建共现矩阵与热力图
理论准备就绪,现在进入最核心的实战环节。我们将把一个完整的分析流程拆解为五个清晰的步骤,并附上每一步的代码和解释。我们会使用一段关于“机器学习”的英文短文本作为示例,但整个流程完全适用于中文或其他语言(只需更换分词器和停用词列表)。
2.1 第一步:文本预处理与清洗
原始文本通常包含很多“噪音”,比如标点、数字、大小写不统一等。预处理的目标是得到一份干净、统一的词汇列表。
import nltk
from nltk.corpus import stopwords
from nltk.tokenize import word_tokenize
import string
def preprocess_text(text):
"""
对输入文本进行预处理:转小写、分词、去除标点数字和停用词。
"""
# 1. 转换为小写,确保统一性
text = text.lower()
# 2. 使用NLTK进行分词
tokens = word_tokenize(text)
# 3. 获取英文停用词集
stop_words = set(stopwords.words('english'))
# 4. 清洗:只保留字母组成的单词,并移除停用词
cleaned_tokens = []
for token in tokens:
# 判断是否为纯字母单词
if token.isalpha():
# 判断是否不是停用词
if token not in stop_words:
cleaned_tokens.append(token)
return cleaned_tokens
# 示例文本
sample_text = """Machine learning is a subset of artificial intelligence. It focuses on the development of computer programs that can access data and use it to learn for themselves. The process of learning begins with observations or data."""
cleaned_words = preprocess_text(sample_text)
print("清洗后的词汇列表:", cleaned_words)
这段代码会输出类似 ['machine', 'learning', 'subset', 'artificial', 'intelligence', 'focuses', ...] 的结果。可以看到,标点、“is”、“a”、“the”、“of”等停用词都被移除了,所有单词都是小写。这是构建高质量共现矩阵的基础。
2.2 第二步:定义词汇表与初始化矩阵
我们需要从清洗后的词汇中提取出唯一的词汇表,并据此初始化一个全零的方阵。这个方阵的行和列都代表词汇表中的词,矩阵中的值 M[i][j] 就表示词i和词j的共现次数。
import numpy as np
def build_vocabulary_and_matrix(cleaned_tokens):
"""
根据清洗后的词汇列表构建词汇表和空的共现矩阵。
"""
# 构建排序后的唯一词汇表
vocabulary = sorted(set(cleaned_tokens))
vocab_size = len(vocabulary)
# 创建一个 vocab_size x vocab_size 的全零矩阵
# 数据类型设为int,因为我们要存储计数
co_matrix = np.zeros((vocab_size, vocab_size), dtype=int)
# 创建一个词到索引的映射字典,方便后续快速查找
word_to_index = {word: idx for idx, word in enumerate(vocabulary)}
return vocabulary, co_matrix, word_to_index
vocab, co_mat, word_index = build_vocabulary_and_matrix(cleaned_words)
print("词汇表:", vocab)
print("共现矩阵形状:", co_mat.shape)
2.3 第三步:滑动窗口与共现计数
这是算法的核心。我们定义一个“窗口大小”(例如,窗口为2表示看目标词左右各2个词的范围)。然后遍历清洗后的词汇列表,对于每一个词(中心词),查看其窗口内的其他词(上下文词),并在矩阵中对应的位置增加计数。
def fill_co_occurrence_matrix(cleaned_tokens, word_to_index, co_matrix, window_size=2):
"""
使用滑动窗口填充共现矩阵。
"""
tokens_length = len(cleaned_tokens)
for center_pos, center_word in enumerate(cleaned_tokens):
# 获取中心词在词汇表中的索引
center_idx = word_to_index[center_word]
# 计算滑动窗口的起止位置
start = max(center_pos - window_size, 0)
end = min(center_pos + window_size + 1, tokens_length)
# 遍历窗口内的所有词
for context_pos in range(start, end):
if context_pos == center_pos:
continue # 跳过中心词本身
context_word = cleaned_tokens[context_pos]
context_idx = word_to_index[context_word]
# 在共现矩阵中对应位置计数加1
co_matrix[center_idx][context_idx] += 1
return co_matrix
# 假设使用窗口大小为2
window_size = 2
filled_matrix = fill_co_occurrence_matrix(cleaned_words, word_index, co_mat, window_size)
print("填充后的共现矩阵(部分):")
print(filled_matrix) # 打印出来可能是个稀疏矩阵,很多0
这里有一个关键细节:我们通常认为共现关系是有方向的吗?在上面的代码中,当“machine”作为中心词时,它右边的“learning”会被计数一次;当“learning”作为中心词时,它左边的“machine”又会被计数一次。因此,最终矩阵中 M[machine][learning] 和 M[learning][machine] 的值可能相等(如果窗口对称),但它们在概念上是两次独立的计数。有些应用会将矩阵对称化(M = M + M.T),将其视为无向关系,这取决于你的分析目标。
2.4 第四步:结果可视化——热力图
数字矩阵不直观,我们需要将其可视化。热力图(Heatmap)是展示共现矩阵的绝佳方式,颜色深浅直接反映共现频率高低。
import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns
def visualize_co_matrix(co_matrix, vocabulary, top_n=15):
"""
将共现矩阵可视化为热力图,默认展示共现最频繁的前top_n个词。
"""
# 将numpy矩阵转换为Pandas DataFrame,方便处理
df = pd.DataFrame(co_matrix, index=vocabulary, columns=vocabulary)
# 计算每个词的总共现强度(行和),并选取最强的top_n个词
word_strength = df.sum(axis=1)
top_words = word_strength.nlargest(top_n).index.tolist()
# 提取子矩阵,只包含这些核心词
df_top = df.loc[top_words, top_words]
# 绘制热力图
plt.figure(figsize=(10, 8))
# cmap选择颜色映射,annot=True显示数值,fmt='d'表示整数格式
sns.heatmap(df_top, annot=True, fmt='d', cmap='YlOrRd', linewidths=.5)
plt.title(f'Top {top_n} Words Co-occurrence Heatmap')
plt.xlabel('Context Word')
plt.ylabel('Center Word')
plt.tight_layout()
plt.show()
# 可视化前10个最活跃的词
visualize_co_matrix(filled_matrix, vocab, top_n=10)
运行这段代码,你会得到一个色彩斑斓的方格图。对角线通常是空的(因为我们跳过了中心词自身),而像“machine”和“learning”这样的词对所在的格子,颜色会非常深,数值也较大,直观地展示了它们的强关联。
2.5 第五步:进阶分析——关联度计算(PMI)
原始共现次数受词语本身频率影响很大。一个非常常见的词(如“data”)可能和很多词都有共现,但这不一定意味着它们有特别强的语义关联。点互信息(Pointwise Mutual Information, PMI) 是一种常用的标准化方法,用于衡量两个词之间的关联强度是否高于随机预期。
PMI的计算公式为:PMI(w1, w2) = log2( P(w1, w2) / (P(w1) * P(w2)) ) 其中,P(w1, w2)是w1和w2的联合概率(即共现次数除以总窗口对数量),P(w)是词w的独立概率(出现次数除以总词数)。
import math
def calculate_pmi(co_matrix, vocabulary):
"""
基于共现矩阵计算PMI矩阵。
"""
# 将共现矩阵转换为浮点型,用于概率计算
co_matrix_float = co_matrix.astype(float)
vocab_size = len(vocabulary)
# 计算总窗口对数量(近似为总共现次数之和,注意每个窗口内有多对关系)
total_pairs = np.sum(co_matrix_float)
# 计算每个词的边缘概率(出现频率)
word_total_occurrences = np.sum(co_matrix_float, axis=1) # 每行的和,即作为中心词出现的总次数
# 这里简化处理,使用中心词频率作为概率估计。更精确的计算需考虑整个语料库的词频。
p_word = word_total_occurrences / total_pairs
# 初始化PMI矩阵
pmi_matrix = np.zeros((vocab_size, vocab_size))
for i in range(vocab_size):
for j in range(vocab_size):
if co_matrix[i, j] > 0:
# 联合概率
p_joint = co_matrix[i, j] / total_pairs
# PMI计算
pmi = math.log2(p_joint / (p_word[i] * p_word[j]))
pmi_matrix[i, j] = pmi
else:
# 未共现,PMI为负无穷,通常设置为0或一个很小的负数
pmi_matrix[i, j] = 0 # 或 np.NINF
return pmi_matrix
# 计算PMI
pmi_mat = calculate_pmi(filled_matrix, vocab)
# 将PMI矩阵也进行可视化
print("PMI矩阵中,‘machine’和‘learning’的PMI值:",
pmi_mat[vocab.index('machine'), vocab.index('learning')])
# 可以同样用热力图展示PMI矩阵
df_pmi = pd.DataFrame(pmi_mat, index=vocab, columns=vocab)
top_words_pmi = df_pmi.sum(axis=1).nlargest(10).index.tolist()
df_pmi_top = df_pmi.loc[top_words_pmi, top_words_pmi]
plt.figure(figsize=(10,8))
# PMI值可能为负,我们使用发散色系RdBu来区分正负关联
sns.heatmap(df_pmi_top, annot=True, fmt='.2f', cmap='RdBu_r', center=0, linewidths=.5)
plt.title('PMI-based Word Association Heatmap (Top 10 Words)')
plt.xlabel('Context Word')
plt.ylabel('Center Word')
plt.tight_layout()
plt.show()
PMI值为正表示两个词的实际共现频率高于随机预期,存在吸引关系;为负则表示低于随机预期,存在排斥关系或几乎不相关;接近0则表示关系接近随机。通过PMI热力图,你能更清晰地识别出那些具有特异性强关联的词对,过滤掉因高频而产生的“虚假”关联。
3. 处理中文文本:挑战与解决方案
上面的示例针对英文,但中文文本分析的需求同样巨大。处理中文的主要挑战在于分词。英文有天然的空格分隔,而中文需要专门的工具将连续的字符序列切分成有意义的词语。幸运的是,我们有多种选择可以集成到流程中。
方案一:使用jieba分词库 jieba 是Python中最流行的中文分词库,简单易用。
pip install jieba
然后,修改我们的预处理函数:
import jieba
from nltk.corpus import stopwords
def preprocess_chinese_text(text, use_stopwords=True):
"""
预处理中文文本。
"""
# 使用jieba进行精确模式分词
tokens = jieba.lcut(text)
cleaned_tokens = []
if use_stopwords:
# 加载中文停用词表(需要自己准备或从网络获取一个txt文件)
try:
with open('chinese_stopwords.txt', 'r', encoding='utf-8') as f:
chinese_stopwords = set([line.strip() for line in f])
except FileNotFoundError:
print("未找到中文停用词文件,将不使用停用词过滤。")
chinese_stopwords = set()
for token in tokens:
# 去除空白字符和长度小于2的词(通常为单字且意义不大)
token = token.strip()
if len(token) < 2:
continue
# 可选的停用词过滤
if use_stopwords and token in chinese_stopwords:
continue
# 这里可以添加其他过滤规则,如去除纯数字、标点等
cleaned_tokens.append(token)
return cleaned_tokens
# 示例中文文本
chinese_text = "机器学习是人工智能的一个重要分支。它利用算法让计算机从数据中学习规律,并做出预测或决策。深度学习是机器学习的一个子领域。"
chinese_words = preprocess_chinese_text(chinese_text, use_stopwords=False) # 示例中暂不启用停用词
print("中文分词结果:", chinese_words)
方案二:使用pkuseg或HanLP等更专业的工具 对于需要更高分词精度(如专业领域)的场景,可以考虑 pkuseg(北大开源)或 HanLP。它们通常提供了预训练好的不同领域模型。
pip install pkuseg
import pkuseg
seg = pkuseg.pkuseg(model_name='web') # 使用web领域模型
tokens = seg.cut(chinese_text)
print("pkuseg分词结果:", tokens)
一旦获得了分词后的词汇列表,后续构建共现矩阵、计算PMI和可视化的步骤就与英文处理完全一样了。你只需要将 cleaned_words 替换为中文分词结果即可。这体现了我们代码框架的通用性。
4. 从矩阵到洞察:实际应用场景与案例
构建出共现矩阵或PMI矩阵后,我们能用它做什么?以下是一些实实在在的应用方向,你可以直接套用上面的代码框架进行尝试。
场景一:产品评论分析 收集电商平台上某产品的用户评论,进行共现分析。
- 操作:将“卡顿”、“发热”、“续航”、“流畅”、“屏幕”等核心名词作为分析焦点。
- 洞察:如果“卡顿”频繁与“游戏”、“更新后”共现,而“发热”与“充电时”、“夏天”强关联,那么问题根源和场景就清晰了。这比只看负面情感占比更有指导意义。
场景二:新闻主题演化追踪 抓取一段时间内关于“新能源汽车”的新闻文章,按周或月切片分析。
- 操作:对每个时间片的语料分别构建共现网络,关注核心词(如“电池”、“充电桩”、“自动驾驶”、“补贴”)的关联词变化。
- 洞察:你可能发现,早期“电池”主要与“续航”、“成本”共现,后期则更多与“安全”、“快充”关联。这直观揭示了公众关注点和行业热点的迁移。
场景三:构建简易词向量(Co-occurrence Vector) 共现矩阵的每一行(或列)实际上就是一个词在高维空间中的向量表示。
- 操作:选取目标词(如“算法”),其对应的行向量
co_matrix[‘算法’]就是一个原始的词向量。 - 应用:虽然不如Word2Vec、GloVe等深度模型训练出的向量强大,但这种基于计数的向量可以用于计算词语相似度(如余弦相似度),快速实现简单的同义词发现或查询扩展。
from sklearn.metrics.pairwise import cosine_similarity
# 假设我们已经有了共现矩阵 `co_mat` 和词汇表 `vocab`
# 获取“machine”和“learning”的向量
idx_machine = vocab.index('machine')
idx_learning = vocab.index('learning')
vector_machine = filled_matrix[idx_machine].reshape(1, -1) # 需要reshape成 (1, n) 格式
vector_learning = filled_matrix[idx_learning].reshape(1, -1)
# 计算余弦相似度
similarity = cosine_similarity(vector_machine, vector_learning)
print(f"‘machine’和‘learning’的余弦相似度: {similarity[0][0]:.4f}")
场景四:作为复杂模型的输入特征 共现矩阵或其衍生特征(如PMI矩阵、PPMI矩阵)可以作为传统机器学习模型(如文本分类、情感分析)的输入特征,为模型提供词语关联的上下文信息。
在实际项目中,我经常需要快速分析一批访谈纪要或开放式问卷。直接读上千条文本是不现实的。我会先用上面的脚本跑一遍共现分析,生成热力图和PMI排名表。往往前十分钟,那些反复被一起提及的核心概念簇就会跃然图上,这为后续的深度定性分析提供了无比清晰的“地图”。记住,工具的目的是解放人力,让你把精力集中在更有价值的解读和决策上。别在数据清洗和简单统计上耗费过多时间,一个自动化的脚本就能让你事半功倍。
更多推荐



所有评论(0)