.
.
.
目前在学习NLP 在学习分词时自己做的一个案例.参考维特比算法.
下面是整个系统的流程图.
-
首先,将原始文本,通过正则表达式根据非中文字符进行拆分。得到一个句子的列表,然后分别对每一个句子进行分词处理。
-
对于单个句子,首先进行按照不同的字长进行划分,如单字,双字和三字等等放入汉字列表并去除重复元素。
-
加载词库,将获得的汉字列表,在词库中进行匹配. .如果存在,则将权值设置为字频的倒数.存入字典中,格式如下{‘我’:1/f,’去一’:1000}.
-
下面以句子中每一个单词为结点以字典为对应权值建立有向网.
-
使用动态规划求出最短路径
-
将最短路径转化为汉字.
案例说明:
待分词句子:
sentence="经常有意见分歧"
可能分词
['经', '常', '有', '意', '见', '分', '歧', '经常', '常有', '有意', '意见', '见分', '分歧',
'歧', '经常有', '常有意', '有意见', '意见分', '见分歧', '分歧', '歧', '经常有意',
'常有意见', '有意见分', '意见分歧', '见分歧', '分歧', '歧', '经常有意见', '常有意见分',
'有意见分歧', '意见分歧', '见分歧', '分歧', '歧']
在字典中查找分词:生成字典
{"经常":0.23, "经":0.3, "有":0.23, "有意见":0.23,"意见":0.16,'常':1000,'歧':1000,
"分歧":0.16,"见":0.3,"意":0.3,"见分歧":0.3, "分":0.23,"我":0.16}
构造有向网如下图所示.
该图的逆邻接表
{
1: [],
2: [[1, 0.3]],
3: [[2, 1000], [1, 0.23]],
4: [[3, 0.23]],
5: [[4, 0.3]],
6: [[5, 0.3], [3, 0.23], [4, 0.16]],
7: [[6, 0.23]],
8: [[7, 1000], [6, 0.16], [5, 0.3]]
}
构造path 和cost表
path表
节点 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 |
---|---|---|---|---|---|---|---|---|
下一跳 | 0 | 1 | 1 | 3 | 4 | 3 | 6 | 6 |
cost表
节点 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 |
---|---|---|---|---|---|---|---|---|
费用 | 0 | 0. 3 | 0. 23 | 0. 46 | 0. 76 | 0.46 | 0.69 | 0. 62 |
则最短路径如上图,绿色部分所示.
即为 8–>6–>3—>1
1 -经- 2 -常- 3 -有- 4 -意- 5 -见- 6 -分- 7 -歧- 8
1-3对应 [经常]
3-6:对应[有意见]
6-8: 对应[分歧]
所以结果为
[‘经常’,’有意见’,’分歧’].
具体代码实现
条目 | 内容 |
---|---|
开发环境 | Ubuntu+Python3 |
运行环境 | Ubuntu+Python3 |
涉及的库函数 | Pandas numpy re |
- 拆分句子:
使用re模块的split方法,将所有的特殊符号作为分隔符,返回只有汉字的列表,共以下函数使用.
p=re.compile("[a-z,\-。、·:,【】|、\d — “!@$^&*”!#%……&×() \n \t \b~]",re.M)
lis_sen=[ x for x in re.split(p,text) if x!=""]
- 对单个句子使用不同的字长,划分为词组列表.
def devide(self,s):
a=[]
for j in range(1,self.max_step+1):#max_step为步长
c=[]
for i in range(len(s)):
c.append(s[i:i+j])
a=a+c
return a
- 加载词库并根据单词过滤:读取res目录下的dict.csv 文件.
def load_ciku(self):
data=pandas.read_csv("../res/dict_.csv",sep='\t')
data_=numpy.array(data)
print("加载词库....")
dic=[x[0].split(',') for x in data_]
print("解析词库....")
dic={x[0]:x[1].split(' ')[0] for x in dic}
return dic
- 使用dict_filter()方法,在词库中提取所有词组列表中含有的单词,并添加权值,构造一个新的字典.
def dict_filter(self,sentence):
res=self.devide(sentence)
dic=self.dic
dictory={}
for x in res:
dictory[x]=1000
if dic.__contains__(x):
dictory[x]=1/int(dic[x])
if len(x)==1:
dictory[x]*=self.offset#消除单字过分的影响。
return dictory
以句子中的每个汉字作为节点,词组字典的权值,建立有向网,主要分为两个部分,
1.将单字符词组构建主干线路.将每个汉字根据句子顺序链接
2.将多字符字符词组添加到图中
def creat_words_graph(self,sentence,dictory):
length=len(sentence)
words_graph={x+1 : [] for x in range(length+1)}
for i in range(2,length+2):
distance=1000
if dictory.__contains__(sentence[i-2]):
distance=dictory[ sentence[i-2] ]
words_graph[i].append( [i-1, distance ] )
dict_=[list(x) for x in list(dictory.items())]
for x in dict_:
if len(x[0])==1:
continue
start=0
while sentence.index( x[0][-1],start)+2< sentence.index(x[0][0]) +1:
#由重复关键字造成的异常.使用 start去掉不合法的部分
start=sentence.index( x[0][-1],start)+1
words_graph[sentence.index( x[0][-1],start)+2].append([sentence.index(x[0][0]) +1, x[1] ])
return words_graph
- 计算根据动态规划最短路径.
def get_shorts_path(self,sentence,words_graph):
length=len(sentence)
cost=[0 for x in range(length+1)]
path=[0 for x in range(length+1)]
path[0]=-1
for i in range(1,length+1):
cost[i],path[i]=self.get_min_cost(words_graph[i+1],cost)
# get_min_cost 得到当前节点最适合的路径
path=[i+1 for i in path ]
short_path=[]
i=length
short_path.append(i+1)
while path[i] !=0:
node=path[i]
short_path.append(node)
i=node-1
short_path.reverse()
return short_path
测试用例.
INPUT[ ] : 其中包括对饮食材料与烹调方法以及烹调风味及佐料的偏好
RES [ ] : ['其中', '包括', '对', '饮食', '材料', '与', '烹调', '方法', '以及', '烹', '调', '风味', '及', '佐料', '的', '偏好']
INPUT[ ] : 我超级喜欢编程写代码,一天都不会感觉疲惫
RES [ ] : ['我', '超级', '喜欢', '编程', '写', '代码', '一', '天都', '不会', '感觉', '疲惫']
INPUT[ ] : 哈尔滨,简称“哈”,古称会宁府、阿勒锦,别称冰城,是黑龙江省省会
RES [ ] : ['哈尔滨', '简称', '哈', '古称', '会宁', '府', '阿勒锦', '别称', '冰城', '是', '黑龙江', '省省会']
源码地址:github
到此,各位少侠的word segmentation 就介绍到这里。
代码江湖漫漫,少侠下期再见!