1. 程式人生 > 實用技巧 >維特比演算法之中文分詞

維特比演算法之中文分詞

維特比原理


尋找上圖最短路徑

中文分詞

此專案需要的資料:

綜合類中文詞庫.xlsx: 包含了中文詞,當做詞典來用
以變數的方式提供了部分unigram概率word_prob
舉個例子: 給定詞典=[我們 學習 人工 智慧 人工智慧 未來 是], 另外我們給定unigram概率:p(我們)=0.25, p(學習)=0.15, p(人工)=0.05, p(智慧)=0.1, p(人工智慧)=0.2, p(未來)=0.1, p(是)=0

  1. 獲取中文字典
    在變數word_prob沒有出現的的單詞但是出現在詞典裡的,統一把概率設定成為0.00001
import xlrd
file_path = './data/綜合類中文詞庫.xlsx'
workbook = xlrd.open_workbook(file_path)
booksheet = workbook.sheet_by_index(0)
col_values = booksheet.col_values(0)
dic_words = {}
max_len_word = 0
for word in col_values:
    dic_words[word] = 0.00001
    len_word = len(word)
    if len_word > max_len_word:
        max_len_word = len_word
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}

for key, value in word_prob.items():
    dic_words[key] = value
  1. 根據詞典,輸入的句子和 word_prob來建立帶權重的有向圖(Directed Graph)
#從頭開始遍歷,找到字典中存在的所有候選詞
def create_graph(input_str):
    N = len(input_str)
    graph = {}
    for idx_end in range(1, N + 1):
        print('idx_end',idx_end)
        temp_list = []
        max_split = min(idx_end, max_len_word) # 最大切分長度為idx_end,即這次迴圈的結果
        for idx_start in range(idx_end - max_split, idx_end): # 就是 0 : idx_end
            
            word = input_str[idx_start:idx_end] # 根據起止索引得到單詞
            print('idx_start',idx_start, word)
            if word in dic_words:
                print(idx_start,word)
                temp_list.append(idx_start)
       
        
        graph[idx_end] = temp_list
        print(graph)
        print('_______________')
    return graph
  1. 通過加權有向圖,實現維特比演算法
def word_segment_viterbi(input_str):
    graph = create_graph(input_str)
    N = len(input_str)
    m = [np.inf] * (N + 1) # 長度為 N+1 長度的陣列。初始化無窮大。
    m[0] = 0  # 路徑值,第0個節點的值為0,後面計算節點1路徑權值時候,需要加上節點0的值。
    last_index = [0] * (N + 1)# 儲存一路轉移的索引
    for idx_end in range(1, N + 1): # 兩層for迴圈 idx_end 為 incoming_links,字典的鍵
        for idx_start in graph[idx_end]: # idx_start 到 idx 組成了一個單詞。input_str[idx_start:idx_end] 在字典裡存在。
            # 從字典找到這個單詞的概率。
            # m[idx_start] 儲存了到這個單詞為止,最短路徑值。
            log_prob = round(-1 * np.log(dic_words[input_str[idx_start:idx_end]])) + m[idx_start]
            if log_prob < m[idx_end]:# 這次迴圈裡,判斷到idx_end 位置的最短路徑值。
                m[idx_end] = log_prob
                last_index[idx_end] = idx_start
    best_segment = []
    i = N
    while True:
        best_segment.insert(0, input_str[last_index[i]:i])
        i = last_index[i]
        if i == 0:
            break
    return best_segment