n-gram 词频统计
n-gram 词频统计
借助 sklearn 提供的 CountVectorizer 可以实现 n-gram 的词频统计。
实现过程
首先,导入所需的包以及数据。
1 | from sklearn.feature_extraction.text import CountVectorizer |
然后,调用 CountVectorizer,以获得每段文本的文本向量。
1 | vectorizer = CountVectorizer(token_pattern=r"(?u)\b\w+\b", ngram_range=(2,2), min_df=5) |
- min_df:指定最小阈值;
- ngram_range:指定要获取 n-gram 的范围。例如 ngram_range=(1,2) 单词量的大小等于 ngram_range=(1,1) 与 ngram_range=(2,2) 单词量大小之和。
【注意】:
- 在处理中文时,token_pattern 参数需要设置为
(?u)\b\w+\b
,即允许单个汉字。 - CountVectorizer 的 fit_transform() 方法会将输入的文本转化为形似 ont-hot 向量(各维度的数值可以超过 1)。
接下来,我们可以使用 get_feature_names() 来获取所有的词语(即单词表)。
1 | vocab_list = vectorizer.get_feature_names() |
接着,借助 pandas 库生成一张列为单词表、行为文本形似 ont-hot 向量的表格。此时,我们只需要调用 DataFrame 对象的 sum() 方法即可得到每个词语的词频。
1 | df = pd.DataFrame(X.toarray(), columns=vectorizer.get_feature_names()) |
当数据量较大时,我们无法将所有的数据都填充到 pandas 的 DataFrame 对象中,此时就需要对表格进行切分。
【步骤】:推荐按列(单词表)进行切分。
- 每次对一部分单词生成 DataFrame 对象;
- 调用当前 DataFrame 对象的 sum() 方法,并将结果转换为 dict(词语:词频);
- 每轮迭代过程中将上一轮的 dict 整合到当前 dict 中,最后整合成一个完整的 dict。
【关于 dict 合并的优化】:我们可以使用 collections 包中的 ChainMap 来帮助我们加快 dict 的合并操作。需要注意的是,ChainMap 返回的是 ChainMap 对象,我们还需要将其转换为 dict。
1 | vocab_dict = dict(ChainMap(vocab_dict, dict(df.sum().items()))) |
【完整代码】:
1 | from sklearn.feature_extraction.text import CountVectorizer |
与 Counter 比较
使用 Counter 需要先对文本数据做一些处理。
1 | content_split_list = [content.split(" ") for content in content_list] |
通过比对 CountVectorizer 与 Counter 的执行时间,可知 CountVectorizer 在执行效率上要高于 Counter。
问题
CountVectorizer 词频统计所遇到的坑
在使用 sklearn 的 CountVectorizer 方法过程中发现,使用 CountVectorizer 统计获得的 char 个数要少于 Counter 方法统计的 char 个数。
准备工作
【数据】:/nfs/users/chenxu/common_word_mining/data/char_total_list.json
1 | [['日', '前'], |
【引入的包】:
1 | import pandas as pd |
【读取数据】:
1 | with open("/nfs/users/chenxu/common_word_mining/data/char_total_list.json", "r", encoding="utf-8") as file: |
拼接后的数据 content_char_list:
1 | ['日 前', |
CountVectorizer
【执行代码】:
1 | from sklearn.feature_extraction.text import CountVectorizer |
Counter
【执行代码】:
1 | from collections import Counter |
我们可以直接使用 set 的方式去统计 char 的个数。
1 | char_list_all = [] |
再做进一步的验证:
1 | set(char_dict) char_set - |
可知 Counter 方法统计的 char 个数没有问题。
两者进行对比
我们再来对比 CountVectorizer 方法获得的字集合与 Counter 方法获得的字集合的差异。
1 | >>> set(char_dict) - set(vocab_dict) |
产生原因
CountVectorizer 默认会将英文转换为小写,例如 AMG 转换为 amg,这导致我误认为 CountVectorizer 会过滤部分英文和数字,但实际上只是因为这些英文和数字不匹配 token_pattern 对应的正则表达式。我们只需要将其修改为 r"(?u)\b\w+[-+]?\w*\b"
即可统计 “Wi-Fi” 这一类的文本内容。