天天看點

BERT介紹及中文文本相似度任務實踐BERT簡介BERT源碼分析

本文内容清單

  • BERT簡介
  • BERT源碼分析
    • 1、從git上克隆代碼
    • 2、下載下傳預訓練模型
    • 3、代碼結構
    • 4、 run_classifier.py檔案(中文文本相似度任務fine-tuning)
      • 1. 自定義資料類
      • 2. 增加自定義類
      • 3. 函數調用參數
      • 4. 訓練模型
      • 5. 總結
    • 附代碼

BERT簡介

BERT全稱 Bidirectional Encoder Representations from Transformers,是一種新的語言模型。自Google在2018年10月底公布BERT在11項NLP任務中的卓越表現後,BERT引起了社群極大的關注與讨論,被認為是 NLP 領域的極大突破。

BERT是一種預訓練語言表示的方法,在大量文本語料上訓練了一個通用的“語言了解”模型,然後用這個模型去執行各類下遊子任務。它是第一個用在預訓練NLP上的無監督、深度雙向系統。

Google已開放源碼,并提供了預訓練模型的下載下傳位址,這些模型已經在大規模資料集上進行了預訓練。

git

位址: google-research/bert

當然

git

上還有其他版本的實作,可自行百度。

關于BERT的理論部分,可參照其他部落格,該文後面主要對BERT的上手實踐進行記錄。。

BERT源碼分析

1、從git上克隆代碼

使用如下指令:

git clone https://github.com/google-research/bert
           

2、下載下傳預訓練模型

git

