第9天:NLP补充——需要的基本知识

    技术2022-07-11  96

    一、字符串的一些简单处理

      字符串是我们组成我们文本的最基本的单位,因此,在自然语言处理中,字符串的处理一般是在文本预处理这一环节中,因此,在学习自然语言处理之前,掌握一些基础的字符串处理的方式是很有必要的。首先让我们回归一下python字符串的相关操作。这些操作我们用的是一款开源的jupyter notebook来编写运行代码。因此在运行下面代码的时候我们要保证自己的电脑安装了jupyter notebook以及python。安装的过程很简单且网上有很多方法以及文章,大家自己可以去看。 1、 去空格及特殊符号

    s = ' hello, world!' print s.strip() print s.lstrip(' hello, ') print s.rstrip('!')

    2、连接字符串

    sStr1 = 'stract' sStr2 = 'append' sStr1 += sStr2 print(sStr1)

    3、查找字符

    str1 = 'abcdg' str2 = 'cdg' print(str1.find(str2))

    4、分割字符串

    str1 = 'ab, cde, fgh, ijk' str2 = ', ' str1 = str1[str1.find(str2)+1:] print(str1) print ('-------------------第二种方法----------------------------') s = 'ab, cde, fgh, ijk' print(s.split(','))

    5、计算字符串中出现频次最多的字母

    print('*****************方法一*************************') import re from collections import Counter def get_max_value_vl(text): text = text.lower() result = re.findall('[a-zA-Z]', text) count = Counter(result) count_list = list(count.value()) 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] print('*****************方法二*************************') 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] print('*****************方法三*************************') import string def get_max_value3(text): text = text.lower() return max(string.ascii_lowercase, key = text.count) max (range(6), key = lambda x: x > 2) max ([3,5,2,4,1,3,0], key = lambda x:x) max ('ah', 'bf', key = lambda x: x[1]) max('ah', 'bf', key = lambda x: x[0]) text = 'Hello World' max ('abcdefghijklmnopqrsfffffffffffffffffffffftuvwxyz', key = text.count)

    6、比较字符串

    import operator sStr1 = 'strchr' sStr2 = 'strch' print(operator.eq(sStr2, sStr1)) print(operator.eq(sStr1, sStr2)) print(operator.eq(sStr1, sStr1))

    7、字符串中的大小写转换

    str1 = 'JCstrlwr' str1 = str1.upper() str2 = str1.lower() print(str1) print(str2)

    8、在字符串中统计出现的字符

    sentence = 'The Mississippi River' def count_chars(s): s=s.lower() count=list(map(s.count,s)) return (max(count)) print (count_chars(sentence))

    9、反转字符串

    str1 = 'abcdefg' str1 = str1[::-1] print(str1)

      这些方法是我们在处理字符串中最基本也是最常用的方法,大家一定要熟练掌握,为文本预处理的第一步做好基础。接下来给大家介绍正则表达式,它能够处理更复杂的文本。

    二、python正则表达式

      正则表达式是处理字符串的强大工具,拥有独特的语法和独立的处理引擎。我们在大文本中匹配字符串时,有些情况用str自带的函数可能可以完成,有些情况会稍稍复杂一些,这个时候我们需要正则表达式了。接下来我们详细介绍正则表达式。 1、语法   当我们要匹配一个/多个/任意个 数字/字母/非数字/非字母/某几个字符/任意字符,想要 贪婪/非贪婪 匹配,想要捕获匹配出来的 第一个/所有 内容的时候,这里有一张表可以供大家参考: 2、匹配工具推荐   网上有很多正则表达式练习效果的工具,但是个感觉这个工具是用的最好的。这款工具是一款在线验证你写的正则表达式的正确与否的工具。具体的效果如下: 3、练习正则表达式   正则表达式需要我们经常的练习去让我们更加熟练掌握经常的规则,在这里给大家推荐一款比较好用的练习正则表达式的在线软件。它是一种类似于游戏过关,有排名也有得分,当你写对第一关,直接跳到下一关。具体效果图如图所示: 4、字符串常用的模块 (1)、re模块

    使用re的一般步骤是: ①、将正则表达式的字符串形式编译为Pattern实例 ②、使用Pattern实例处理文本并获得匹配结果 ③、使用Match实例获得信息,进行其他的操作。

    import re pattern = re.compile(r'hello.*\!') match = pattern.match('hello everyone, my name is stefan!') if match: print(match.group())

    re.compile(strPattern[, flag]): 这个方法是Pattern类的工厂方法,用于将字符串形式的正则表达式编译为Pattern对象。 第二个参数flag是匹配模式,取值可以使用按位或运算符’|‘表示同时生效,比如re.I | re.M。 当然,我们也可以在regex字符串中指定模式,比如re.compile(‘pattern’, re.I | re.M)等价于re.compile(’(?im)pattern’) 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): 详细模式。这个模式下正则表达式可以是多行,忽略空白字符,并可以加入注释。以下两个正则表达式是等价的:

    regex_1 = re.compile(r"""\d + # 数字部分 \. # 小数点部分 \d * # 小数的数字部分""", re.X) regex_2 = re.compile(r"\d+\.\d*")

    (2) 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。

    import re m = re.match(r'(\w+)(\w)(?P<sign>.*)', 'hello everyone!') print("m.string:", m.string) print("m.re", m.re) print("m.pos", m.pos) print("m.endpos", m.endpos) print("m.lastindex", m.lastindex) print("m.lastgroup", m.lastgroup) print("m.group(1, 2)", m.group(1,2)) print("m.group():", m.groups()) print("m.groupdict():", m.groupdict()) print("m.start(2):", m.start(2)) print("m.span(2):", m.span(2)) print(r"m.expend(r'\2\1\3'):", m.expand(r'\2\1\3'))

    (3)、Pattern   Pattern对象是一个编译好的正则表达式,通过Pattern提供的一系列方法可以对文本进行匹配查找。Pattern不能直接实例化,必须使用re.compile()进行构造。

    Pattern提供了几个可读属性用于获取表达式的相关信息:  pattern: 编译时用的表达式字符串。  flags: 编译时用的匹配模式。数字形式。  groups: 表达式中分组的数量。  groupindex: 以表达式中有别名的组的别名为键、以该组对应的编号为值的字典,没有别名的组不包含在内。

    import re p = re.compile(r'(\w+)(\w+)(?P<sign>.*)', re.DOTALL) print("p.pattern:", p.pattern) print("p.flags:", p.flags) print("p.groups:", p.groups) print("p.groupindex", p.groupindex)

    (4)、使用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))

    import re pattern = re.compile(r'H.*g') match = pattern.search('hello Hanxiaoyang!') if match: print(match.group())

    split(string[, maxsplit]) | re.split(pattern, string[, maxsplit]):  按照能够匹配的子串将string分割后返回列表。  maxsplit用于指定最大分割次数,不指定将全部分割。

    import re p = re.compile(r'\d+') print(p.split('one1two2three3four4'))

    findall(string[, pos[, endpos]]) | re.findall(pattern, string[, flags]):  搜索string,以列表形式返回全部能匹配的子串。

    import re p = re.compile(r'\d+') print(p.findall('one1two2three3four4'))

    finditer(string[, pos[, endpos]]) | re.finditer(pattern, string[, flags]):  搜索string,返回一个顺序访问每一个匹配结果(Match对象)的迭代器。

    import re p = re.compile(r'\d+') for m in p.finditer('one1two2three3four4'): print(m.group())

    sub(repl, string[, count]) | re.sub(pattern, repl, string[, count]):  使用repl替换string中每一个匹配的子串后返回替换后的字符串。   当repl是一个字符串时,可以使用\id或\g、\g引用分组,但不能使用编号0。   当repl是一个方法时,这个方法应当只接受一个参数(Match对象),并返回一个字符串用于替换(返回的字符串中不能再引用分组)。 count用于指定最多替换次数,不指定时全部替换。

    import re p = re.compile(r'(\w+)(\w+)') s = 'i want to say,hello stefan,i love you!!!' print(p.sub(r'\2\1', s)) def func(m): return m.group(1).title() + ' '+ m.group(2).title() print(p.sub(func, s))

    subn(repl, string[, count]) |re.sub(pattern, repl, string[, count]):  返回 (sub(repl, string[, count]), 替换次数)。

    import re p = re.compile(r'(\w+)(\w+)') s = 'i want to say,hello stefan,i love you!!!' print(p.subn(r'\2\1', s)) def func(m): return m.group(1).title()+' ' + m.group(2).title() print (p.subn(func, s))

      这些正则表达式中提到的的方法是我们在处理字符串中最基本也是最常用的方法,大家一定要熟练掌握,为文本预处理的第一步做好基础。接下来给大家介绍结巴分词,它是我们在处理分词的第一个最为常用的工具。

    三、jieba中文处理

      我们在做自然语言处理时,见到最多的语料库就是英文,比较简单,我们只需要用到nltk即可。但是我们也会常常处理一些中文的语料,与英文不同的是,中文不用空格分开每个有意义的词的。处理中文的词库有很多,比如snowNLP、LTP、HanLP以及结巴分词。但是我们最常用的一种分词就是jieba分词。接下来给大家详细介绍结巴分词。 1、基本分词函数与用法   jieba.cut 以及 jieba.cut_for_search 返回的结构都是一个可迭代的 generator,可以使用 for 循环来获得分词后得到的每一个词语(unicode)

    ①、 jieba.cut 方法接受三个输入参数:  需要分词的字符串  cut_all 参数用来控制是否采用全模式  HMM 参数用来控制是否使用 HMM 模型 ②、jieba.cut_for_search 方法接受两个参数  需要分词的字符串  是否使用 HMM 模型。

      该方法适合用于搜索引擎构建倒排索引的分词,粒度比较细

    import jieba seg_list = jieba.cut("我喜欢你,你喜欢我吗?", cut_all = True) print(seg_list) 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.lcut以及jieba.lcut_for_search直接返回 list

    result_lcut = jieba.lcut("我爱我党,把每一份热心都送给人民") print(result_lcut) print(" ".join(result_lcut)) print(" ".join(jieba.lcut_for_search("我爱我党,把每一份热心都送给人民")))

    2、添加用户自定义词典   很多时候我们需要针对自己的场景进行分词,会有一些领域内的专有词汇。

    ①、可以用jieba.load_userdict(file_name)加载用户字典 ②、少量的词汇可以自己用下面方法手动添加:  用 add_word(word, freq=None, tag=None) 和 del_word(word) 在程序中动态修改词典  用 suggest_freq(segment, tune=True) 可调节单个词语的词频,使其能(或不能)被分出来。

    print('/'.join(jieba.cut('我爱我党,把每一份热心都送给人民!', HMM = False))) jieba.suggest_freq(('送','给'), True) print('/'.join(jieba.cut('我爱我党,把每一份热心都送给人民!', HMM=False)))

    3、关键词提取 基于 TF-IDF 算法的关键词抽取

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

      这里需要我们注意的是:本代码中用到了两个文档,一个是NBA.txt(提取码:rig3),另一个是西游记.txt(提取码:wia4),当然大家也可以选择用其他的语料库。但是我们一定要将其语料库放在代码同一级目录下,如图所示:   其中语料库分别如下图所示:

    import jieba.analyse as analyse lines = open('NBA.txt',encoding='UTF-8').read() print(" ".join(analyse.extract_tags(lines, topK = 20, withWeight=False, allowPOS=()))) lines = open('西游记.txt',encoding='UTF-8').read() print(" ".join(analyse.extract_tags(lines, topK = 40, withWeight=False, allowPOS=())))

      另外还有一些关于TF-IDF 算法的关键词抽取补充:

    关键词提取所使用逆向文件频率(IDF)文本语料库可以切换成自定义语料库的路径 用法: jieba.analyse.set_idf_path(file_name) # file_name为自定义语料库的路径  自定义语料库示例见这里 关键词提取所使用停止词(Stop Words)文本语料库可以切换成自定义语料库的路径  用法: jieba.analyse.set_stop_words(file_name) # file_name为自定义语料库的路径  自定义语料库示例见这里  用法示例见这里 关键词一并返回关键词权重值示例  用法示例见这里

    基于 TextRank 算法的关键词抽取

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

    import jieba.analyse as analyse lines = open('NBA.txt',encoding='UTF-8').read() print(" ".join(analyse.textrank(lines, topK=40, withWeight=False, allowPOS=('ns', 'n', 'vn','v')))) print("--------------others-------------------") print(" ".join(analyse.textrank(lines, topK=40, withWeight=False, allowPOS=('ns','n')))) lines = open('西游记.txt',encoding='UTF-8').read() print(" ".join(analyse.textrank(lines, topK=40, withWeight=False, allowPOS=('ns', 'n', 'vn','v'))))

    4、词性标注

    jieba.posseg.POSTokenizer(tokenizer=None) 新建自定义分词器,tokenizer 参数可指定内部使用的 jieba.Tokenizer 分词器。jieba.posseg.dt 为默认词性标注分词器。  标注句子分词后每个词的词性,采用和 ictclas 兼容的标记法。  具体的词性对照表参见计算所汉语词性标记集

    import jieba.posseg as pseg words = pseg.cut("我爱我党") for word, flag in words: print('%s %s' % (word, flag))

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

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

      注意:并行分词仅支持默认分词器 jieba.dt 和 jieba.posseg.dt。 由于本人未装Linux系统,因此本快代码就不掩演示了。 6、Tokenize:返回词语在原文的起止位置   注意,输入参数只接受 unicode

    print("这是默认的模式:tokenize") result = jieba.tokenize(u'我爱中国国产党') for tk in result: print("%s\t\t start: %d \t\t end:%d" % (tk[0], tk[1], tk[2])) result = jieba.tokenize(u'我爱中国共产党', mode = 'search') for tk in result: print("%s\t\t start: %d \t\t end:%d" % (tk[0], tk[1], tk[2]))

    7、ChineseAnalyzer for Whoosh 搜索引擎   用法:

    这里需要首先安装whoosh第三方库 这个库主需要用 pip install whoosh 命令安装即可,安装完后重启jupyter notebook,即可导入。 from jieba.analyse import ChineseAnalyzer

    from __future__ import unicode_literals import jieba import sys,os sys.path.append("../") from whoosh.index import create_in,open_dir from whoosh.fields import * from whoosh.qparser import QueryParser from jieba.analyse import ChineseAnalyzer analyzer = jieba.analyse.ChineseAnalyzer() schema = Schema(title=TEXT(stored=True), path=ID(stored=True), content=TEXT(stored=True, analyzer=analyzer)) if not os.path.exists("tmp"): os.mkdir("tmp") ix = create_in("tmp", schema) # for create new index #ix = open_dir("tmp") # for read only writer = ix.writer() writer.add_document( title="document1", path="/a", content="This is the first document we’ve added!" ) writer.add_document( title="document2", path="/b", content="The second one 你 中文测试中文 is even more interesting! 吃水果" ) writer.add_document( title="document3", path="/c", content="买水果然后来世博园。" ) writer.add_document( title="document4", path="/c", content="工信处女干事每月经过下属科室都要亲口交代24口交换机等技术性器件的安装工作" ) writer.add_document( title="document4", path="/c", content="咱俩交换一下吧。" ) writer.commit() searcher = ix.searcher() parser = QueryParser("content", schema=ix.schema) for keyword in ("水果世博园","你","first","中文","交换机","交换"): print(keyword+"的结果为如下:") q = parser.parse(keyword) results = searcher.search(q) for hit in results: print(hit.highlights("content")) print("\n--------------我是神奇的分割线--------------\n") for t in analyzer("我的好朋友是李明;我爱北京天安门;IBM和Microsoft; I have a dream. this is intetesting and interested me a lot"): print(t.text)

      以上就是所有关于jieba分词常用地用法,希望大家能够将其掌握,更好的应用在我们的文本与处理中。

    四、总结

      由于文本预处理是自然语言处理中首要的环节,因此,我们通过本文为大家补充了在NLP学习之前我们应该掌握的一些方法。字符串是我们NLP文本预处理的最基本的单位,因此,首先给大家介绍了字符串中常用的一些方法,但是,在复杂的文本中这些方法是远远不够的,接下来又给大家详细介绍了正则表达式以及jieba分词,以及介绍了他们在NLP中常用的一些功能,又通过代码将其实现,让读者既可以了解其用法还可以进行实际的操作,对该部分内容进行深刻的掌握。

    Processed: 0.010, SQL: 9