1. 程式人生 > >詞法分析-中文分詞技術-正向最大匹配法與逆向最大匹配法

詞法分析-中文分詞技術-正向最大匹配法與逆向最大匹配法

Long Time No See...

最近深受痛苦的折磨,這一年來所有的事跌宕起伏,如同一瞬,一個個打擊接踵而至,從年初的各種擦邊掛,到各種失敗,各種放棄,似乎沒有發生一個順心的事,不知道從什麼時候起戾氣變得越來越重,更無與人說。不管如何,“盡吾志也而不能至者,可以無悔矣,其孰能譏之乎?”……

我決定重拾包袱,從最初開始,從現在開始……

 

 

因為考研耽誤了好多事,包括友誼,包括學習,在這就邊做實驗邊複習點這學期的知識。

 

詞法分析:把字串序列轉化為單詞序列的過程,進行詞法分析的函式或程式叫作詞法分析器或掃描器。

例如:“XXXXXXXXXXX”->"X/XX/X/XX/X/X/X/X/X"

中文分詞技術:把一句話根據詞語字典切分為若干個詞語。方法大體分為三類:字元匹配法、理解法、統計法,字元匹配法包括正向最大匹配、逆向最大匹配、最少切分、雙向最大匹配等。

本次任務:給定字典,[‘研究’,’研究生’, ’生命’,’命’,’的’,‘起源’],使用正向最大匹配(Maximum Match Method,MM)和逆向最大匹配(Reverse Maximum Match Method,RMM)對“研究生命的起源”進行切分。

MM:

Step 1 假定分詞詞典中的最長詞有i個漢字字元,則用被處理的當前字串中的前i個字作為匹配欄位,查詢字典。

Step 2 若字典中存在這樣一個i字詞,則匹配成功;否則,失敗,將匹配欄位中的最後一個字元去掉, 對剩下字串進行匹配。 Step 3 如此進行下去,直到匹配成功,即切分出一個詞或剩餘字串長度為0。 不停的匹配,直到文件被掃描完為止。

 

程式設計思路:第一步求詞典中最長分詞的長度,設為maxlen。設cobegin為源字串時匹配的開始字元下標。cobegin初始為0,設coend為字串的末尾下標的下一個,即coend=len(s)

第二步,設切分的大小為size,初始化為maxlen,如果切下的s[cobegin:cobegin+size]在字典中,則把切下的放在一個列表中,並cogebin=cobegin+size 且size重置為maxlen,否則size減1,重新匹配,當size為0且cobegin<coend 時,則表明無法將切分完畢;當cobegin>=coend時,退出迴圈。

 

程式碼實現 MaxMatch.py

dic=['研究','研究生','生命','命','的','起源']
out=[]
s='研究生命的起源'
maxlen=0 #字典中最大分詞長度
for i in dic:
    if maxlen<len(i):
        maxlen=len(i)
cobegin=0 #剩餘字元開始
coend=len(s) #剩餘字元結束
size=maxlen #開始匹配長度
while cobegin<coend:
    ch=s[cobegin:cobegin+size] 
    if ch in dic:#匹配成功
        cobegin+=size
        out.append(ch)
        size=maxlen
    else: #匹配失敗
        size-=1
    if size==0:
        print("error")
        break

最後結果:

好像不太對的樣子,我意為自己寫錯了從網上覆制了一個c++的程式碼(忘了在哪個部落格找到的),執行,還是這個結果:

MM.cpp:

#include<iostream>
#include<string>
using namespace std;

// 巨集,計算陣列個數
#define GET_ARRAY_LEN(array,len){len=(sizeof(array)/sizeof(array[0]));}

string dict[] = {"研究", "研究生", "生命", "命", "的","起源"};

// 是否為詞表中的詞或者是詞表中詞的字首
bool inDict(string str)
{
    bool res = false;
    int i;
    int len = 0;
    
    GET_ARRAY_LEN(dict, len);

    for (i = 0; i<len; i++)
    {
        // 是否和詞表詞相等或者是詞表詞字首
        if( str == dict[i].substr(0, str.length()))
        {
            res = true;
        }
    }
    return res;
}


int main()
{
    string sentence = "研究生命的起源";
    string word = "一";
    int wordlen = word.length();
    
    int i;
    string s1 = "";
    
    for (i = 0; i<sentence.length(); i = i+wordlen)
    {
        string tmp = s1 + sentence.substr(i, wordlen);

        if(inDict(tmp))
        {
            s1 = s1 + sentence.substr(i, wordlen);
        }
        else
        {
            cout<<"分詞結果:"<<s1<<endl;
            s1 = sentence.substr(i, wordlen);
        }
    }
    cout<<"分詞結果:"<<s1<<endl;
}

結果還是一樣:

 

 

RMM:原理和MM類似,唯一不同的時候匹配是倒著匹配(從字串的最後開始),然後對最後切分的欄位進行逆置即可。

ReMaxMatch.py:

import math
dic=['研究','研究生','生命','命','的','起源']
out=[]
s='研究生命的起源'
maxlen=0 #字典中最大分詞長度
for i in dic:
    if maxlen<len(i):
        maxlen=len(i)
cobegin=0 #剩餘字元開始
coend=len(s) #剩餘字元結束
size=maxlen #開始匹配長度
while cobegin<coend:
    ch=s[coend-size:coend] 
    if ch in dic:#匹配成功
        coend-=size
        out.append(ch)
        size=maxlen
    else: #匹配失敗
        size-=1
    if size==0:
        print("error")
        break
i=0
while i<math.floor(len(out)/2): #逆置
    str1=out[i]
    out[i]=out[len(out)-i-1]
    out[len(out)-i-1]=str1
    i+=1

結果:

 

兩者的差異:

正向匹配時會優先選擇匹配長度更長的單詞,但同一字首的單詞並不是越長就越正確,逆向同理會匹配同一字尾匹配長度更長的單詞,兩者都有其侷限性,需要做出一定的優化,比如使用最小切分法和雙向切分等。