Bert介紹
Bert模型是Google在2018年10月釋出的語言表示模型,Bert在NLP領域橫掃了11項任務的最優結果,可以說是現今最近NLP中最重要的突破。Bert模型的全稱是Bidirectional Encoder Representations from Transformers,是透過訓練Masked Language Model和預測下一句任務得到的模型。關於Bert具體訓練的細節和更多的原理,有興趣的讀者可以去看在[arXiv](https://arxiv.org/abs/1810.04805)上的原文。本篇文章從實踐入手,帶領大家進行Bert的中文文字分類和作為句子向量進行使用的教程。
對於文字分類任務,一個句子中的N個字元對應了E_1,…,E_N,這N個embedding。文字分類實際上是將BERT得到的T_1這一層連線上一個全連線層進行多分類。
準備工作
1. 下載bert
在命令列中輸入
git clone
https://github.com/google-research/bert.git
2. 下載bert預訓練模型
Google提供了多種預訓練好的bert模型,有針對不同語言的和不同模型大小的。對於中文模型,我們使用[Bert-Base, Chinese](https://storage.googleapis.com/bert_models/2018_11_03/chinese_L-12_H-768_A-12.zip)。為了下載該模型,可能需要使用梯子。如果需要下載其他的模型(英文以及其他語言),可以在[Bert](https://github.com/google-research/bert)裡的Pre-trained models找到下載連結。
3.(可選項)
安裝bert-as-service,這是一個可以利用bert模型將句子對映到固定長度向量的服務。
在命令列中輸入
pip install bert-serving-server # server
pip install bert-serving-client # client, independent of 'bert-serving-server'
該服務要求tensorflow的最低版本為1.10。
準備資料
資料格式
作為中文文字分類問題,需要先將資料集整理成可用的形式。不同的格式對應了不同的DataProcessor類。可以將資料儲存成如下格式:
game APEX是個新出的吃雞遊戲。
technology Google將要推出tensorflow2.0。
一行代表一個文字,由標籤加上一個tab加上正文組成。
將文字分割為三個檔案,train.tsv(訓練集),dev.tsv(驗證集),test.tsv(測試集);然後放置在同一個data_dir資料夾下。
編寫DataProcessor類
在bert資料夾下的“run_classifier.py**中的”def main(_):”函式中將processors的內容增加為
python
processors = {
"cola": ColaProcessor,
"mnli": MnliProcessor,
"mrpc": MrpcProcessor,
"xnli": XnliProcessor,
"mytask": MyTaskProcessor,
}
實現如下的“MyTaskProcessor
(DataProcessor)”類,並將這一段程式碼放置在“run_classifier.py”和其他Processor並列的位置。
“\_\_init\_\_(self)”中的self.labels含有所有的分類label,在這個例子中我們將文字可能分為3類:game, fashion, houseliving。
python
class MyTaskProcessor(DataProcessor):
"""Processor for the News data set (GLUE version)."""
def __init__(self):
self.labels = ['game', 'fashion', 'houseliving']
def get_train_examples(self, data_dir):
return self._create_examples(
self._read_tsv(os.path.join(data_dir, "train.tsv")), "train")
def get_dev_examples(self, data_dir):
return self._create_examples(
self._read_tsv(os.path.join(data_dir, "dev.tsv")), "dev")
def get_test_examples(self, data_dir):
return self._create_examples(
self._read_tsv(os.path.join(data_dir, "test.tsv")), "test")
def get_labels(self):
return self.labels
def _create_examples(self, lines, set_type):
"""Creates examples for the training and dev sets."""
examples = []
for (i, line) in enumerate(lines):
guid = "%s-%s" % (set_type, i)
text_a = tokenization.convert_to_unicode(line[1])
label = tokenization.convert_to_unicode(line[0])
examples.append(
InputExample(guid=guid, text_a=text_a, text_b=None, label=label))
return examples
如果資料格式並不是一個label,一個tab,一段文字;則需要更改“_create_examples()”的實現。
編寫執行指令碼
新建一個執行指令碼檔名為“run.sh”,將檔案內容編輯為:
bash
export DATA_DIR=/media/ganjinzero/Code/bert/data/
export BERT_BASE_DIR=/media/ganjinzero/Code/bert/chinese_L-12_H-768_A-12
python run_classifier.py \
--task_name=mytask \
--do_train=true \
--do_eval=true \
--data_dir=$DATA_DIR/ \
--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=/mytask_output
其中DATA_DIR是你的要訓練的文字的資料所在的資料夾,BERT_BASE_DIR是你的bert預訓練模型存放的地址。task_name要求和你的DataProcessor類中的名稱一致。下面的幾個引數,do_train代表是否進行fine tune,do_eval代表是否進行evaluation,還有未出現的引數do_predict代表是否進行預測。如果不需要進行fine tune,或者顯示卡配置太低的話,可以將do_trian去掉。max_seq_length代表了句子的最長長度,當視訊記憶體不足時,可以適當降低max_seq_length。
進行預測
執行指令碼
bash
./run.sh
可以得到類似如下樣式的結果
***** Eval results *****
eval_accuracy = 0.845588
eval_loss = 0.505248
global_step = 343
loss = 0.505248
如果出現了這樣的輸出,就是執行成功了。在“run.sh”裡指定的output_dir資料夾下可以看到模型的evaluation結果和fine-tune(微調)之後的模型檔案。
以句子向量的形式使用Bert
如果想要將bert模型的編碼和其他模型一起使用,將bert模型作為句子向量使用很有意義(也就是所謂的句子級別的編碼)。我們可以使用bert-as-service來完成這個目標。
安裝完bert-as-service以後,就可以利用bert模型將句子對映到固定長度的向量上。在終端中用一下命令啟動服務:
bash
bert-serving-start -model_dir /media/ganjinzero/Code/bert/chinese_L-12_H-768_A-12 -num_worker=4
model_dir後面的引數是bert預訓練模型所在的資料夾。num_worker的數量應該取決於你的CPU/GPU數量。
這時就可以在Python中呼叫如下的命令:
python
from bert_serving.client import BertClient
bc = BertClient()
bc.encode(['一二三四五六七八', '今天您吃了嗎?'])
最好以列表的形式,而非單個字串傳給”bc.encode()”引數,這樣程式執行的效率較高。
參考文件
[Github:bert]
(https://github.com/google-research/bert)
[arXiv:bert](https://arxiv.org/pdf/1810.04805.pdf)
[Github:bert-as-service](https://github.com/hanxiao/bert-as-service)
【作者簡介】
GjZero,清華大學統計中心博士二年級在讀。研究方向是醫學資訊學中的自然語言處理。興趣是撲克、麻將等和博弈論有關的運動。
github:
https://github.com/GanjinZero
個人主頁:
https://ganjinzero.github.io/