自然语言处理
自然语言处理
1. NLTK python的NLP工具包
2. WordNet 英文的语义网络
3. BabelNet 多语言版的语义网络, 全部数据有29GB, 需要以科研的身份申请, 否则只能在线上用每天限量1k的接口. 等有空试验下接口.
4. 决策树分类器
5. 朴素贝叶斯分类器
6. 最大熵分类器 -- 需要补习概率论知识了
Update 2018-06-15: 浙大概率论第四版, 看了两章, 看懂贝叶斯公式了. 但是没看到有关最大熵的章节, 继续看NLTK, 百度了一些最大熵的说明, 这次大概看懂了, 但是后面的词性标注, 句法标注还是看得很懵, 貌似还是需要很多语言相关的先验知识. 下载了deep learning book 中文版, 打算直接看深度学习吧
基础知识
词向量和语言模型 http://licstar.net/archives/328
分词参考汇总
SNS文本数据挖掘 http://www.matrix67.com/blog/archives/5044
1. 基于AC自动机的分词
https://spaces.ac.cn/archives/3908
这个是基于词库(用AC自动机载入)的分词,用动态规划得到概率最大的分词结果
import ahocorasick import math def load_dic(dicfile): dic = ahocorasick.Automaton() total = 0.0 with open(dicfile, 'r', encoding='utf-8') as dicfile: words = [] for line in dicfile: line = line.split(' ') count = int(line[1]) if count <= 0: count = 1 words.append((line[0], count)) total += int(line[1]) for word,occurs in words: dic.add_word(word, (word, math.log(occurs/total))) #这里使用了对数概率,防止溢出 dic.make_automaton() return dic def print_all_cut(sentence): words = [] for i,j in dic.iter(sentence): print(i, j) dic = load_dic('dict.txt') # 可以看到, 序号就是词在句中的位置 print_all_cut('最大匹配法是指从左到右逐渐匹配词库中的词语') ''' 使用动态规划, 每次用start处的概率加上本词的概率, 如果end处未赋值或者end处的值比这个小, 就用这个值代替 如果start处还没有值, 就取start之前的最大的值造一个 ''' def max_proba_cut(sentence): paths = {0:([], 0)} end = 0 for i,j in dic.iter(sentence): start,end = 1+i-len(j[0]), i+1 if start not in paths: last = max([i for i in paths if i < start]) paths[start] = (paths[last][0]+[sentence[last:start]], paths[last][1]-10) proba = paths[start][1]+j[1] if end not in paths or proba > paths[end][1]: paths[end] = (paths[start][0]+[j[0]], proba) if end < len(sentence): return paths[end][0] + [sentence[end:]] else: return paths[end][0] path = max_proba_cut('最大匹配法是指从左到右逐渐匹配词库中的词语') print(path)
2A. 基于信息熵的新词发现
https://spaces.ac.cn/archives/3491
这是无词库的分词方式,对语料预处理,去掉中英文标点仅保留中英文数字并拆分为句子,对所有内容进行1~N元划分,分别计算支持度和熵
import numpy as np import pandas as pd import re from numpy import log, min f = open('d:/WorkPython/tmp/TianLongBaBu.txt', 'r', encoding='utf-8') # 读取文章 s = f.read() # 读取为一个字符串 # 定义要去掉的标点字 drop_dict = [u',', u'\n', u'。', u'、', u':', u'(', u')', u'[', u']', u'.', u',', u' ', u'\u3000', u'”', u'“', u'?', u'?', u'!', u'‘', u'’', u'…'] for i in drop_dict: # 去掉标点字 s = s.replace(i, '') # 为了方便调用,自定义了一个正则表达式的词典 myre = {2: '(..)', 3: '(...)', 4: '(....)', 5: '(.....)', 6: '(......)', 7: '(.......)'} min_count = 10 # 录取词语最小出现次数 min_support = 30 # 录取词语最低支持度,1代表着随机组合 min_s = 3 # 录取词语最低信息熵,越大说明越有可能独立成词 max_sep = 4 # 候选词语的最大字数 t = [] # 保存结果用。 l = list(s) # 将字符串拆成字符列表 serial = pd.Series(l) # 给每个字符加上编号, 从0开始 t.append(serial.value_counts()) # 对每个字符计算字频, 会产生一个按字频倒序的列表, 作为t[0] tsum = t[0].sum() # 统计总字数, .sum()函数是pandas.Series的一个函数 rt = [] # 保存结果用 for m in range(2, max_sep + 1): print(u'正在生成%s字词...' % m) t.append([]) for i in range(m): # 生成所有可能的m字词, 放到t[m-1] t[m - 1] = t[m - 1] + re.findall(myre[m], s[i:]) t[m - 1] = pd.Series(t[m - 1]).value_counts() # 对每个词计算词频, 产生按字频倒序的列表, 作为t[m-1] t[m - 1] = t[m - 1][t[m - 1] > min_count] # 去掉词频小于min_count的词 tt = t[m - 1][:] # 复制刚才产生的列表 ''' 用map的方式,将刚才产生的列表的每个元素,一一用lambda里的方法去计算, 这里 ms in tt.index ms是这个元素的值, 例如'段誉' aa = ms[:m-1] 前一部分, 例如'段' bb = tmp[m-1:] 后一部分, 例如'誉' cc = t[m - 1][tmp] 这个元素的词频 dd = t[m - 2][tmp[:m - 1]] 前一部分对应元素的词频 ee = t[0][tmp[m - 1:]] 后一部分对应元素的词频 ''' ld = lambda ms: tsum * t[m - 1][ms] / t[m - 2 - k][ms[:m - 1 - k]] / t[k][ms[m - 1 - k:]] for k in range(m - 1): qq = np.array(list(map(ld, tt.index))) > min_support # 最小支持度筛选。 tt = tt[qq] # pandas.Series可以通过[True, False, ...]这样的序列提取子集合, 不断滤掉未达到最小支持度的词 rt.append(tt.index) def cal_S(sl): # 信息熵计算函数 return -((sl / sl.sum()).apply(log) * sl / sl.sum()).sum() for i in range(2, max_sep + 1): print(u'正在进行%s字词的最大熵筛选(%s)...' % (i, len(rt[i - 2]))) pp = [] # 保存所有的左右邻结果 for j in range(i + 2): pp = pp + re.findall('(.)%s(.)' % myre[i], s[j:]) pp = pd.DataFrame(pp).set_index(1).sort_index() # 先排序,这个很重要,可以加快检索速度 index = np.sort(np.intersect1d(rt[i - 2], pp.index)) # 作交集 # 下面两句分别是左邻和右邻信息熵筛选 index = index[np.array(list(map(lambda s: cal_S(pd.Series(pp[0][s]).value_counts()), index))) > min_s] rt[i - 2] = index[np.array(list(map(lambda s: cal_S(pd.Series(pp[2][s]).value_counts()), index))) > min_s] # 下面都是输出前处理 for i in range(len(rt)): t[i + 1] = t[i + 1][rt[i]] t[i + 1].sort_values(ascending=False) # 保存结果并输出 pd.DataFrame(pd.concat(t[1:])).to_csv('result.txt', header=False)
2B. 基于切分的新词发现
https://spaces.ac.cn/archives/3913
简单地通过计算相邻两字出现的概率,断开概率低于阈值的字而达到分词的效果
import re import json from collections import defaultdict #defaultdict是经过封装的dict,它能够让我们设定默认值 from tqdm import tqdm #tqdm是一个非常易用的用来显示进度的库 from math import log def load_texts(text_file): with open(text_file, 'r', encoding='utf-8') as articles: for article in articles: json_obj = json.loads(article) if 'content' in json_obj: yield json_obj['content'] class Find_Words: def __init__(self, min_count=10, min_pmi=0): self.min_count = min_count self.min_pmi = min_pmi self.chars, self.pairs = defaultdict(int), defaultdict(int) #如果键不存在,那么就用int函数初始化一个值,int()的默认结果为0 self.total = 0. # 预切断句子,以免得到太多无意义(不是中文、英文、数字)的字符串 def text_filter(self, texts): for a in tqdm(texts): for t in re.split(u'[^\u4e00-\u9fa50-9a-zA-Z]+', a): #这个正则表达式匹配的是任意非中文、非英文、非数字,因此它的意思就是用任意非中文、非英文、非数字的字符断开句子 if t: yield t # 计数函数,计算单字出现频数、相邻两字出现的频数 def count(self, texts): for text in self.text_filter(texts): self.chars[text[0]] += 1 for i in range(len(text)-1): self.chars[text[i+1]] += 1 self.pairs[text[i:i+2]] += 1 self.total += 1 self.chars = {i:j for i,j in self.chars.items() if j >= self.min_count} #最少频数过滤 self.pairs = {i:j for i,j in self.pairs.items() if j >= self.min_count} #最少频数过滤 self.strong_segments = set() for i,j in self.pairs.items(): #根据互信息找出比较“密切”的邻字 _ = log(self.total*j/(self.chars[i[0]]*self.chars[i[1]])) if _ >= self.min_pmi: self.strong_segments.add(i) # 根据前述结果来找词语 def find_words(self, texts): self.words = defaultdict(int) for text in self.text_filter(texts): s = text[0] for i in range(len(text)-1): if text[i:i+2] in self.strong_segments: #如果比较“密切”则不断开 s += text[i+1] else: self.words[s] += 1 #否则断开,前述片段作为一个词来统计 s = text[i+1] self.words = {i:j for i,j in self.words.items() if j >= self.min_count} #最后再次根据频数过滤 fw = Find_Words(16, 1) fw.count(load_texts('D:/WorkPython/tmp/articles.json')) fw.find_words(load_texts('D:/WorkPython/tmp/articles.json')) import pandas as pd words = pd.Series(fw.words).sort_values(ascending=False) words.to_csv('output.csv')
3. 字标注法与HMM模型
https://spaces.ac.cn/archives/3922
4. 基于双向LSTM的seq2seq字标注
https://spaces.ac.cn/archives/3924
5. 基于语言模型的无监督分词
https://spaces.ac.cn/archives/3956
使用KenLM作为训练工具,使用预先分好词的语料进行训练,生成klm格式的语言模型后,在python中载入处理分词。
6. 基于全卷积网络的中文分词 https://spaces.ac.cn/archives/4195
7. 用词典完成深度学习分词 https://spaces.ac.cn/archives/4245
8. 改进新词发现算法 https://spaces.ac.cn/archives/6920
专业词汇的无监督挖掘 https://spaces.ac.cn/archives/6540
Keras seq2seq自动生成标题 https://kexue.fm/archives/5861