一、基本文本处理技能
目前有三大主流分词方法:基于字符串匹配的分词方法、基于理解的分词方法和基于统计的分词方法。
实际应用中,常常将字符串匹配分词和统计分词结合使用,这样既体现了匹配分词速度快、效率高的优点,同时又能运用统计分词识别生词、自动消除歧义等方面的特点。
0、分词
分词算法设计的基本原则:
- 颗粒度越大越好。用于进行语义分析的文本分词,单词的字数越多,所能表示的含义越确切。eg: “公安局长”可以分为“公安 局长”、“公安局 长”、“公安局长”都算对,但是要用于语义分析,则“公安局长”的分词结果最好.
- 切分结果中非词典词越少越好,单字字典词数越少越好。“非词典词”就是不包含在词典中的单字,而“单字字典词”指的是可以独立运用的单字。eg:“技术和服务”可切分为“技术 和服 务”,但“务”字无法独立成词,有1个非词典词;“技术 和 服务”其中“和”字可以单独成词(词典中要包含)有0个非词典词,因此选用后者。
- 总体词数越少越好,在相同字数的情况下,总词数越少。
1、分词匹配方法
最大匹配法:最大匹配是指以词典为依据,取词典中最长单词为第一个次取字数量的扫描串,在词典中进行扫描(为提升扫描效率,还可以跟据字数多少设计多个字典,然后根据字数分别从不同字典中进行扫描。下面以“我们在野生动物园玩”详细说明一下这几种匹配方法:
(1)正向最大匹配法
正向即从前往后取词,从7->1,每次减一个字,直到词典命中或剩下1个单字。
第1轮扫描:
第1次:“我们在野生动物”,扫描7字词典,无
第2次:“我们在野生动”,扫描6字词典,无
。。。。
第6次:“我们”,扫描2字词典,有
扫描中止,输出第1个词为“我们”,去除第1个词后开始第2轮扫描,即:
第2轮扫描:
第1次:“在野生动物园玩”,扫描7字词典,无
第2次:“在野生动物园”,扫描6字词典,无
。。。。
第6次:“在野”,扫描2字词典,有
扫描中止,输出第2个词为“在野”,去除第2个词后开始第3轮扫描,即:
第3轮扫描:
第1次:“生动物园玩”,扫描5字词典,无
第2次:“生动物园”,扫描4字词典,无
第3次:“生动物”,扫描3字词典,无
第4次:“生动”,扫描2字词典,有
扫描中止,输出第3个词为“生动”,第4轮扫描,即:
第4轮扫描:
第1次:“物园玩”,扫描3字词典,无
第2次:“物园”,扫描2字词典,无
第3次:“物”,扫描1字词典,无
扫描中止,输出第4个词为“物”,非字典词数加1,开始第5轮扫描,即:
第5轮扫描:
第1次:“园玩”,扫描2字词典,无
第2次:“园”,扫描1字词典,有
扫描中止,输出第5个词为“园”,单字字典词数加1,开始第6轮扫描,即:
第6轮扫描:
第1次:“玩”,扫描1字字典词,有
扫描中止,输出第6个词为“玩”,单字字典词数加1,整体扫描结束。
结果:正向最大匹配法,最终切分结果为:“我们/在野/生动/物/园/玩”,其中,单字字典词为2,非词典词为1。
(2)反向最大匹配法
逆向即从后往前取词,其他逻辑和正向相同。即:
第1轮扫描:“在野生动物园玩”
第1次:“在野生动物园玩”,扫描7字词典,无
第2次:“野生动物园玩”,扫描6字词典,无
。。。。
第7次:“玩”,扫描1字词典,有
扫描中止,输出“玩”,单字字典词加1,开始第2轮扫描
第2轮扫描:“们在野生动物园”
第1次:“们在野生动物园”,扫描7字词典,无
第2次:“在野生动物园”,扫描6字词典,无
第3次:“野生动物园”,扫描5字词典,有
扫描中止,输出“野生动物园”,开始第3轮扫描
第3轮扫描:“我们在”
第1次:“我们在”,扫描3字词典,无
第2次:“们在”,扫描2字词典,无
第3次:“在”,扫描1字词典,有
扫描中止,输出“在”,单字字典词加1,开始第4轮扫描
第4轮扫描:“我们”
第1次:“我们”,扫描2字词典,有
扫描中止,输出“我们”,整体扫描结束。
结果:逆向最大匹配法,最终切分结果为:“我们/在/野生动物园/玩”,其中,单字字典词为2,非词典词为0。
(3)双向最大匹配法
正向最大匹配法和逆向最大匹配法,都有其局限性 ==》双向最大匹配法,双向最大匹配法。
即,两种算法都切一遍,然后根据大颗粒度词越多越好,非词典词和单字词越少越好的原则,选取其中一种分词结果输出。
如:“我们在野生动物园玩”
正向最大匹配法,最终切分结果为:“我们/在野/生动/物/园/玩”,其中,两字词3个,单字字典词为2,非词典词为1。
逆向最大匹配法,最终切分结果为:“我们/在/野生动物园/玩”,其中,五字词1个,两字词1个,单字字典词为2,非词典词为0。
非字典词:正向(1)>逆向(0)(越少越好)
单字字典词:正向(2)=逆向(2)(越少越好)
总词数:正向(6)>逆向(4)(越少越好)
因此最终输出为逆向结果。
2、词、字符频率统计
python中 collections.Counter
模块
新建两个txt文件,其内容分别为:
word1.txt
hello
python
goodbye
python
word2.txt
i
like
python
import os
from collections import Counter
sumsdata = []
for fname in os.listdir(os.getcwd()):
if os.path.isfile(fname) and fname.endswith('.txt'):
with open(fname, "r") as fp:
data = fp.readlines()
fp.close()
sumsdata += [line.strip().lower() for line in data]
cnt = Counter()
for word in sumsdata:
cnt[word] += 1
cnt=dict(cnt)
for key, value in cnt.items():
print(key + ":" + str(value))
输出结果
i:1
like:1
python:3
hello:1
goodbye:1
二、语言模型
统计语言模型是一个单词序列上的概率分布,对于一个给定长度为m的序列,它可以为整个序列产生一个概率 P(w_1,w_2,…,w_m) 。其实就是想办法找到一个概率分布,它可以表示任意一个句子或序列出现的概率。
目前在自然语言处理相关应用非常广泛,如语音识别(speech recognition) , 机器翻译(machine translation), 词性标注(part-of-speech tagging), 句法分析(parsing)等。传统方法主要是基于统计学模型,最近几年基于神经网络的语言模型也越来越成熟。
常见的方法有n-gram模型方法、决策树方法、最大熵模型方法、最大熵马尔科夫模型方法、条件随机域方法、神经网络方法,等等。
1、n-gram模型:unigram、bigram、trigram
为了解决自由参数数目过多的问题,引入了马尔科夫假设:随意一个词出现的概率只与它前面出现的有限的n个词有关。基于上述假设的统计语言模型被称为N-gram语言模型。
当n取1、2、3时,n-gram模型分别称为unigram、bigram、trigram语言模型
unigram
一元分词,把句子分成一个一个的汉字bigram
二元分词,把句子从头到尾每两个字组成一个词语,也叫一阶马尔科夫链trigram
三元分词,把句子从头到尾每三个字组成一个词语,也叫二阶马尔科夫链
2、文本矩阵化
过程:
加载数据集->分词->生成词汇表->生成word_index->加载预训练词向量模型->生成词向量矩阵
(1)分词——jieba
jieba:https://github.com/fxsjy/jieba
三种分词模式:
- 精确模式,试图将句子最精确地切开,适合文本分析;
- 全模式,把句子中所有的可以成词的词语都扫描出来, 速度非常快,但是不能解决歧义;
- 搜索引擎模式,在精确模式的基础上,对长词再次切分,提高召回率,适合用于搜索引擎分词。
import jieba
# 全模式
text = "我来到北京清华大学"
seg_list = jieba.cut(text, cut_all=True)
print(u"[全模式]: ", "/ ".join(seg_list))
seg_list = jieba.cut(text, cut_all=False)
print(u"[精确模式]: ", "/ ".join(seg_list))
# 默认是精确模式
seg_list = jieba.cut(text)
print(u"[默认模式]: ", "/ ".join(seg_list))
# 搜索引擎模式
seg_list = jieba.cut_for_search(text)
print(u"[搜索引擎模式]: ", "/ ".join(seg_list))
输出结果
[全模式]: 我/ 来到/ 北京/ 清华/ 清华大学/ 华大/ 大学
[精确模式]: 我/ 来到/ 北京/ 清华大学
[默认模式]: 我/ 来到/ 北京/ 清华大学
[搜索引擎模式]: 我/ 来到/ 北京/ 清华/ 华大/ 大学/ 清华大学
(2)新词识别
新词识别: “杭研”并没有在词典中,但是也被 Viterbi
算法识别出来了
import jieba
seg_list = jieba.cut("他来到了网易杭研大厦")
print(u"[新词识别]: ", "/ ".join(seg_list))
(3)自定义词典
自定义词典,以便包含 jieba
词库中没有的词语(尤其是专有名称),以提升正确率。
import jieba
text = "故宫的著名景点包括乾清宫、太和殿和黄琉璃瓦等"
# 全模式
seg_list = jieba.cut(text, cut_all=True)
print(u"[全模式: ]", "/ ".join(seg_list))
# 精确模式
seg_list = jieba.cut(text, cut_all=False)
print(u"[精确模式: ]", "/".join(seg_list))
# 搜索引擎模式
seg_list = jieba.cut_for_search(text)
print(u"[搜索引擎模式: ]", "/".join(seg_list))
输出结果
[全模式: ] 故宫/ 的/ 著名/ 著名景点/ 景点/ 包括/ 乾/ 清宫/ / / 太和/ 太和殿/ 和/ 黄/ 琉璃/ 琉璃瓦/ 等
[精确模式: ] 故宫/的/著名景点/包括/乾/清宫/、/太和殿/和/黄/琉璃瓦/等
[搜索引擎模式: ] 故宫/的/著名/景点/著名景点/包括/乾/清宫/、/太和/太和殿/和/黄/琉璃/琉璃瓦/等
缺陷:jieba认出了专有名词”太和殿”,但没有认出”乾清宫”和”黄琉璃瓦”。
设置自定义字典:
每一行分三部分,第一部分为词语,中间部分为词频,最后部分为词性(可省略,ns为地点名词),用空格隔开。如下所示。
乾清宫 1 n
黄琉璃瓦 1 n
改进
import jieba
# 设置并加载自定义字典
filename = './mydict.txt'
jieba.load_userdict(filename)
text = "故宫的著名景点包括乾清宫、太和殿和黄琉璃瓦等"
# 全模式
seg_list = jieba.cut(text, cut_all=True)
print(u"[全模式: ]", "/ ".join(seg_list))
# 精确模式
seg_list = jieba.cut(text, cut_all=False)
print(u"[精确模式: ]", "/".join(seg_list))
# 搜索引擎模式
seg_list = jieba.cut_for_search(text)
print(u"[搜索引擎模式: ]", "/".join(seg_list))
输出结果:新添加的两个专有名词已经被结巴分词工具辨别出来了。
[全模式: ] 故宫/ 的/ 著名/ 著名景点/ 景点/ 包括/ 乾清宫/ 清宫/ / / 太和/ 太和殿/ 和/ 黄琉璃瓦/ 琉璃/ 琉璃瓦/ 等
[精确模式: ] 故宫/的/著名景点/包括/乾清宫/、/太和殿/和/黄琉璃瓦/等
[搜索引擎模式: ] 故宫/的/著名/景点/著名景点/包括/清宫/乾清宫/、/太和/太和殿/和/琉璃/琉璃瓦/黄琉璃瓦/等
(4)关键词
jieba.analyse.extract_tags(text, topK=)
import jieba
import jieba.analyse
filename = './mydict.txt'
jieba.load_userdict(filename)
text = "故宫的著名景点包括乾清宫、太和殿和午门等。其中乾清宫非常精美,午门是紫禁城的正门,午门居中向阳。"
seg_list = jieba.cut(text, cut_all=False)
print (u"分词结果:")
print ("/ ".join(seg_list))
# 获取关键词
keywords = jieba.analyse.extract_tags(text, topK=5)
print(u"关键词: ")
print(" ".join(keywords))
输出结果
分词结果:
故宫/ 的/ 著名景点/ 包括/ 乾清宫/ 、/ 太和殿/ 和/ 午门/ 等/ 。/ 其中/ 乾清宫/ 非常/ 精美/ ,/ 午门/ 是/ 紫禁城/ 的/ 正门/ ,/ 午门/ 居中/ 向阳/ 。
关键词:
午门 乾清宫 著名景点 太和殿 向阳
分析:输出结果按出现词频降序,在词频一样是,根据TF/IDF的升序输出。
(5)去除停用词
为节省存储空间和提高搜索效率,常会自动过滤某些字或词,eg:“的”、“是”、“而且”、“但是”、”非常“等。这些字或词被称为 stop words (停用词)
在分词之前去除停用词,再进行分词。
import jieba
import jieba.analyse
filename = './mydict.txt'
jieba.load_userdict(filename)
text = "故宫的著名景点包括乾清宫、太和殿和午门等。其中乾清宫非常精美,午门是紫禁城的正门,午门居中向阳。"
stopwords = {}.fromkeys(['的', '包括', '等', '是'])
seg_list = jieba.cut(text, cut_all=False)
final = ''
for seg in seg_list:
if seg not in stopwords:
final += seg
print(final, '\n')
seg_list1 = jieba.cut(final, cut_all=False)
print("/ ".join(seg_list1))
输出结果
故宫著名景点乾清宫、太和殿和午门。其中乾清宫非常精美,午门紫禁城正门,午门居中向阳。
故宫/ 著名景点/ 乾清宫/ 、/ 太和殿/ 和/ 午门/ 。/ 其中/ 乾清宫/ 非常/ 精美/ ,/ 午门/ 紫禁城/ 正门/ ,/ 午门/ 居中/ 向阳/ 。
(6)构建词表
def build_vocab(train_dir, vocab_dir, vocab_size=5000):
"""根据训练集构建词汇表,存储"""
data_train, _ = read_file(train_dir)
all_data = []
for content in data_train:
all_data.extend(content)
counter = Counter(all_data)
count_pairs = counter.most_common(vocab_size - 1)
words, _ = list(zip(*count_pairs))
# 添加一个 <PAD> 来将所有文本pad为同一长度
words = ['<PAD>'] + list(words)
open_file(vocab_dir, mode='w').write('\n'.join(words) + '\n')
(7)文档向量化
import jieba
import pandas as pd
from sklearn.feature_extraction.text import CountVectorizer
# 读取停用词
def read_stopword(filename):
stopword = []
fp = open(filename, 'r')
for line in fp.readlines():
stopword.append(line.replace('\n', ''))
fp.close()
return stopword
# 切分数据,并删除停用词
def cut_data(data, stopword):
words = []
for content in data['content']:
word = list(jieba.cut(content))
for w in list(set(word) & set(stopword)):
while w in word:
word.remove(w)
words.append(' '.join(word))
data['content'] = words
return data
# 获取单词列表
def word_list(data):
all_word = []
for word in data['content']:
all_word.extend(word)
all_word = list(set(all_word))
return all_word
# 计算文本向量
def text_vec(data):
count_vec = CountVectorizer(max_features=300, min_df=2)
count_vec.fit_transform(data['content'])
fea_vec = count_vec.transform(data['content']).toarray()
return fea_vec
if __name__ == '__main__':
data = pd.read_csv('./cnews/cnews.test.txt', names=['title', 'content'], sep='\t') # (10000, 2)
data = data.head(50)
stopword = read_stopword('stopword.txt')
data = cut_data(data, stopword)
fea_vec = text_vec(data)
print(fea_vec)