基于Python的中英文分词基础:正则表达式和jieba分词器

基于Python的中英文分词基础:正则表达式和jieba分词器

  • 前言介绍
  • 英文字符串处理
    • Python中的str
    • 正则表达式
      • Python中的正则表达式模块 re
    • 小练习
      • 字符串中出现频次最多的字母
      • 统计字符出现的频率
  • 中文字符串处理:jieba分词器
    • jieba可以做关键词抽取
      • 基于 TF-IDF 算法的关键词抽取(term frequency–inverse document frequency)
      • 基于 TextRank 算法的关键词抽取
    • jieba做词性标注
    • jieba分词加速:并行分词
    • Tokenize
    • ChineseAnalyzer for Whoosh 搜索引擎
    • 使用命令行分词

前言介绍

这篇博文是我的一次笔记,主要讨论NLP的基础知识:中英文字符串的处理,其中英文部分主要是正则表达式,中文部分是jieba分词器。对于其他常用的扩展包:如NLTK、spacy等将接下来的博文中进行更新。

语料库和词汇知识库对于NLP任务起着极其关键的重要,有时候是建立和改进一个NLP系统的“瓶颈”。对于“语料库和词汇知识库”的基础知识,可以参考宗成庆老师的专著:

《统计自然语言处理》

如果说图像处理技术是智能机器的视觉系统,那么自然语言处理就是智能机器的视觉系统、听觉系统、语言系统;CV注重对一帧一帧的图片或者一段连续的视频进行处理,提取重要信息,而NLP注重对文本、语音(语音识别、语音合成)等信息的理解和生成,不管是语音还是图像中的语言信息最终还是转换为文本进行处理;

如果说语言信息是一种物质的话,那么语音就是包裹物质的外壳,文本是传达物质信息的实际载体;写过爬虫或者从事技术开发的童鞋或多或少都要跟文本信息(字符串)打交道,本博文将简要介绍面向英文和中文字符串的常见处理方法;

英文字符串处理

Python中的str

通过python中的字符串操作(str),可以直接完成一些简单的字符串处理任务,下图采用dir()函数快速了解str对象中的常用内置函数:基于Python的中英文分词基础:正则表达式和jieba分词器_第1张图片
下面将挑一些使用频率比较高的内置函数进行演示:

  • 去空格和特殊字符:s.strip() 将字符串头和尾的空格,以及位于头尾的\n \t之类给删掉;
    s.lstrip():从左侧起,去掉左侧括号内的字符,s.rstrip():去掉右边;
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
  • 查找字符后者子串:str1.index(str2)
# 返回值< 0 为未找到
In:
sStr1 = 'strchr'
sStr2 = 'r'
nPos = sStr1.index(sStr2)
print nPos

Out:
2
  • 比较两个字符串:cmp(str1,str2); 返回-1:str1str2; 0: str1=str1
In: 
sStr1 = 'strchr'
sStr2 = 'strch'
print cmp(sStr2,sStr1)
print cmp(sStr1,sStr2)
print cmp(sStr1,sStr1)

Out:
-1
1
0
  • 字符串的大小写:str1.upper()str1.lower()
In:
sStr1 = 'JCstrlwr'
sStr1 = sStr1.upper()
print sStr1
sStr1 = sStr1.lower()
print sStr1

Out:
JCSTRLWR
jcstrlwr
  • 翻转字符串:str1[::-1]
In:
sStr1 = 'abcdefg'
sStr1 = sStr1[::-1]
print sStr1

Out:
gfedcba
  • 查找字符串:str1.find(str2) 返回str2中首字母出现的下标,不存在返回-1
In:
sStr1 = 'abcdefg'
sStr2 = 'cde'
print sStr1.find(sStr2)

Out:
2
  • 分割字符串:s.split()
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

Python中的正则表达式模块 re

re模块 (import re),
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]):

  • 这个是Pattern类的工厂方法,用于将字符串形式的正则表达式编译为Pattern对象。
  • 第二个参数flag是匹配模式,取值可以使用按位或运算符’|‘表示同时生效,比如re.I | re.M。
    flag可选值有:
    re.I(re.IGNORECASE): 忽略大小写(括号内是完整写法,下同)
    re.M(MULTILINE): 多行模式,改变’^‘和’$‘的行为
    re.S(DOTALL): 点任意匹配模式,改变’.'的行为
    re.L(LOCALE): 使预定字符类 \w \W \b \B \s \S 取决于当前区域设定
    re.U(UNICODE): 使预定字符类 \w \W \b \B \s \S \d \D 取决于unicode定义的字符属性
    re.X(VERBOSE): 详细模式。这个模式下正则表达式可以是多行,忽略空白字符,并可以加入注释。