上給出了以下預訓練模型,可根據需要進行下載下傳:

  • BERT-Large, Uncased(Whole Word Masking):24-layer, 1024-hidden, 16-heads, 340M parameters
  • BERT-Large, Cased (Whole Word Masking: 24-layer, 1024-hidden, 16-heads, 340M parameters
  • BERT-Base, Uncased: 12-layer, 768-hidden, 12-heads, 110M parameters
  • BERT-Large, Uncased24-layer, 1024-hidden, 16-heads, 340M parameters
  • BERT-Base, Cased:12-layer, 768-hidden, 12-heads , 110M parameters
  • BERT-Large, Cased: 24-layer, 1024-hidden, 16-heads, 340M parameters
  • BERT-Base, Multilingual Cased (New, recommended): 104 languages, 12-layer, 768-hidden, 12-heads, 110M parameters
  • BERT-Base, Multilingual Uncased (Orig, not recommended) (Not recommended, use Multilingual Cased instead): 102 languages, 12-layer, 768-hidden, 12-heads, 110M parameters
  • BERT-Base, Chinese: Chinese Simplified and Traditional, 12-layer, 768-hidden, 12-heads, 110M parameters

其中,最後一個是針對中文的預訓練模型。

由于接下來的工作是對中文進行處理,是以下載下傳最後一個模型,檔案名為

chinese_L-12_H-768_A-12.zip

。将其解壓至

bert

檔案夾,包含以下三種檔案:

  • 配置檔案(bert_config.json):用于指定模型的超參數
  • 詞典檔案(vocab.txt):用于WordPiece 到 Word id的映射
  • Tensorflow checkpoint(bert_model.ckpt):包含了預訓練模型的權重(實際包含三個檔案)

3、代碼結構

BERT的本質上是一個兩段式的NLP模型。第一階段:Pre-training,利用無标記的語料訓練一個語言模型;第二階段:Fine-tuning,利用預訓練好的語言模型,完成具體的NLP下遊任務。

我們在下載下傳的預訓練模型的基礎上,進行

Fine-tuning

,是以着重介紹微調的代碼使用。

下圖是

bert

的源碼結構:

BERT介紹及中文文本相似度任務實踐BERT簡介BERT源碼分析

其中,

run_classifier.py

run_squad.py

這兩個檔案是用來做

fine-tuning

的。

extract_features.py

檔案從名稱來看可知是用來提取特征向量的,後面文章再具體介紹。

4、 run_classifier.py檔案(中文文本相似度任務fine-tuning)

接下來,以 中文文本相似度 任務為例,介紹進行

fine-tuning

的流程。

打開

run_classifier.py

檔案,檢視其檔案結構,可以如下看到幾個類,下圖是

run_classifier.py

檔案中所包含的幾個資料處理類:

BERT介紹及中文文本相似度任務實踐BERT簡介BERT源碼分析

其中,幾個

****Processor

的類,都繼承于同一個父類

DataProcessor(object)

,該父類提供了 4 個抽象方法,如下圖:

BERT介紹及中文文本相似度任務實踐BERT簡介BERT源碼分析

由名稱我們可知:前三個方法是擷取 訓練、驗證、測試的輸入資料,第四個方法是擷取

labels

對于我們處理自己的資料,就需要自定義新的資料類,并重寫這四個方法,進而實作資料的擷取過程。

1. 自定義資料類

具體的實作方法可以參考其他幾個類的實作過程。

現有類分析

假設我們資料類取名為

SimProcessor

,具體如何實作4個方法呢,我們不妨來看看其中一個資料類,如

XnliProcessor

,以

get_train_examples()函數

get_labels()函數

為例分析:

def get_train_examples(self, data_dir): # 擷取訓練資料
    """See base class."""
    lines = self._read_tsv(
        os.path.join(data_dir, "multinli",
                     "multinli.train.%s.tsv" % self.language)) # 讀取訓練檔案,字尾為.tsv
    examples = [] # 空清單
    for (i, line) in enumerate(lines): # 逐行讀取
        if i == 0:
            continue
        guid = "train-%d" % (i)
        text_a = tokenization.convert_to_unicode(line[0]) # 句子A
        text_b = tokenization.convert_to_unicode(line[1]) # 句子B
        label = tokenization.convert_to_unicode(line[2]) # label
        if label == tokenization.convert_to_unicode("contradictory"):
            label = tokenization.convert_to_unicode("contradiction")
        examples.append(
            InputExample(guid=guid, text_a=text_a, text_b=text_b, label=label))
        # 最後将輸入資料封裝到 InputExample的對象中,并添加到list中,傳回
    return examples
    
def get_labels(self):
    """See base class."""
    return ["contradiction", "entailment", "neutral"] # 标簽資訊
           

由上述代碼可知,讀取資料集的基本流程為:

1. 讀取.tsv格式的訓練資料集檔案(也可是其他格式的檔案,隻要與讀取代碼比對即可)

2. 逐行讀取,并提取出句子A,句子B和label資訊

3. 将上述資料封裝到名為

InputExample

的對象中,并添加到list中

4. 傳回該list

注意:讀取的每行資訊

line

的下标内容取決于

.tsv

資料集檔案中的資料形式(順序)

get_labels()

函數中,用于設定标簽内容,文本相似度計算是二分類問題,是以将label設定為

["0", "1"]

注意:

get_labels

方法傳回的是一個數組,因為相似度問題可了解為二分類,傳回标簽是0和1。

注意:這裡傳回的是字元串。

自定義類

SimProcessor類

的部分實作代碼如下(僅供參考):

使用的

.csv

格式的檔案,每行内容為:

0,句子A,句子B

,是以

label

在索引為0的位置,

label = str(line[0])

class SimProcessor(DataProcessor):
    def get_train_examples(self, data_dir):
        """See base class."""
        file_path = os.path.join(data_dir, 'train.csv') # 資料格式舉例:0,句子A,句子B
        examples = []
        with open(file_path, encoding='utf-8') as f: # 使用open函數讀取檔案
            reader = f.readlines()
        for (i, line) in enumerate(reader):
            guid = "train-%d" % (i)
            split_line = line.strip().split(",") # 根據逗号分隔
            text_a = tokenization.convert_to_unicode(line[1])
            text_b = tokenization.convert_to_unicode(line[2])
            label = str(line[0])
            examples.append(
                InputExample(guid=guid, text_a=text_a, text_b=text_b, label=label))
        return examples
           

小說明

  • tsv

    格式:Tab-separated values的縮寫,即制表符分隔值
  • csv

    格式:Comma-separated values的縮寫,逗号分隔符,更常見一些。

    Python

    中可以使用

    csv

    子產品來實作該格式的讀寫。

2. 增加自定義類

定義完

SimProcessor

類之後,還需要将其加入到

main

函數中的

processors

變量中去。

找到

main()

函數,增加新定義資料類,如下所示:

def main(_):
    tf.logging.set_verbosity(tf.logging.INFO)

    processors = {
        "cola": ColaProcessor,
        "mnli": MnliProcessor,
        "mrpc": MrpcProcessor,
        "xnli": XnliProcessor,
        "sim": SimProcessor, #新增該項
    }
           

3. 函數調用參數

檢視函數執行入口,可發現必須調用如下參數,這5個參數是必填參數:

if __name__ == "__main__":
    flags.mark_flag_as_required("data_dir")
    flags.mark_flag_as_required("task_name")
    flags.mark_flag_as_required("vocab_file")
    flags.mark_flag_as_required("bert_config_file")
    flags.mark_flag_as_required("output_dir")
    tf.app.run()
           

每個參數的含義如下:

參數 說明

data_dir

資料存放位址

task_mask

processor的名字

vocab_file

字典檔案的位址

bert_config_file

配置檔案

output_dir

模型輸出位址

其他參數可到

run_classifier.py

檔案的最開始位置檢視,部分參數如下:

# Other parameters

flags.DEFINE_string(
    "init_checkpoint", None,
    "Initial checkpoint (usually from a pre-trained BERT model).")

flags.DEFINE_bool(
    "do_lower_case", True,
    "Whether to lower case the input text. Should be True for uncased "
    "models and False for cased models.")

flags.DEFINE_integer(
    "max_seq_length", 128,
    "The maximum total input sequence length after WordPiece tokenization. "
    "Sequences longer than this will be truncated, and sequences shorter "
    "than this will be padded.")

flags.DEFINE_bool("do_train", False, "Whether to run training.")

flags.DEFINE_bool("do_eval", False, "Whether to run eval on the dev set.")

flags.DEFINE_bool(
    "do_predict", False,
    "Whether to run the model in inference mode on the test set.")
           

4. 訓練模型

配置完以上代碼,接下來就可以訓練模型了。

參考

git

上給出的示例,如下:

export BERT_BASE_DIR=/path/to/bert/uncased_L-12_H-768_A-12
export GLUE_DIR=/path/to/glue

python run_classifier.py \
  --task_name=MRPC \
  --do_train=true \
  --do_eval=true \
  --data_dir=$GLUE_DIR/MRPC \
  --vocab_file=$BERT_BASE_DIR/vocab.txt \
  --bert_config_file=$BERT_BASE_DIR/bert_config.json \
  --init_checkpoint=$BERT_BASE_DIR/bert_model.ckpt \
  --max_seq_length=128 \
  --train_batch_size=32 \
  --learning_rate=2e-5 \
  --num_train_epochs=3.0 \
  --output_dir=/tmp/mrpc_output/
           

由于參數較多,修改參數後,将其儲存至

.sh

檔案中,友善執行。

修改後示例如下:

#!/usr/bin/env bash
export BERT_BASE_DIR=/**/NLP/bert/chinese_L-12_H-768_A-12 #全局變量 下載下傳的預訓練bert位址
export MY_DATASET=/**/NLP/bert/data #全局變量 資料集所在位址

python run_classifier.py \
  --task_name=sim  \
  --do_train=true \
  --do_eval=true \
  --do_predict=true \
  --data_dir=$MY_DATASET \
  --vocab_file=$BERT_BASE_DIR/vocab.txt \
  --bert_config_file=$BERT_BASE_DIR/bert_config.json \
  --init_checkpoint=$BERT_BASE_DIR/bert_model.ckpt \
  --max_seq_length=128  \
  --train_batch_size=32 \
  --learning_rate=5e-5 \
  --num_train_epochs=2.0 \
  --output_dir=./sim_output/
           

将資料放入

data_dir

目錄下,測試完成後,在

output_dir

檔案夾下會生成訓練好的模型檔案,

test_result.tsv

檔案。如下

BERT介紹及中文文本相似度任務實踐BERT簡介BERT源碼分析

test_result.tsv

檔案内容如下:

BERT介紹及中文文本相似度任務實踐BERT簡介BERT源碼分析

次元

N*2

N

表示測試樣本數量,2列分别表示測試樣本屬于

label

的機率值,即第一列為兩個句子為0的機率,第二列為兩個句子為1的機率。

5. 總結

  1. 下載下傳預訓練模型,解壓
  2. 準備訓練資料,包括訓練集、驗證集和測試集,分别儲存至

    .tsv或.csv

    檔案中
  3. run_classifier.py

    檔案中新增資料處理類

    xxxProcessor()

    ,内部實作其父類

    DataProcessor

    的四個子方法
  4. run_classifier.py

    檔案中的

    main

    函數中增加該資料類
  5. 設定訓練參數,運作

    run_classifier.py

    函數,擷取結果。

附代碼

  • run_classfifer.py

    檔案中修改的代碼部分貼在此處。
  • 以中文文本相似度為例,相當于二分類
# 增加資料處理類, 可放置在class ColaProcessor(DataProcessor)類後面。
class SimProcessor(DataProcessor):
    def get_train_examples(self, data_dir):
        """See base class."""
        file_path = os.path.join(data_dir, 'train.csv')
        examples = []
        with open(file_path, encoding='utf-8') as f:
            reader = f.readlines()
        for (i, line) in enumerate(reader):
            guid = "train-%d" % (i)
            split_line = line.strip().split(",")
            text_a = tokenization.convert_to_unicode(split_line [1])
            text_b = tokenization.convert_to_unicode(split_line [2])
            label = str(split_line [0])
            examples.append(
                InputExample(guid=guid, text_a=text_a, text_b=text_b, label=label))
        return examples

    def get_dev_examples(self, data_dir):
        """See base class."""
        file_path = os.path.join(data_dir, 'val.csv')
        examples = []
        with open(file_path, encoding='utf-8') as f:
            reader = f.readlines()
        for (i, line) in enumerate(reader):
            guid = "dev-%d" % (i)
            split_line = line.strip().split(",")
            text_a = tokenization.convert_to_unicode(split_line [1])
            text_b = tokenization.convert_to_unicode(split_line [2])
            label = str(line[0])
            examples.append(
                InputExample(guid=guid, text_a=text_a, text_b=text_b, label=label))
        return examples

    def get_test_examples(self, data_dir):
        """See base class."""
        file_path = os.path.join(data_dir, 'test.csv')
        examples = []
        with open(file_path, encoding='utf-8') as f:
            reader = f.readlines()
        for i, line in enumerate(reader):
            guid = "test-%d" % (i)
            split_line = line.strip().split(",")
            text_a = tokenization.convert_to_unicode(split_line[1])
            text_b = tokenization.convert_to_unicode(split_line[2])
            label = str(split_line[0])
            examples.append(
                InputExample(guid=guid, text_a=text_a, text_b=text_b, label=label))
        return examples

    def get_labels(self):
        """See base class."""
        return ["0", "1"]

# main函數中增加新定義的資料類
def main(_):
    tf.logging.set_verbosity(tf.logging.INFO)

    processors = {
        "cola": ColaProcessor,
        "mnli": MnliProcessor,
        "mrpc": MrpcProcessor,
        "xnli": XnliProcessor,
        "sim": SimProcessor, #增加此項
    }
           

執行訓練指令,可直接運作上文中提到的

.sh

檔案,命名為

train_sim.sh

。運作:

sh ./train_sim.sh 
           

本文參考資料如下:

  • BERT簡介及中文分類
  • 使用BERT做中文文本相似度計算與文本分類
  • BERT中文實踐

繼續閱讀