本文内容清單
- 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
的源碼結構:

其中,
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
檔案中所包含的幾個資料處理類:
其中,幾個
****Processor
的類,都繼承于同一個父類
DataProcessor(object)
,該父類提供了 4 個抽象方法,如下圖:
由名稱我們可知:前三個方法是擷取 訓練、驗證、測試的輸入資料,第四個方法是擷取
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
小說明
-
格式:Tab-separated values的縮寫,即制表符分隔值tsv
-
格式:Comma-separated values的縮寫,逗号分隔符,更常見一些。csv
中可以使用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()
每個參數的含義如下:
參數 | 說明 |
---|---|
| 資料存放位址 |
| processor的名字 |
| 字典檔案的位址 |
| 配置檔案 |
| 模型輸出位址 |
其他參數可到
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
檔案。如下
test_result.tsv
檔案内容如下:
次元
N*2
,
N
表示測試樣本數量,2列分别表示測試樣本屬于
label
的機率值,即第一列為兩個句子為0的機率,第二列為兩個句子為1的機率。
5. 總結
- 下載下傳預訓練模型,解壓
- 準備訓練資料,包括訓練集、驗證集和測試集,分别儲存至
檔案中.tsv或.csv
- 在
檔案中新增資料處理類run_classifier.py
,内部實作其父類xxxProcessor()
的四個子方法DataProcessor
- 在
檔案中的run_classifier.py
函數中增加該資料類main
- 設定訓練參數,運作
函數,擷取結果。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中文實踐