当然,也可以在字符串中指定模式,比如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。
方法:

  • group([group1, …]):
    获得一个或多个分组截获的字符串;指定多个参数时将以元组形式返回。group1可以使用编号也可以使用别名;编号0代表整个匹配的子串;不填写参数时,返回group(0);没有截获字符串的组返回None;截获了多次的组返回最后一次截获的子串。
  • groups([default]):
    以元组形式返回全部分组截获的字符串。相当于调用group(1,2,…last)。default表示没有截获字符串的组以这个值替代,默认为None。
  • groupdict([default]):
    返回以有别名的组的别名为键、以该组截获的子串为值的字典,没有别名的组不包含在内。default含义同上。
  • start([group]):
    返回指定的组截获的子串在string中的起始索引(子串第一个字符的索引)。group默认值为0。
  • end([group]):
    返回指定的组截获的子串在string中的结束索引(子串最后一个字符的索引+1)。group默认值为0。
  • span([group]):
    返回(start(group), end(group))。
  • expand(template):
    将匹配到的分组代入template中然后返回。template中可以使用\id或\g、\g引用分组,但不能使用编号0。\id与\g是等价的;但\10将被认为是第10个分组,如果你想表达\1之后是字符’0’,只能使用\g<1>0。

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分词器

jieba是一个非常好用的中文工具,是以分词起家的,但是功能比分词要强大很多。是目前开源软件中,最好用的中文分析库之一。Python环境下需要安装并导入:

import jieba

基于Python的中英文分词基础:正则表达式和jieba分词器_第2张图片
jieba.cut 以及 jieba.cut_for_search 返回的结构都是一个可迭代的 generator,可以使用 for 循环来获得分词后得到的每一个词语(unicode)
jieba.cut 方法接受三个输入参数:

  • 需要分词的字符串
  • cut_all 参数用来控制是否采用全模式
  • HMM 参数用来控制是否使用 HMM 模型(分词效果比较好的,一般都需要隐马尔可夫模型)

jieba.cut_for_search 方法接受两个参数:该方法适合用于搜索引擎构建倒排索引的分词,粒度比较细

  • 需要分词的字符串
  • 是否使用 HMM 模型。
    jieba.lcut 以及 jieba.lcut_for_search 直接返回 list 而不是 generator,用法与上面的一致。

添加用户自定义词典

  • 很多时候我们需要针对自己的场景进行分词,会有一些领域内的专有词汇。
    • 可以用 jieba.load_userdict(file_name) 加载用户字典;
    • 少量的词汇可以自己用下面方法手动添加:
      • add_word(word, freq=None, tag=None) 和 del_word(word) 在程序中动态修改词典
        注意: 这种情况下HMM 需要被设置为False,不然HMM会忽略掉动态修改的信息;
      • suggest_freq(segment, tune=True) 可调节单个词语的词频,使其能(或不能)被分出来。

jieba可以做关键词抽取

关于TF-IDF和TextRank的理论,将在下一篇博客中给出,这里只关注这两个方法在jieba中的使用;
通常数据量很大的时候,可以选用TextRank提取关键词(TextRank与Google的PageRank类似,通过分析大量的文档获得更加精确的关系);
TF-IDF可以通过优化停用词库和自定义语料库,也可以获得比较好的效果;

基于 TF-IDF 算法的关键词抽取(term frequency–inverse document frequency)

TF: 当前词在当前文档中出现的频率
IDF: 这个词在所有文档中出现的频率,逆向文件频率
用法:

import jieba.analyse
jieba.analyse.extract_tags(sentence, topK=20, withWeight=False, allowPOS=())
  • sentence 为待提取的文本
  • topK 为返回几个 TF/IDF 权重最大的关键词,默认值为 20
  • withWeight 为是否一并返回关键词权重值,默认值为 False
  • allowPOS 仅包括指定词性的词,默认值为空,即不筛选

关于TF-IDF 算法的关键词抽取补充内容:
1. 关键词提取所使用逆向文件频率(IDF)文本语料库可以切换成自定义语料库的路径

  • 用法: jieba.analyse.set_idf_path(file_name) # file_name为自定义语料库的路径
    • 自定义语料库示例可参考:

      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)文本语料库可以切换成自定义语料库的路径

  • 用法: jieba.analyse.set_stop_words(file_name) # file_name为自定义语料库的路径
    • 自定义语料库示例可参考

      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

基于 TextRank 算法的关键词抽取

# 直接使用,接口相同,注意默认过滤词性:
 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

基本思想:

  • 将待抽取关键词的文本进行分词
  • 以固定窗口大小(默认为5,通过span属性调整),词之间的共现关系,构建图
  • 计算图中节点的PageRank,注意是无向带权图

jieba做词性标注

# 新建自定义分词器,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

jieba分词加速:并行分词

原理: 将目标文本按行分隔后,把各行文本分配到多个 Python 进程并行分词,然后归并结果,从而获得分词速度的可观提升 基于 python 自带的 multiprocessing 模块,目前暂不支持 Windows
用法:

  • jieba.enable_parallel(4) # 开启并行分词模式,参数为并行进程数;
  • jieba.disable_parallel() # 关闭并行分词模式;

实验结果:在 4 核 3.4GHz Linux 机器上,对金庸全集进行精确分词,获得了 1MB/s 的速度,是单进程版的 3.3 倍。
注意: 并行分词仅支持默认分词器 jieba.dt 和 jieba.posseg.dt。

Tokenize

返回词语在原文的起止位置(定位)
注意,输入参数只接受 unicode

ChineseAnalyzer for Whoosh 搜索引擎

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的课程笔记,接下来将整理关键词提取算法;

你可能感兴趣的:(NLP,自然语言处理,jieba分词器,正则表达式)