自然語言學習15-基於 CRF 的中文句法依存分析模型

句法分析是自然語言處理中的關鍵技術之一,其基本任務是確定句子的句法結構或者句子中詞彙之間的依存關係。主要包括兩方面的內容,一是確定語言的語法體系,即對語言中合法句子的語法結構給予形式化的定義;另一方面是句法分析技術,即根據給定的語法體系,自動推導出句子的句法結構,分析句子所包含的句法單位和這些句法單位之間的關係。

依存關係本身是一個樹結構,每一個詞看成一個節點,依存關係就是一條有向邊。本文主要通過清華大學的句法標註語料庫,來實現基於 CRF 的中文句法依存分析模型。

 

語料特徵生成

語料特徵提取,主要採用 N-gram 模型來完成。使用 3-gram 完成提取,將詞性與詞語兩兩進行匹配,分別返回特徵集合和標籤集合,需要注意整個語料採用的是 UTF-8 編碼格式。

引入需要的庫,然後對語料進行讀文件操作。語料採用 UTF-8 編碼格式,以句子爲單位,按 Tab 鍵作分割處理,從而實現句子 3-gram 模型的特徵提取。具體實現如下。

    import sklearn_crfsuite
    from sklearn_crfsuite import metrics
    from sklearn.externals import joblib

 

 

其目的是使用模型 sklearn_crfsuite .CRF,metrics 用來進行模型性能測試,joblib 用來保存和加載訓練好的模型。

接着,定義包含特徵處理方法的類,命名爲 CorpusProcess,類結構定義如下:

class CorpusProcess(object):

    def __init__(self):
        """初始化"""
        pass

    def read_corpus_from_file(self, file_path):
        """讀取語料"""
        pass

    def write_corpus_to_file(self, data, file_path):
        """寫語料"""
        pass

    def process_sentence(self,lines):
        """處理句子"""
        pass   

    def initialize(self):
        """語料初始化"""
        pass   

    def generator(self, train=True):
        """特徵生成器"""
        pass  

    def extract_feature(self, sentences):
        """提取特徵"""
        pass

 CorpusProcess 類中各個方法的具體實現。

第1步, 實現 init 構造函數,目的初始化預處理好的語料的路徑:

    def __init__(self):
            """初始化"""
            self.train_process_path =  dir +  "data//train.data"   #預處理之後的訓練集
            self.test_process_path =  dir +  "data//dev.data"  #預處理之後的測試集

這裏的路徑可以自定義,這裏的語料之前已經完成了預處理過程。

第2-3步,read_corpus_from_file 方法和 write_corpus_to_file 方法,分別定義了語料文件的讀和寫操作:

    def read_corpus_from_file(self, file_path):
        """讀取語料"""
        f = open(file_path, 'r',encoding='utf-8')
        lines = f.readlines()
        f.close()
        return lines

    def write_corpus_to_file(self, data, file_path):
        """寫語料"""
        f = open(file_path, 'w')
        f.write(str(data))
        f.close()

這一步,主要用 open 函數來實現語料文件的讀和寫。

第4-5步,process_sentence 方法和 initialize 方法,用來處理句子和初始化語料,把語料按句子結構用 list 存儲起來,存儲到內存中:

    def process_sentence(self,lines):
            """處理句子"""
            sentence = []
            for line in lines:
                if not line.strip():
                    yield sentence
                    sentence = []
                else:
                    lines = line.strip().split(u'\t')
                    result = [line for line in lines]
                    sentence.append(result)   

        def initialize(self):
            """語料初始化"""
            train_lines = self.read_corpus_from_file(self.train_process_path)
            test_lines = self.read_corpus_from_file(self.test_process_path)
            self.train_sentences = [sentence for sentence in self.process_sentence(train_lines)]
            self.test_sentences = [sentence for sentence in self.process_sentence(test_lines)] 

這一步,通過 process_sentence 把句子收尾的空格去掉,然後通過 initialize 函數調用上面 read_corpus_from_file 方法讀取語料,分別加載訓練集和測試集。

第6步,特徵生成器,分別用來指定生成訓練集或者測試集的特徵集:

    def generator(self, train=True):
        """特徵生成器"""
        if train: 
            sentences = self.train_sentences
        else: 
            sentences = self.test_sentences
        return self.extract_feature(sentences)    

這一步,對訓練集和測試集分別處理,如果參數 train 爲 True,則表示處理訓練集,如果是 False,則表示處理測試集。

