NLP-分词方法

一、正向最大匹配

从左到右选择词典中最长的词条进行匹配,获得分词结果。
1、统计分词词典,确定词典中最长词条的字符的长度m作为窗口大小;
2、从左向右取待切分语句的m个字符作为匹配字段,与词典中的词语进行匹配,若匹配成功,则作为一个切分后的词语,否则,去掉待匹配字符的最后一个字符继续与词典匹配,重复上述步骤直到切分出所有词语。

dictA = ['南京','南京市', '南京市长', '市长' ,'长江大桥',  '大桥']
maxDictA = max([len(word) for word in dictA])#取词典中词语的最大长度作为窗口长度

def cutA(sentence):
    result = []
    sentenceLen = len(sentence)
    n = 0

    while n < sentenceLen:
        matched = 0
        for i in range(maxDictA, 0, -1):#去掉待匹配字符的最后一个字符
            piece = sentence[n:n+i]
            if piece in dictA:
                result.append(piece)
                matched = 1
                n = n + i
                break
        if not matched:
            result.append(sentence[n])
            n += 1
    return result
print(cutA("南京市长江大桥"))
['南京市长', '江', '大桥']

二、逆向最大匹配

1、从右到左选择词典中最长的词条进行匹配,获得分词结果;
2、当匹配失败时,去掉待切分字符串的最前面的一个字符,继续与词典匹配。

dictA = ['南京','南京市', '南京市长', '市长' ,'长江大桥',  '大桥']
maxDictA = max([len(word) for word in dictA])

def cutB(sentence):
    result = []
    sentenceLen = len(sentence)
    while sentenceLen > 0:
        word = ''
        for i in range(maxDictA, 0, -1):
            piece = sentence[sentenceLen-i:sentenceLen]
            if piece in dictA:
                word = piece
                result.append(word)
                sentenceLen -= i
                break
        if word is '':
            sentenceLen -= 1
            result.append(sentence[sentenceLen])
    return result[::-1]
print(cutB("南京市长江大桥"))
['南京市', '长江大桥']

三、双向最大匹配

将正向最大匹配和逆向匹配得到的分词结果进行比较,按照最大匹配原则,最终结果为切分总词数最少。

def twowaycut(sentence):
    if len(cutA(sentence)) > len(cutB(sentence)):
        result = cutB(sentence)
    elif len(cutA(sentence)) < len(cutB(sentence)):
        result = cutA(sentence)
    return result
print(twowaycut("南京市长江大桥"))
['南京市', '长江大桥']

四、基于枚举法的分词方法

以变量的方式提供了部分词语的unigram概率
1: 对于给定字符串:”我们学习人工智能,人工智能是未来“, 找出所有可能的分割方式。
2: 计算出每一个切分之后句子的概率。
3: 返回第二步中概率最大的结果。

1.获取中文词典

import xlrd
# 获取一个Book对象
workbook = xlrd.open_workbook("data/综合类中文词库.xlsx")
# 获取一个sheet对象的列表
booksheet = workbook.sheet_by_index(0)
rows = booksheet.get_rows()
voca=set([row[0].value for row in rows])#中文词典
print(voca,'\n',len(voca))

2.将Unigram概率转换为log形式

为了问题的简化,只列出了一小部分单词的概率。 在这里没有出现的的单词但是出现在词典里的,统一把概率设置成为0.00001。

import numpy as np
word_prob = {"北京":0.03,"的":0.08,"天":0.005,"气":0.005,"天气":0.06,"真":0.04,"好":0.05,"真好":0.04,"啊":0.01,"真好啊":0.02,
             "今":0.01,"今天":0.07,"课程":0.06,"内容":0.06,"有":0.05,"很":0.03,"很有":0.04,"意思":0.06,"有意思":0.005,"课":0.01,
             "程":0.005,"经常":0.08,"意见":0.08,"意":0.01,"见":0.005,"有意见":0.02,"分歧":0.04,"分":0.02, "歧":0.005}
             
# 计算-log(x)
for word in word_prob.keys():
    word_prob[word]= round(-np.log(word_prob[word]),1)
print(word_prob)

3.递归计算所有可行的分词

