这篇博文是我的一次笔记,主要讨论NLP的基础知识:中英文字符串的处理,其中英文部分主要是正则表达式,中文部分是jieba分词器。对于其他常用的扩展包:如NLTK、spacy等将接下来的博文中进行更新。
语料库和词汇知识库对于NLP任务起着极其关键的重要,有时候是建立和改进一个NLP系统的“瓶颈”。对于“语料库和词汇知识库”的基础知识,可以参考宗成庆老师的专著:
《统计自然语言处理》
如果说图像处理技术是智能机器的视觉系统,那么自然语言处理就是智能机器的视觉系统、听觉系统、语言系统;CV注重对一帧一帧的图片或者一段连续的视频进行处理,提取重要信息,而NLP注重对文本、语音(语音识别、语音合成)等信息的理解和生成,不管是语音还是图像中的语言信息最终还是转换为文本进行处理;
如果说语言信息是一种物质的话,那么语音就是包裹物质的外壳,文本是传达物质信息的实际载体;写过爬虫或者从事技术开发的童鞋或多或少都要跟文本信息(字符串)打交道,本博文将简要介绍面向英文和中文字符串的常见处理方法;
通过python中的字符串操作(str),可以直接完成一些简单的字符串处理任务,下图采用dir()函数快速了解str对象中的常用内置函数:
下面将挑一些使用频率比较高的内置函数进行演示:
In:
s = ' hello, world!'
print(len(s))
a = s.strip()
print(a)
print(len(a))
print(s.lstrip(' hello, '))
print(s.rstrip('!'))
Out:
14
hello, world!
13
world!
hello, world
In:
sStr1 = 'strcat'
sStr2 = 'append'
sStr1 += sStr2
print sStr1
Out:
strcatappend
# 返回值< 0 为未找到
In:
sStr1 = 'strchr'
sStr2 = 'r'
nPos = sStr1.index(sStr2)
print nPos
Out:
2
In:
sStr1 = 'strchr'
sStr2 = 'strch'
print cmp(sStr2,sStr1)
print cmp(sStr1,sStr2)
print cmp(sStr1,sStr1)
Out:
-1
1
0
In:
sStr1 = 'JCstrlwr'
sStr1 = sStr1.upper()
print sStr1
sStr1 = sStr1.lower()
print sStr1
Out:
JCSTRLWR
jcstrlwr
In:
sStr1 = 'abcdefg'
sStr1 = sStr1[::-1]
print sStr1
Out:
gfedcba
In:
sStr1 = 'abcdefg'
sStr2 = 'cde'
print sStr1.find(sStr2)
Out:
2
In:
sStr1 = 'ab,cde,fgh,ijk'
sStr2 = ','
sStr1 = sStr1[sStr1.find(sStr2) + 1:] # 有个小小的递归在这里
print sStr1
#或者
s = 'ab,cde,fgh,ijk'
print(s.split(','))
Out:
cde,fgh,ijk
['ab', 'cde', 'fgh', 'ijk']
好了,以上就是常用的字符串操作,下面介绍正则表达式;
对于正则表达式一定不陌生,它描述了一种字符串匹配的模式(pattern),可以用来检查一个串是否含有某种子串、将匹配的子串替换或者从某个串中取出符合某个条件的子串等。
对于正则表达式的教程可以参考:
https://www.runoob.com/regexp/regexp-syntax.html
当要匹配 一个/多个/任意个 数字/字母/非数字/非字母/某几个字符/任意字符,想要 贪婪/非贪婪 匹配,想要捕获匹配出来的 第一个/所有 内容的时候,主要以下这张正则表达式的常用语法:
http://regexr.com/
https://alf.nu/RegexGolf
re模块 (import re),
re一般步骤:
• 将正则表达式的字符串形式编译(re.compile)为Pattern实例;
• 使用Pattern实例处理文本并获得匹配结果(一个Match的实例);
• 使用match实例获得信息,进行其他操作;
示例:
import re
# 将正则表达式编译成Pattern对象
pattern = re.compile(r'hello.*\!') #匹配以hello开始,以!结束的字符串
# 使用Pattern匹配文本,获得匹配结果,无法匹配时将返回None
match = pattern.match('hello,world! How are you?')
if match:
# 使用Match获得分组信息
print(match.group())
Out: Hello, world!
re.compile(strPattern[, flag]):
当然,也可以在字符串中指定模式,比如re.compile(‘pattern’, re.I | re.M)等价于re.compile(’(?im)pattern’)
Match
Match对象是一次匹配的结果,包含了很多关于此次匹配的信息,可以使用Match提供的可读属性或方法来获取这些信息。
match属性:
string: 匹配时使用的文本。
re: 匹配时使用的Pattern对象。
pos: 文本中正则表达式开始搜索的索引。值与Pattern.match()和Pattern.seach()方法的同名参数相同。
endpos: 文本中正则表达式结束搜索的索引。值与Pattern.match()和Pattern.seach()方法的同名参数相同。
lastindex: 最后一个被捕获的分组在文本中的索引。如果没有被捕获的分组,将为None。
lastgroup: 最后一个被捕获的分组的别名。如果这个分组没有别名或者没有被捕获的分组,将为None。
方法:
pattern
match(string[, pos[, endpos]]) | re.match(pattern, string[, flags]):
这个方法将从string的pos下标处起尝试匹配pattern:如果pattern结束时仍可匹配,则返回一个Match对象;如果匹配过程中pattern无法匹配,或者匹配未结束就已到达endpos,则返回None。pos和endpos的默认值分别为0和len(string)。
注意:这个方法并不是完全匹配。当pattern结束时若string还有剩余字符,仍然视为成功。想要完全匹配,可以在表达式末尾加上边界匹配符’$’。
**search(string[, pos[, endpos]]) | re.search(pattern, string[, flags]): **
这个方法从string的pos下标处起尝试匹配pattern:如果pattern结束时仍可匹配,则返回一个Match对象若无法匹配,则将pos加1后重新尝试匹配,直到pos=endpos时仍无法匹配则返回None。pos和endpos的默认值分别为0和len(string));
split(string[, maxsplit]) | re.split(pattern, string[, maxsplit]):
按照能够匹配的子串将string分割后返回列表。maxsplit用于指定最大分割次数,不指定将全部分割。
findall(string[, pos[, endpos]]) | re.findall(pattern, string[, flags]):
搜索string,以列表形式返回全部能匹配的子串。
finditer(string[, pos[, endpos]]) | re.finditer(pattern, string[, flags]):
搜索string,返回一个顺序访问每一个匹配结果(Match对象)的迭代器。
sub(repl, string[, count]) | re.sub(pattern, repl, string[, count]):
使用repl替换string中每一个匹配的子串后返回替换后的字符串。
当repl是一个字符串时,可以使用\id或\g、\g引用分组,但不能使用编号0。
当repl是一个方法时,这个方法应当只接受一个参数(Match对象),并返回一个字符串用于替换(返回的字符串中不能再引用分组)。 count用于指定最多替换次数,不指定时全部替换。
subn(repl, string[, count]) |re.sub(pattern, repl, string[, count]):
返回 (sub(repl, string[, count]), 替换次数)。
#版本1:利用正则表达式和计数器
import re
from collections import Counter
def get_max_value_v1(text):
text = text.lower()
result = re.findall('[a-zA-Z]', text) # 去掉列表中的符号符
count = Counter(result) # Counter({'l': 3, 'o': 2, 'd': 1, 'h': 1, 'r': 1, 'e': 1, 'w': 1})
count_list = list(count.values())
max_value = max(count_list)
max_list = []
for k, v in count.items():
if v == max_value:
max_list.append(k)
max_list = sorted(max_list)
return max_list[0]
#利用计数器
from collections import Counter
def get_max_value(text):
count = Counter([x for x in text.lower() if x.isalpha()])
m = max(count.values())
return sorted([x for (x, y) in count.items() if y == m])[0]
#version 3
import string
def get_max_value(text):
text = text.lower()
return max(string.ascii_lowercase, key=text.count)
# map函数
#T h e M i s s i s s i p p i R i v e r
#[1, 1, 2, 2, 1, 5, 4, 4, 5, 4, 4, 5, 2, 2, 5, 2, 1, 5, 1, 2, 1]
sentence='The Mississippi River'
def count_chars(s):
s=s.lower()
count=list(map(s.count,s))
return (max(count))
print count_chars(sentence)
jieba是一个非常好用的中文工具,是以分词起家的,但是功能比分词要强大很多。是目前开源软件中,最好用的中文分析库之一。Python环境下需要安装并导入:
import jieba
jieba.cut 以及 jieba.cut_for_search 返回的结构都是一个可迭代的 generator,可以使用 for 循环来获得分词后得到的每一个词语(unicode)
jieba.cut 方法接受三个输入参数:
jieba.cut_for_search 方法接受两个参数:该方法适合用于搜索引擎构建倒排索引的分词,粒度比较细
添加用户自定义词典
关于TF-IDF和TextRank的理论,将在下一篇博客中给出,这里只关注这两个方法在jieba中的使用;
通常数据量很大的时候,可以选用TextRank提取关键词(TextRank与Google的PageRank类似,通过分析大量的文档获得更加精确的关系);
TF-IDF可以通过优化停用词库和自定义语料库,也可以获得比较好的效果;
TF: 当前词在当前文档中出现的频率
IDF: 这个词在所有文档中出现的频率,逆向文件频率
用法:
import jieba.analyse
jieba.analyse.extract_tags(sentence, topK=20, withWeight=False, allowPOS=())
关于TF-IDF 算法的关键词抽取补充内容:
1. 关键词提取所使用逆向文件频率(IDF)文本语料库可以切换成自定义语料库的路径
自定义语料库示例可参考:
https://github.com/fxsjy/jieba/blob/master/extra_dict/idf.txt.big
用法示例可参考
https://github.com/fxsjy/jieba/blob/master/test/extract_tags_idfpath.py
2. 关键词提取所使用停止词(Stop Words)文本语料库可以切换成自定义语料库的路径
自定义语料库示例可参考
https://github.com/fxsjy/jieba/blob/master/extra_dict/stop_words.txt
用法示例可参考
https://github.com/fxsjy/jieba/blob/master/test/extract_tags_stop_words.py
3.关键词一并返回关键词权重值示例
https://github.com/fxsjy/jieba/blob/master/test/extract_tags_with_weight.py
# 直接使用,接口相同,注意默认过滤词性:
jieba.analyse.textrank(sentence, topK=20, withWeight=False, allowPOS=('ns', 'n', 'vn', 'v'))
# 新建自定义 TextRank 实例
jieba.analyse.TextRank()
原始论文: TextRank: Bringing Order into Texts
http://web.eecs.umich.edu/~mihalcea/papers/mihalcea.emnlp04.pdf
基本思想:
# 新建自定义分词器,tokenizer 参数可指定内部使用的 jieba.Tokenizer 分词器。
# jieba.posseg.dt 为默认词性标注分词器
jieba.posseg.POSTokenizer(tokenizer=None)
标注句子分词后每个词的词性,采用和 ictclas 兼容的标记法。
具体的词性对照表参见计算所汉语词性标记集
http://ictclas.nlpir.org/nlpir/html/readme.htm
import jieba.posseg as pseg
words = pseg.cut("我爱自然语言处理")
for word, flag in words:
print('%s %s' % (word, flag))
Out:
我 r
爱 v
自然语言 l
处理 v
原理: 将目标文本按行分隔后,把各行文本分配到多个 Python 进程并行分词,然后归并结果,从而获得分词速度的可观提升 基于 python 自带的 multiprocessing 模块,目前暂不支持 Windows
用法:
实验结果:在 4 核 3.4GHz Linux 机器上,对金庸全集进行精确分词,获得了 1MB/s 的速度,是单进程版的 3.3 倍。
注意: 并行分词仅支持默认分词器 jieba.dt 和 jieba.posseg.dt。
返回词语在原文的起止位置(定位)
注意,输入参数只接受 unicode
from jieba.analyse import ChineseAnalyzer
使用示例:python -m jieba news.txt > cut_result.txt
命令行选项(翻译):
使用: python -m jieba [options] filename
结巴命令行界面。
固定参数:
filename 输入文件
可选参数:
-h, --help 显示此帮助信息并退出
-d [DELIM], --delimiter [DELIM]
使用 DELIM 分隔词语,而不是用默认的’ / '。
若不指定 DELIM,则使用一个空格分隔。
-p [DELIM], --pos [DELIM]
启用词性标注;如果指定 DELIM,词语和词性之间
用它分隔,否则用 _ 分隔
-D DICT, --dict DICT 使用 DICT 代替默认词典
-u USER_DICT, --user-dict USER_DICT
使用 USER_DICT 作为附加词典,与默认词典或自定义词典配合使用
-a, --cut-all 全模式分词(不支持词性标注)
-n, --no-hmm 不使用隐含马尔可夫模型
-q, --quiet 不输出载入信息到 STDERR
-V, --version 显示版本信息并退出
如果没有指定文件名,则使用标准输入。
–help 选项输出:
$> python -m jieba --help
Jieba command line interface.
positional arguments:
filename input file
optional arguments:
-h, --help show this help message and exit
-d [DELIM], --delimiter [DELIM]
use DELIM instead of ’ / ’ for word delimiter; or a
space if it is used without DELIM
-p [DELIM], --pos [DELIM]
enable POS tagging; if DELIM is specified, use DELIM
instead of ‘_’ for POS delimiter
-D DICT, --dict DICT use DICT as dictionary
-u USER_DICT, --user-dict USER_DICT
use USER_DICT together with the default dictionary or
DICT (if specified)
-a, --cut-all full pattern cutting (ignored with POS tagging)
-n, --no-hmm don’t use the Hidden Markov Model
-q, --quiet don’t print loading messages to stderr
-V, --version show program’s version number and exit
If no filename specified, use STDIN instead.
最后:这部分内容整理自Julyedu的课程笔记,接下来将整理关键词提取算法;