PS:這篇文章中的代碼,僅為一個簡單的DEMO,并未進行過代碼和算法的優化,參數也未進行過調整,僅僅是演示了一個從訓練模型到應用的完整過程
簡介
關于這種分詞方法,網上的相關文章已經是相當相當少了,最出名的就是NLPIR分詞中采用了這種方法(貌似最早也是這個分詞工具的作者提出的)
相關文章
自然語言處理中的N-Gram模型詳解
自然語言處理中N-Gram模型的Smoothing算法
HanLP作者對N-ShortPath分詞理解的一個簡單介紹
呂震宇先生對NLPIR分詞的理解(這些文章里有些地方暫時持保留意見,或者說對于這個分詞算法本身就有很多疑問)
代碼
from nltk.util import bigrams, trigrams
from nltk.text import Text
from nltk import FreqDist
from functools import reduce
from bidict import bidict
import numpy as np
corpus = [
'<begin> 小鳥 聲音 不大 , 卻 句 句 在理 , 全場 都 靜靜 恭聽。 <end>',
'<begin> 他 說 : “ 神 是否 創造 世界 ,即 神 對 世界 的 關系 如何 ,這個 問題 其實 就是 關于 精神 對 感性 一般 或 抽象 對 實在、類 對 個體 的 關系 如何 的 問題 ;這個 問題 是 屬于 人類 認識 和 哲學 上 最 重要 又 最 困難 的 問題 之一 , 整個 哲學史 其實 只在 這個 問題 周圍 繞 圈子 , 古代 哲學 中 斯多葛派 和 伊壁鳩魯派 間 、 柏拉圖派 和 亞里士多德派 間 、 懷疑派 和 獨斷派 間 的 爭論 , 中古哲學 中 唯名論者 和 實在論者 間 的 爭論 , 以及 近代 哲學 中 唯心主義者 和 實在論者 或 經驗主義者 間 的 爭論 , 歸根結底 都是 關于 這個 問題 。 <end>”',
'<begin> 討論 法 的 本位 問題 , 應該 局限 于 實在 法效 用 的 實現 借助 于 何種 規范 手段 的 范圍 內 , 它 主要 應 討論 " 法 是 什么 " 的 問題 , 而 不是 " 法 應當 是 什么 " 的 問題 。 <end>',
'<begin> 現在 , 你 已是 全班 第一名 了 , 我們 都要 向 你 學習 , 我們 還會 繼續 幫助 你 。 <end>',
'<begin> 他們 的 罪惡 行徑 也 從 反面 教育 我們 , 革命 的 政治工作 對于 我們 黨 的 各項 工作 , 對于 我們 軍隊 和 人民 來說 , 確實 是 不可以 須臾 離開 的 生命線 。 <end>',
'<begin> 從 研究系 辦 的 刊物 來看 , 確實 登載 過 大量 的 討論 社會主義 的 文章 , 似乎 亦 擁護 社會主義 , 但 實際上 這 只是 假象 。 <end>',
'<begin> 他 那些 舞臺 下 、 劇場 外 的 事 的確 是 鮮為人知 的 。 <end>',
# '<begin> 他 說 的 確實 在理 <end>'
]
# 單字切分,暫時沒用到
def atomic_split(param1, param2):
if isinstance(param1, list):
return param1 + list(param2.replace(' ', ''))
else:
return atomic_split(atomic_split([], param1), param2)
atomics = reduce(atomic_split, corpus)
#對語料的切分
def word_split(param1, param2):
if isinstance(param1, list):
return param1 + param2.split()
else:
return word_split(word_split([], param1), param2)
words = reduce(word_split, corpus)
#計算詞頻,索引
fd = FreqDist(words)
index = bidict()
pos = 0
for k, c in fd.items():
index[k] = pos
pos = pos + 1
#=====利用nltk的biggrams函數,建立gram矩陣==========================
grams = list(bigrams(words))
gc = np.zeros((fd.B(), fd.B()), dtype=np.int32)
#統計gram次數
for p1, p2 in grams:
gc[index[p1], index[p2]] += 1
#統計gram概率
gp = np.zeros((fd.B(), fd.B()))
#平滑系數
ratio = 0.9
for row in range(0, fd.B()):
for col in range(0, fd.B()):
gp[row, col] = ratio * (gc[row, col] / fd[index.inv[row]]) + (
1 - ratio) * (fd[index.inv[col]] / len(words))
#======================模型訓練完成=================================
#=============求最短路徑(非N-最短路徑,算法和原方法不同,一個是因為對原算法有疑問,另一個為了快速完成DEMO)==================
def split(s, pos=0):
if len(s) <= pos: return [{'key': '<end>'}]
result = []
for k in fd.keys():
end = pos + len(k)
if len(k) > 1 and end <= len(s) and k == s[pos:end]:
result.append({'key': k, 'childs': split(s, end)})
result.append({'key': s[pos:pos + 1], 'childs': split(s, pos + 1)})
return result
def split_to_tree(s):
return {'key': '<begin>', 'childs': split(input)}
def segment(node):
k = node['key']
childs = node.get('childs')
if not childs:
return
i1 = index.get(k)
for child in childs:
i2 = index.get(child['key'])
child['score'] = gp[i1, i2] if (i1 and
i2) else (1 - ratio) * 1 / len(words)
segment(child)
def shortest(node):
childs = node.get("childs")
score = node.get('score', 0)
key = node.get('key')
if not childs:
return score, [key]
current_score, current_seq = -1, []
for child in childs:
_score, _seq = shortest(child)
if _score > current_score:
current_score, current_seq = _score, _seq
return current_score + score, [key] + current_seq
#================分詞函數完成=================
input = '他說的確實在理'
# 將輸入轉化成tree
root = split_to_tree(input)
# 根據上面的gram概率模型,計算得分
segment(root)
#求最大得分
score, seq = shortest(root)
print(seq, score)