# TODO:利用递归计算所有可行的分词之后的结果
def word_break(s, dic):
    def sentences(cur):
        result=[]
        if cur <len(s):
            for next in range(cur+1, len(s)+1):
                if s[cur:next] in dic:
                    result = result+[s[cur:next]+(tail and ','+tail) for tail in sentences(next)                    
        else:
            return ['']
        return result
        
    list_new = []
    for line in sentences(0):
        line = line.split(",")
        list_new.append(line)
    return list_new

4.返回最大概率的分词结果

def word_segment_naive(input_str):
    """
    1. 对于输入字符串做分词,并返回所有可行的分词之后的结果。
    2. 针对于每一个返回结果,计算句子的概率
    3. 返回概率最高的作为最后结果

    input_str: 输入字符串   输入格式:“今天天气好”
    best_segment: 最好的分词结果  输出格式:["今天","天气","好"]
    """

    # TODO: 第一步: 计算所有可能的分词结果,要保证每个分完的词存在于词典里,这个结果有可能会非常多。
    segments = word_break(input_str,  voca)  # 存储所有分词的结果。如果次字符串不可能被完全切分,则返回空列表(list)
    # 格式为:segments = [["今天",“天气”,“好”],["今天",“天“,”气”,“好”],["今“,”天",“天气”,“好”],...]

    # TODO: 第二步:循环所有的分词结果,并计算出概率最高的分词结果,并返回
    best_segment =[]
    best_score = np.inf
    for seg in segments:
        score=0
        for word in seg:
            if word in word_prob.keys():
                 score += word_prob[word]
            else:
                score +=  round(-np.log(0.00001),1)
        if score < best_score:
            best_score=score
            best_segment = seg

    return best_segment

5.测试

print (word_segment_naive("北京的天气真好啊"))
print (word_segment_naive("今天的课程内容很有意思"))
print (word_segment_naive("经常有意见分歧"))
['北京', '的', '天气', '真好', '啊']
['今天', '的', '课程', '内容', '很', '有意思']
['经常', '有', '意见', '分歧']

维特比(Viterbi)算法优化

1. 基于输入字符串,词典,以及给定的unigram概率来创建DAG(有向图)。
2. 编写维特比算法来寻找最优的PATH
3. 返回分词结果
def word_segment_viterbi(input_str):
    # TODO: 第一步:根据词典,输入的句子,以及给定的unigram概率来创建带权重的有向图(Directed Graph),使用dp来求解最短路径。
    graph ={}
    N = len(input_str)
    for i in range(N,0,-1):
        k=i-1
        in_list=[]
        flag=input_str[k:i]
        while k>=0:
            if flag in voca:
                in_list.append(k)
            k-=1
            flag = input_str[k:i]
        graph[i]=in_list
        
    # TODO: 第二步: 利用维特比算法来找出最好的PATH, 这个PATH是P(sentence)最大或者 -log P(sentence)最小的PATH。
    mem=[0]* (N+1)
    last_index=[0]*(N+1)
    for i in range(1,N+1):
        min_dis=np.inf
        for j in graph[i]:
            if input_str[j:i] in word_prob.keys():
                #有向图的每一条边是一个单词的概率(只要存在于词典里的都可以作为一个合法的单词),这些概率在 word_prob,如果不在word_prob里的单词但在词典里,统一用概率值0.00001。
                if min_dis > mem[j]+round(-np.log(word_prob[input_str[j:i]]),1):
                    min_dis=mem[j]+round(-np.log(word_prob[input_str[j:i]]),1)
                    last_index[i]=j
            else:
                if min_dis > mem[j]+round(-np.log(0.00001),1):
                    min_dis=mem[j]+round(-np.log(0.00001),1)
                    last_index[i]=j
        mem[i]=min_dis

    # TODO: 第三步: 根据最好的PATH, 返回最好的切分
    best_segment=[]
    j=N
    while True:
        best_segment.append(input_str[last_index[j]:j])
        j=last_index[j]
        if j==0 and last_index[j]==0:
            break
    best_segment.reverse()
    return best_segment
['北京', '的', '天气', '真好', '啊']
['今天', '的', '课程', '内容', '很', '有意思']
['经常', '有', '意见', '分歧']
  • 2
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值