第7步,特徵提取,簡單的進行 3-gram 的抽取,將詞性與詞語兩兩進行匹配,分別返回特徵集合和標籤集合:

    def extract_feature(self, sentences):
            """提取特徵"""
            features, tags = [], []
            for index in range(len(sentences)):
                feature_list, tag_list = [], []
                for i in range(len(sentences[index])):
                    feature = {"w0": sentences[index][i][0],
                               "p0": sentences[index][i][1],
                               "w-1": sentences[index][i-1][0] if i != 0 else "BOS",
                               "w+1": sentences[index][i+1][0] if i != len(sentences[index])-1 else "EOS",
                               "p-1": sentences[index][i-1][1] if i != 0 else "un",
                               "p+1": sentences[index][i+1][1] if i != len(sentences[index])-1 else "un"}
                    feature["w-1:w0"] = feature["w-1"]+feature["w0"]
                    feature["w0:w+1"] = feature["w0"]+feature["w+1"]
                    feature["p-1:p0"] = feature["p-1"]+feature["p0"]
                    feature["p0:p+1"] = feature["p0"]+feature["p+1"]
                    feature["p-1:w0"] = feature["p-1"]+feature["w0"]
                    feature["w0:p+1"] = feature["w0"]+feature["p+1"]
                    feature_list.append(feature)
                    tag_list.append(sentences[index][i][-1])
                features.append(feature_list)
                tags.append(tag_list)
            return features, tags    

經過第6步,確定處理的是訓練集還是測試集之後,通過 extract_feature 對句子進行特徵抽取,使用 3-gram 模型,得到特徵集合和標籤集合的對應關係。

模型訓練及預測

預定義模型需要的一些參數,並初始化模型對象,進而完成模型訓練和預測,以及模型的保存與加載。

定義模型 ModelParser 類,進行初始化參數、模型初始化,以及模型訓練、預測、保存和加載,類的結構定義如下:

    class ModelParser(object):

        def __init__(self):
            """初始化參數"""
            pass

        def initialize_model(self):
            """模型初始化"""
            pass

        def train(self):
            """訓練"""
            pass

        def predict(self, sentences):
            """模型預測"""
            pass

        def load_model(self, name='model'):
            """加載模型 """
            pass

        def save_model(self, name='model'):
            """保存模型"""
            pass

分析 ModelParser 類中方法的具體實現。

第1步,init 方法實現算法模型參數和語料預處理 CorpusProcess 類的實例化和初始化:

    def __init__(self):
            """初始化參數"""
            self.algorithm = "lbfgs"
            self.c1 = 0.1
            self.c2 = 0.1
            self.max_iterations = 100
            self.model_path = "model.pkl"
            self.corpus = CorpusProcess()  #初始化CorpusProcess類
            self.corpus.initialize()  #語料預處理
            self.model = None

這一步,init 方法初始化參數以及 CRF 模型的參數,算法選用 LBFGS,c1 和 c2 分別爲0.1,最大迭代次數100次。然後定義模型保存的文件名稱,以及完成對 CorpusProcess 類 的初始化。

第2-3步,initialize_model 方法和 train 實現模型定義和訓練:

     def initialize_model(self):
            """模型初始化"""
            algorithm = self.algorithm
            c1 = float(self.c1)
            c2 = float(self.c2)
            max_iterations = int(self.max_iterations)
            self.model = sklearn_crfsuite.CRF(algorithm=algorithm, c1=c1, c2=c2,
                                              max_iterations=max_iterations, all_possible_transitions=True)

        def train(self):
            """訓練"""
            self.initialize_model()
            x_train, y_train = self.corpus.generator()
            self.model.fit(x_train, y_train)
            labels = list(self.model.classes_)
            x_test, y_test = self.corpus.generator(train=False)
            y_predict = self.model.predict(x_test)
            metrics.flat_f1_score(y_test, y_predict, average='weighted', labels=labels)
            sorted_labels = sorted(labels, key=lambda name: (name[1:], name[0]))
            print(metrics.flat_classification_report(y_test, y_predict, labels=sorted_labels, digits=3))
            self.save_model()

這一步,initialize_model 方法實現 了 sklearn_crfsuite.CRF 模型的初始化。然後在 train 方法中,先通過 fit 方法訓練模型,再通過 metrics.flat_f1_score 對測試集進行 F1 性能測試,最後將模型保存。

第4-6步,分別實現模型預測、保存和加載方法。

    def predict(self, sentences):
        """模型預測"""
        self.load_model()
        features, _ = self.corpus.extract_feature(sentences)
        return self.model.predict(features)

    def load_model(self, name='model'):
        """加載模型 """
        self.model = joblib.load(self.model_path)

    def save_model(self, name='model'):
        """保存模型"""
        joblib.dump(self.model, self.model_path)

最後,實例化類,並進行模型訓練:

    model = ModelParser()
    model.train()

對模型進行預測,預測數據輸入格式爲三維,表示完整的一句話:

