基本文本处理技能
分词
中国有一种特有的文化现象,叫做文盲,即只会听读不会写。建国后国家曾经进行了很多次文化普及运动,例如义务教育,旨在降低文盲率。现在的文盲比率已经被大幅度降低,文盲大多是农村长者,没能享受到教育红利。所以文盲其实不是一个贬义词,而是一种特殊的现象,其根源在于汉语的音与形分离。
新文化运动时期,曾有人提出过将文字罗马化。解释罗马化并不容易,不如举一个例子,日语与韩文都是罗马化后的汉字。其音意一体,可听说即可写。区别点在于韩文进行了完全罗马化,而日语在罗马化的进程中保留了部分汉字。而我们最为熟知的罗马化文字是英语。对于完全罗马化的文字,不存在文盲这一概念。这也是日语常常需要在汉字上标注读音的原因,因为日本也有不认识汉字的人,需要转化为音义后才能理解。
从这种角度来讲,汉字的文字复杂度较高,也因此能在有限的字数中表示更多的含义。在很长的一段时间内,汉字没有标点,依靠理解进行断句。韩愈在《师说》中提到的“句读之不解”,正是“不明白怎么断句”。
说了这么多,无非是想指出汉字与英语存在着根本差别:字和词的差别。在汉字中,句子由连续的多个汉字组成,词与词之间没有分隔符,需要对连续的汉字进行合并划分,也就是分词。
分词的正向最大匹配
正向最大匹配的方式是正向迭代语句,每匹配到一个词就将这个词存储并不在迭代,转而搜索下一个词。这种算法被称为贪心算法。
举例:
- 第一轮匹配
1.1 祝你新年快乐.
1.2 祝你新年快
1.3 祝你新年
1.4 祝你新
1.5 祝你
1.6 祝(第一个词) - 第二轮匹配
2.1 你新年快乐
2.2 你新年快
2.3 你新年
2.4 你新
2.5 你(第二个词) - 第三轮匹配
3.1 新年快乐(第三个词)
因此,这个句子的分词结果为【祝】【你】【新年快乐】
这个句子本身比较短,因此可以被很快地分类。然而在实际操作中,长句往往能达到二十字甚至三十字,迭代次数非常恐怖。因此往往需要采用分治策略,即将句子切片进行匹配,没有被匹配的字则归入到下一个切片中进行匹配。通过这种方式,单次迭代的耗时降低,而汉字“短小精悍”的特性也使得单个切片在四五个字时即可有较好的匹配性能。
逆向最大匹配
逆向迭代语句。这个东西其实解释起来挺麻烦的,直接上示例吧。
- 第一轮匹配
1.1 祝你新年快乐
1.2 你新年快乐
1.3 新年快乐(第一个词) - 第二轮匹配
2.1 祝你
2.2 你(第二个词) - 第三轮匹配
3.1 祝(第三个词)
这个示例的匹配结果与正向匹配相同,但实际上,很多时候二者的匹配结果并不相同。二者的优劣很难预判。
双向最大匹配
无论正向匹配还是逆向匹配,都存在误差。
在汉语中,词义的准确性与词语的长度往往正相关,因此可以通过比较两种方向最大匹配中的匹配结果,选择词语长度更长的一项,作为双向最大匹配的匹配结果。
词、字符频率统计
代码如下:
from collections import Counter
text = '祝你新年快乐。'
print(Counter(text))
这里调用了collection.Counter模块,输出结果为一个包含了各字出现频率的字典。包括‘。’也被计入。如果需要删去这些没有实意的词语,可以下载一个停用词词典,过滤后就干净了。
语言模型n-gram
这里主要使用的是n-gram语言模型,其核心思想为将有含义的最小单位(这里全部指词语)与相邻单位进行组合,计算其相关性。有关于n-gram的深度解释和优化方法在其他博客中已经非常完备,由于本人水平不足,不在此卖弄,谨附上链接供各位参考:https://blog.csdn.net/songbinxu/article/details/80209197
unigram、bigram、trigram
理论上来说,n-gram中用来匹配的N越大,语义判断越准确。然而在N过大的情况下,可能出现一万个句子中只有一两个能得到匹配结果,其余的全是无法匹配。因此N的选定较为重要,一般认为N=3即具备良好的性质。
· unigram指词语独立,每个词之间没有关联关系
· unigram指两个词语是一个单元
· trigram词语是一个单元
unigram、bigram频率统计
unigram对独立的词语进行频率统计,将句子用分词工具处理后(例如jieba分词),通过collection.Counter方式得出统计结果。
如果需要bigram,则需要先对分词结果进行n-gram处理,得出关联关系,然后再按照前面的方式得出统计结果。
文本矩阵化
分词(这里使用jieba分词)
有篇博客介绍jieba分词,写得很好,我直接贴上链接:https://www.cnblogs.com/skyme/p/4651331.html
以下是jieba分词的三种分词模式。
支持三种分词模式:
精确模式,试图将句子最精确地切开,适合文本分析;
全模式,把句子中所有的可以成词的词语都扫描出来, 速度非常快,但是不能解决歧义;
搜索引擎模式,在精确模式的基础上,对长词再次切分,提高召回率,适合用于搜索引擎分词。<
可以看出,三种分词模式各有千秋,以下是代码示例。
# encoding=utf-8
import jieba
seg_list = jieba.cut("我来到北京清华大学", cut_all=True)
print("Full Mode: " + "/ ".join(seg_list)) # 全模式
seg_list = jieba.cut("我来到北京清华大学", cut_all=False)
print("Default Mode: " + "/ ".join(seg_list)) # 精确模式
seg_list = jieba.cut("他来到了网易杭研大厦") # 默认是精确模式
print(", ".join(seg_list))
seg_list = jieba.cut_for_search("小明硕士毕业于中国科学院计算所,后在日本京都大学深造") # 搜索引擎模式
print(", ".join(seg_list))
在这一示例中,“行研”并不在字典中。由于jieba分词有一定的新词识别能力,因此通过Viterbi算法识别出了这一词语。输出结果如下:
【全模式】: 我/ 来到/ 北京/ 清华/ 清华大学/ 华大/ 大学
【精确模式】: 我/ 来到/ 北京/ 清华大学
【新词识别】:他, 来到, 了, 网易, 杭研, 大厦 (此处,“杭研”并没有在词典中,但是也被Viterbi算法识别出来了)
【搜索引擎模式】: 小明, 硕士, 毕业, 于, 中国, 科学, 学院, 科学院, 中国科学院, 计算, 计算所, 后, 在, 日本, 京都, 大学, 日本京都大学, 深造
jieba分词支持导入自定义词典,自定义词典可以选择覆盖在当前词典上,也可以仅用来完善当前词典。在导入词典以外,还支持对当前词典的调整,例如增加新词、删除某一词语等。在此处附上官方示例:
#encoding=utf-8
from __future__ import print_function, unicode_literals
import sys
sys.path.append("../")
import jieba
jieba.load_userdict("userdict.txt")
import jieba.posseg as pseg
jieba.add_word('石墨烯')
jieba.add_word('凱特琳')
jieba.del_word('自定义词')
test_sent = (
"李小福是创新办主任也是云计算方面的专家; 什么是八一双鹿\n"
"例如我输入一个带“韩玉赏鉴”的标题,在自定义词库中也增加了此词为N类\n"
"「台中」正確應該不會被切開。mac上可分出「石墨烯」;此時又可以分出來凱特琳了。"
)
words = jieba.cut(test_sent)
print('/'.join(words))
print("="*40)
result = pseg.cut(test_sent)
for w in result:
print(w.word, "/", w.flag, ", ", end=' ')
print("\n" + "="*40)
terms = jieba.cut('easy_install is great')
print('/'.join(terms))
terms = jieba.cut('python 的正则表达式是好用的')
print('/'.join(terms))
print("="*40)
# test frequency tune
testlist = [
('今天天气不错', ('今天', '天气')),
('如果放到post中将出错。', ('中', '将')),
('我们中出了一个叛徒', ('中', '出')),
]
for sent, seg in testlist:
print('/'.join(jieba.cut(sent, HMM=False)))
word = ''.join(seg)
print('%s Before: %s, After: %s' % (word, jieba.get_FREQ(word), jieba.suggest_freq(seg, True)))
print('/'.join(jieba.cut(sent, HMM=False)))
print("-"*40)
(“我们中出了一个叛徒”和“欢迎新老师生前来就餐”有种异曲同工之妙啊…)
关于在jieba分词中多次提及的HMM算法,我也找到了一篇很好的博客,在这里附上链接:https://www.cnblogs.com/skyme/p/4651331.html
希望自己以后能有时间有能力安心啃博客吧。
去停用词
引用自https://blog.csdn.net/Junkichan/article/details/51883207
stopwords=[]
st = open('/Users/Administrator/Desktop/stopwords3.txt', 'rb')
for line in st:
stopwords.append(line)
for j in range(1,10):
for i in range(10, 510):
print u'正在处理',(j,i)
try:
f = open('/Users/Administrator/Desktop/delstopwords2/%d/%d.txt' % (j,i), 'rb')
for line in f:
if line not in stopwords:
b = open('/Users/Administrator/Desktop/delstopwords3/%d/%d.txt' % (j,i), 'a')
line=line.strip()
b.write(line)
b.write('\n')
b.close()
except:
continue
文本向量化
这里的主要内容来自一篇博客,附上连接:https://blog.csdn.net/u012374510/article/details/54972133
目前在我知识体系内的是词袋模型,将文本独热化。这种方式可以剔除一些生僻不常用的字词,从而提高文本的利用率。问题点在于汉语的含义高度压缩于寥寥数字,且不同人对汉语的使用能力不同,过度口语化的句子和过度凝练的句子都难以分析。具体的弊端在参考的博客中已经提及,我直接抄过来吧:
1.多维度灾难,词的维数比较多
2.语义丢失, 表现为,词的顺序信息丢失,近义词没办法体现,假定词都是独立的,等等
唔…已经抄了这么多了,那就把通过sklearn进行的变换操作得代码也复制过来吧。引自https://blog.csdn.net/herosunly/article/details/89177698,感谢学习小组先锋。
from sklearn.feature_extraction.text import CountVectorizer
count_vectorizer = CountVectorizer(stop_words=stop_words) #上一步的停用词
count_vectorizer.fit(seg_list)
vec = count_vectorizer.transform(seg_list).toarray()
vocab_list = count_vectorizer.get_feature_names() #得到字典