隱馬爾可夫分詞
雖然目前 nlp 很多工已經發展到了使用深度學習的迴圈神經網路模型和注意力模型,但傳統的模型咱們也一樣要了解。這裡看下如何使用隱馬爾科夫模型(HMM)進行分詞。
隱馬爾科夫模型
隱馬爾科夫模型是一種有向圖模型,圖模型能清晰表達變數相關關係的概率,常見的圖模型還有條件隨機場,節點表示變數,節點之間的連線表示兩者相關概率。用以下定義來描述HMM模型,
設系統所有可能的狀態集合為 ,所有能觀察的物件的集合 ,那麼就一共有n種隱狀態和m種顯狀態。
再設總共T個時刻的狀態序列 ,對應有T個時刻的觀察序列 ,這兩個很容易理解,對應到nlp的詞性標註中就是一句話和這句話的詞性標註,比如“我/是/中國/人”和“代詞/動詞/名詞/名詞”。
隱馬爾科夫模型主要有三組概率:轉移概率、觀測概率和初始狀態概率。
系統狀態之間存在著轉移概率,設一個轉移矩陣 ,因為有n中隱狀態,所以是n行n列。概率的計算公式為,
此表示任意時刻t的狀態若為 ,則下一時刻狀態為 的概率,即任意時刻兩種狀態的轉移概率了。
觀測概率矩陣為 ,其中 ,它用於表示在任意時刻t,若狀態為 ,則生成觀察狀態 的概率。
除此之外還有一個初始狀態概率為 ,用於表示初始時刻各狀態出現的概率,其中 ,即t=1時刻狀態為 的概率。
綜上所述,一個隱馬爾科夫模型可以用 描述。

序列標籤
給訓練資料打上標籤,可以指定四類標籤:BMES。其中 B 代表詞的開始,M 代表詞的中間,E 代表詞的結尾,S代表單字詞。比如下面
農B業E生B產E再B次E獲B得E好S的S收B成E 複製程式碼
完成標籤後即得到了觀察序列和狀態序列,隱馬爾科夫模型的五要素得到了兩個,通過這兩個要素可以將轉移概率矩陣、觀察概率矩陣和初始狀態概率計算出來。
實現程式碼
先定義訓練檔案讀取函式,這裡字與標籤之間以 \t
分割,讀取過程順便統計觀察物件集合和狀態集合。
def read_data(filename): sentences = [] sentence = [] with open(filename, 'r', encoding='utf-8') as f: for line in f.readlines(): word_label = line.strip().split('\t') if len(word_label) == 2: observation_set.add(word_label[0]) state_set.add(word_label[1]) sentence.append(word_label) else: sentences.append(sentence) sentence = [] return sentences 複製程式碼
定義訓練函式,讀取訓練語料後遍歷所有句子,統計觀察概率矩陣 observation_matrix
,統計初始狀態概率 pi_state
,統計轉移概率矩陣 transition_matrix
,這裡我們先統計個數,後面對其進行歸一化後得到真正的概率。所以可以看到後面分別對轉移概率矩陣、觀察概率矩陣和初始狀態做歸一化處理。
def train(): print('begin training......') sentences = read_data(data_path) for sentence in sentences: pre_label = -1 for word, label in sentence: observation_matrix[label][word] = observation_matrix.setdefault(label, {}).setdefault(word, 0) + 1 if pre_label == -1: pi_state[label] = pi_state.setdefault(label, 0) + 1 else: transition_matrix[pre_label][label] = transition_matrix.setdefault(pre_label, {}).setdefault(label, 0) + 1 pre_label = label for key, value in transition_matrix.items(): number_total = 0 for k, v in value.items(): number_total += v for k, v in value.items(): transition_matrix[key][k] = 1.0 * v / number_total for key, value in observation_matrix.items(): number_total = 0 for k, v in value.items(): number_total += v for k, v in value.items(): observation_matrix[key][k] = 1.0 * v / number_total number_total = sum(pi_state.values()) for k, v in pi_state.items(): pi_state[k] = 1.0 * v / number_total print('finish training.....') save_model() 複製程式碼
訓練後將模型引數儲存到檔案中,預測時載入指定檔案的模型。
def load_model(): print('loading model...') with open(model_path, 'rb') as f: model = pickle.load(f) return model def save_model(): print('saving model...') model = [transition_matrix, observation_matrix, pi_state, state_set, observation_set] with open(model_path, 'wb') as f: pickle.dump(model, f) 複製程式碼
訓練完成後得到模型引數,接著定義預測函式,我們預測其實就是 HMM 的解碼問題,一般可以使用 Viterbi 演算法,詳細可以參考前面寫的文章《 ofollow,noindex">隱馬爾可夫模型的Viterbi解碼演算法 》。
隨著時刻推進,每個節點都儲存了前一時刻所有節點到該節點的最優值的子路徑,最優值通過上一時刻節點上的累乘概率乘以當前時刻概率來判斷的。當前時刻的某一節點可能的路徑為上一時刻所有節點到該節點的路徑,但我們只保留其中一條最優路徑,。依次計算完所有步後,最後通過回溯的方法得到整個過程的最優路徑。
def predict(): text = '我在圖書館看書' min_probability = -1 * float('inf') words = [{} for _ in text] path = {} for state in state_set: words[0][state] = 1.0 * pi_state.get(state, default_probability) * observation_matrix.get(state, {}).get( text[0], default_probability) path[state] = [state] for t in range(1, len(text)): new_path = {} for state in state_set: max_probability = min_probability max_state = '' for pre_state in state_set: probability = words[t - 1][pre_state] * transition_matrix.get(pre_state, {}).get(state, default_probability) \ * observation_matrix.get(state, {}).get(text[t], default_probability) max_probability, max_state = max((max_probability, max_state), (probability, pre_state)) words[t][state] = max_probability tmp = copy.deepcopy(path[max_state]) tmp.append(state) new_path[state] = tmp path = new_path max_probability, max_state = max((words[len(text) - 1][s], s) for s in state_set) result = [] p = re.compile('BM*E|S') for i in p.finditer(''.join(path[max_state])): start, end = i.span() word = text[start:end] result.append(word) print(result) 複製程式碼
大致描述下解碼過程,初始狀態時,對於四種狀態,“我”最大概率都是 S;接著分別計算“我”的四種狀態到“在”的四種狀態的概率,此時最大的也都為 S;繼續分別計算“在”的四種狀態到“圖”的四種狀態的概率,最大的都為 B;直到分別計算“館”的四種狀態到“看”的四種狀態的概率時,最大概率不同了;
'M': ['S'] 'B': ['S'] 'E': ['S'] 'S': ['S'] 複製程式碼
...... 複製程式碼
'M': ['S', 'S', 'B', 'M', 'E', 'B', 'M'] 'B': ['S', 'S', 'B', 'M', 'E', 'S', 'B'] 'E': ['S', 'S', 'B', 'M', 'E', 'B', 'E'] 'S': ['S', 'S', 'B', 'M', 'E', 'S', 'S'] 複製程式碼