[[['堅決', 'a', 'ad', '1_v'],

 ['懲治', 'v', 'v', '0_Root'],

 ['貪污', 'v', 'v', '1_v'],

 ['賄賂', 'n', 'n', '-1_v'],

 ['等', 'u', 'udeng', '-1_v'],

 ['經濟', 'n', 'n', '1_v'],

 ['犯罪', 'v', 'vn', '-2_v']]]

模型預測的結果如下圖所示: 

         precision    recall  f1-score   support

        -1_a      0.811     0.796     0.804       221
        -1_b      0.783     0.770     0.777        61
        -1_c      0.000     0.000     0.000         5
        -1_d      0.711     0.409     0.519        66
        -1_f      0.867     0.565     0.684        23
        -1_h      0.000     0.000     0.000         0
        -1_k      0.667     1.000     0.800         2
        -1_m      0.905     0.895     0.900       256
        -1_n      0.720     0.754     0.737       967
        11_n      0.000     0.000     0.000         0
       -1_ng      0.000     0.000     0.000        23
       -1_nl      0.750     0.143     0.240        21
       -1_nr      1.000     0.059     0.111        17
      -1_nr1      0.000     0.000     0.000         0
      -1_nr2      0.000     0.000     0.000         0
      -1_nrf      0.000     0.000     0.000        25
      -1_nrj      0.000     0.000     0.000         2
       -1_ns      0.870     0.400     0.548        50
      -1_nsf      0.822     0.402     0.540        92
       -1_nt      0.667     0.286     0.400        14
       -1_nz      0.000     0.000     0.000         7
        -1_o      0.000     0.000     0.000         1
        -1_p      0.524     0.214     0.303       103
        -1_q      0.706     0.649     0.676        37
        -1_r      0.946     0.841     0.891        63
        -1_s      0.737     0.933     0.824        15
        -1_t      0.952     0.894     0.922        66
        -1_u      0.000     0.000     0.000         3
        -1_v      0.628     0.669     0.648      2396
        -1_x      0.000     0.000     0.000         0
        -1_z      0.875     0.636     0.737        11
        -2_a      0.800     0.364     0.500        11
        -2_b      0.000     0.000     0.000         1
        -2_c      0.000     0.000     0.000         0
        -2_d      0.000     0.000     0.000         2
        -2_f      0.000     0.000     0.000         1
        -2_m      0.897     0.876     0.886        89
        -2_n      0.095     0.021     0.034        95
       -2_ng      0.000     0.000     0.000         0
       -2_nl      0.000     0.000     0.000         1
       -2_nr      0.000     0.000     0.000         1
      -2_nr2      0.000     0.000     0.000         0
      -2_nrf      0.000     0.000     0.000         3
       -2_ns      0.000     0.000     0.000         3
      -2_nsf      0.000     0.000     0.000         3
       -2_nz      0.000     0.000     0.000         1
        -2_p      0.000     0.000     0.000         9
        -2_q      0.000     0.000     0.000         2
        -2_r      0.000     0.000     0.000         3
        -2_s      0.000     0.000     0.000         0
        -2_t      1.000     0.920     0.958        25
        -2_u      0.000     0.000     0.000         0
        -2_v      0.326     0.211     0.256       445
        -2_z      0.000     0.000     0.000         0
        -3_a      1.000     0.500     0.667         2
        -3_b      0.000     0.000     0.000         0
        -3_d      0.000     0.000     0.000         0
        -3_m      0.625     0.750     0.682        20
        -3_n      0.000     0.000     0.000        20
       -3_nl      0.000     0.000     0.000         0
      -3_nsf      0.000     0.000     0.000         0
        -3_p      0.000     0.000     0.000         1
        -3_q      0.000     0.000     0.000         0
        -3_t      1.000     0.571     0.727        14
        -3_v      0.125     0.045     0.066       112
        -4_b      0.000     0.000     0.000         0
        -4_d      0.000     0.000     0.000         0
        -4_m      0.500     0.200     0.286         5
        -4_n      0.000     0.000     0.000         8
      -4_nsf      0.000     0.000     0.000         0
        -4_p      0.000     0.000     0.000         1
        -4_t      0.000     0.000     0.000         0
        -4_v      0.000     0.000     0.000        33
        -5_d      0.000     0.000     0.000         0
        -5_m      0.000     0.000     0.000         2
        -5_n      0.000     0.000     0.000         3
        -5_v      0.000     0.000     0.000         4
        -6_m      0.000     0.000     0.000         0
        -6_n      0.000     0.000     0.000         1
        -6_v      0.000     0.000     0.000         2
        -7_m      0.000     0.000     0.000         0
        -7_n      0.000     0.000     0.000         0
        -7_v      0.000     0.000     0.000         0
        -8_n      0.000     0.000     0.000         0
        -8_v      0.000     0.000     0.000         0
         1_R      0.000     0.000     0.000         0
      0_Root      0.640     0.781     0.704      1997
         0_a      0.000     0.000     0.000         0
         1_a      0.655     0.599     0.626       282
         2_a      0.000     0.000     0.000         6
         3_a      0.000     0.000     0.000         0
         1_b      0.706     0.436     0.539        55
         2_b      0.000     0.000     0.000         2
         3_b      0.000     0.000     0.000         0
         1_c      0.500     0.400     0.444         5
         2_c      0.000     0.000     0.000         1
         3_c      0.000     0.000     0.000         0
         1_d      0.439     0.305     0.360        59
         2_d      0.000     0.000     0.000         8
         3_d      0.000     0.000     0.000         2
         1_f      0.738     0.649     0.691        74
         2_f      0.000     0.000     0.000         3
         3_f      0.000     0.000     0.000         0
         1_h      0.000     0.000     0.000         0
         1_k      0.818     0.750     0.783        12
         0_m      0.750     0.375     0.500         8
         1_m      0.715     0.628     0.669       148
         2_m      0.541     0.541     0.541        37
         3_m      0.429     0.167     0.240        18
         4_m      1.000     0.600     0.750         5
         5_m      0.333     0.250     0.286         4
         6_m      0.000     0.000     0.000         1
         1_n      0.673     0.804     0.732      3348
         2_n      0.224     0.090     0.129       587
         3_n      0.308     0.053     0.090       152
         4_n      0.000     0.000     0.000        40
         5_n      0.000     0.000     0.000        15
         6_n      0.000     0.000     0.000         4
         7_n      0.000     0.000     0.000         3
         8_n      0.000     0.000     0.000         0
         9_n      0.000     0.000     0.000         0
        1_ng      0.333     0.036     0.065        83
        2_ng      0.000     0.000     0.000         3
        3_ng      0.000     0.000     0.000         0
        1_nl      0.600     0.079     0.140        38
        2_nl      0.000     0.000     0.000         4
        3_nl      0.000     0.000     0.000         0
        1_nr      0.000     0.000     0.000         9
        2_nr      0.000     0.000     0.000         3
        3_nr      0.000     0.000     0.000         0
       1_nr1      0.000     0.000     0.000         0
       1_nr2      0.000     0.000     0.000         0
       2_nr2      0.000     0.000     0.000         0
       1_nrf      0.000     0.000     0.000         9
       2_nrf      0.000     0.000     0.000         1
       3_nrf      0.000     0.000     0.000         0
       1_nrj      0.000     0.000     0.000         1
       2_nrj      0.000     0.000     0.000         0
        1_ns      0.536     0.294     0.380        51
        2_ns      0.000     0.000     0.000         2
        3_ns      0.000     0.000     0.000         0
        4_ns      0.000     0.000     0.000         0
        5_ns      0.000     0.000     0.000         0
       1_nsf      0.649     0.333     0.440        72
       2_nsf      0.000     0.000     0.000         4
       3_nsf      0.000     0.000     0.000         0
        1_nt      0.889     0.421     0.571        19
        2_nt      0.000     0.000     0.000         0
        3_nt      0.000     0.000     0.000         0
        1_nz      1.000     0.200     0.333        10
        2_nz      1.000     0.333     0.500         3
        3_nz      0.000     0.000     0.000         0
         1_o      0.000     0.000     0.000         0
         1_p      0.705     0.518     0.597        83
         2_p      0.000     0.000     0.000         0
         3_p      0.000     0.000     0.000         0
         1_q      0.819     0.831     0.825       349
         2_q      0.000     0.000     0.000         1
         1_r      0.750     0.774     0.762        62
         2_r      0.000     0.000     0.000         1
         1_s      0.711     0.818     0.761        33
         2_s      0.000     0.000     0.000         0
         3_s      0.000     0.000     0.000         0
         1_t      0.907     0.801     0.851       146
         2_t      0.667     0.606     0.635        33
         3_t      0.375     0.300     0.333        10
         4_t      0.000     0.000     0.000         2
         5_t      0.000     0.000     0.000         0
         1_u      1.000     0.200     0.333        20
         2_u      0.000     0.000     0.000         1
         3_u      0.000     0.000     0.000         0
         1_v      0.670     0.814     0.735      3331
         2_v      0.456     0.187     0.265       445
         3_v      0.091     0.011     0.020        87
         4_v      0.000     0.000     0.000        20
         5_v      0.000     0.000     0.000         8
         6_v      0.000     0.000     0.000         2
         7_v      0.000     0.000     0.000         0
         1_x      1.000     0.333     0.500         3
         1_y      0.000     0.000     0.000         0
         1_z      0.333     0.500     0.400         2

   micro avg      0.660     0.660     0.660     17297
   macro avg      0.245     0.175     0.191     17297
weighted avg      0.624     0.660     0.630     17297

  

 

 

 

 

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章