百度NLP預訓練模型ERNIE2.0最強實操課程來襲!【附教程】

  • 2019 年 11 月 8 日
  • 筆記

2019年3月,百度正式發佈NLP模型ERNIE,其在中文任務中全面超越BERT一度引發業界廣泛關注和探討。經過短短几個月時間,百度ERNIE再升級,發佈持續學習的語義理解框架ERNIE 2.0,及基於此框架的ERNIE 2.0預訓練模型。繼1.0後,ERNIE英文任務方面取得全新突破,在共計16個中英文任務上超越了BERT和XLNet, 取得了SOTA效果。

本篇內容可以說是史上最強實操課程,由淺入深完整帶大家試跑ERNIE,大家可前往AI Studio fork代碼 (https://aistudio.baidu.com/aistudio/projectdetail/117030),運行即可獲贈12小時GPU算力,每天都有哦~

一、基礎部分

1.1 準備代碼、數據、模型

step1:下載ERNIE代碼。溫馨提示:如果下載慢,暫停重試

!git clone --depth 1 https://github.com/PaddlePaddle/ERNIE.git

step2:下載並解壓finetune數據

!wget --no-check-certificate https://ernie.bj.bcebos.com/task_data_zh.tgz  !tar xf task_data_zh.tgz

step3:下載預訓模型

!wget --no-check-certificate https://ernie.bj.bcebos.com/ERNIE_1.0_max-len-512.tar.gz  !mkdir -p ERNIE1.0  !tar zxf ERNIE_1.0_max-len-512.tar.gz -C ERNIE1.0

備用方案,如果下載慢的話,可以用我們預先下載好的代碼和數據

%cd ~  !cp -r work/ERNIE1.0 ERNIE1.0  !cp -r work/task_data task_data  !cp -r work/lesson/ERNIE ERNIE

完成ERNIE代碼部分的準備之後,讓我們一起以一個序列標註任務來舉例。

什麼是序列標註任務?

下面這張圖可以概括性的讓大家理解序列標註任務:

序列標註的任務可以用來做什麼?

可以:信息抽取、數據結構化,幫助搜索引擎搜索的更精準
可以:…

序列標註任務: 一起來看看這個任務的數據長什麼樣子吧?

序列標註任務輸入數據包含2部分:
1)標籤映射文件:存儲標籤到ID的映射。
2)訓練測試數據:2列,文本、標籤(文本中每個字之間使用隱藏字符2分割,標籤同理。)

# 標籤映射文件  !cat task_data/msra_ner/label_map.json

{  "B-PER": 0,  "I-PER": 1,  "B-ORG": 2,  "I-ORG": 3,  "B-LOC": 4,  "I-LOC": 5,  "O": 6  }

 

# 測試數據  !head task_data/msra_ner/dev.tsv

B: Begin
I: Inside
O: Outside

ERNIE應用於序列化標註

 

1.2 利用ERNIE做Finetune

step1:設置環境變量

%cd ERNIE  !ln -s ../task_data  !ln -s ../ERNIE1.0  %env TASK_DATA_PATH=task_data  %env MODEL_PATH=ERNIE1.0  !echo "task_data_path: ${TASK_DATA_PATH}"  !echo "model_path: ${MODEL_PATH}"

step2:運行finetune腳本

!sh script/zh_task/ernie_base/run_msra_ner.sh

1.3將Finetune結果打印

在finetune過程中,會自動保存對test集的預測結果,我們可以查看預測結果是否符合預期。

由於Finetune需要一些時間,所以不等Finetune完了,直接查看我們之前已經Finetune收斂後的模型與test集的預測結果

%cd ~  show_ner_prediction('work/lesson/test_result.5.final')

二、進階部分

2.1 GPU顯存過小,如何使用ERNIE?

腳本進階:模型太大,無法完全放進顯存的情況下,如何只使用前3層參數熱啟Finetune?

如果能只加載幾層模型就好了!

方法:只需要修改一行配置文件ernie_config.json,就能自動的使用前3層參數熱啟Finetune。

提示:ernie_config.json在ERNIE1.0發佈的預訓練模型中

TODO 結合“終端”標籤,運行一下吧
提示:您可以需要用到sed與pwd命令

step1:設置環境變量

%cd ~%cd ERNIE  !ln -s ../task_data  !ln -s ../ERNIE1.0  %env TASK_DATA_PATH=task_data  %env MODEL_PATH=ERNIE1.0  !echo "task_data_path: ${TASK_DATA_PATH}"  !echo "model_path: ${MODEL_PATH}"  !pwd

!sh script/zh_task/ernie_base/run_msra_ner.sh

2.2如何將ERNIE適配我的業務數據?

數據進階:如何修改輸入格式?

假設msra ner任務的輸入數據格式變了,每條樣本不是以行式保存,而是以列式保存。列式保存是指,每條樣本由多行組成,每行包含一個字符和對應的label,不同樣本間以空行分割,具體樣例如下:

text_a label  海 O  釣 O  比 O  賽 O  地 O  點 O  在 O  廈 B-LOC  門 I-LOC  與 O  金 B-LOC  門 I-LOC  之 O  間 O  的 O  海 O  域 O  。 O

當輸入數據為列式時,我們如何修改ERNIE的數據處理代碼,以適應新的數據格式。
首先,我們先大致了解一下ERNIE的數據處理流程:

  • ERNIE對於finetune任務的所有數據處理代碼都在reader/task_reader.py中,裏面已經預先寫好了適合多種不同類型任務的Reader類,ERNIE通過Reader讀取並處理數據給後續模型使用。
  • Reader類對數據處理流程做了以下幾步抽象:

step 1.  從文件中逐條讀取樣本,通過_read_tsv等方法,讀取不同格式的文件,並將讀取的每條樣本存入一個list

step 2. 逐一將讀取的樣本轉化為Record。Record中包含了一條樣本經過數據處理後,模型所需要的所有features。處理成Record的流程一般又分以下幾步:

1. 將文本tokenize,超過最大長度時截斷;
2. 加入'[CLS]’、'[SEP]’等標記符後,將文本ID化;
3. 生成每個token對應的position和token_type信息。

step 3. 將多個Record組成batch,同一個batch內feature長度不一致時,padding至batch內最大的feature長度。

了解了ERNIE的數據處理流程以後,我們發現當輸入數據格式變了,我們只需要修改第1步的代碼,保持其他代碼不變,就能適應新的數據格式。具體來說,只需要在reader/task_reader.py的 SequenceLabelReader 類中,加入下面的 _read_tsv 函數(重寫基類 BaseReader 的 _read_tsv)。

def _read_tsv(self, input_file, quotechar=None):  with open(input_file, 'r', encoding='utf8') as f:  reader = csv_reader(f)  headers = next(reader)  text_indices = [  index for index, h in enumerate(headers) if h != 'label'  ]  Example = namedtuple('Example', headers)    examples = []  buf_t, buf_l = [], []  for line in reader:  if len(line) != 2:  assert len(buf_t) == len(buf_l)  example = Example(u'^B'.join(buf_t), u'^B'.join(buf_l))  examples.append(example)  buf_t, buf_l = [], []  continue  if line[0].strip() == '':  continue  buf_t.append(line[0])  buf_l.append(line[1])  if len(buf_t) > 0:  assert len(buf_t) == len(buf_l)  example = Example(u'^B'.join(buf_t), u'^B'.join(buf_l))  examples.append(example)  buf_t, buf_l = [], []  return examples

我們將已經修改好的數據和代碼,預先放在work/lesson/2目錄中,可以替換掉ERNIE項目中對應的文件,然後嘗試運行

%cd ~  !cp -r work/lesson/2/msra_ner_columnwise task_data/msra_ner_columnwise  !cp -r work/lesson/2/task_reader.py ERNIE/reader/task_reader.py  !cp -r work/lesson/2/run_msra_ner.sh ERNIE/script/zh_task/ernie_base/run_msra_ner_columnwise.sh  %cd ERNIE  !ln -s ../task_data  !ln -s ../ERNIE1.0  %env TASK_DATA_PATH=task_data  %env MODEL_PATH=ERNIE1.0 !sh script/zh_task/ernie_base/run_msra_ner_columnwise.sh

2.3在哪裡改模型結構?

模型進階:如何將序列標註任務的損失函數替換為CRF?
目前序列標註任務的finetune代碼中,以 softmax ce 作為損失函數,該損失函數較為簡單,沒有考慮到序列中詞與詞之間的聯繫,如何替換一個更優秀的損失函數呢?

我們只需要修改其中的create_model函數,將 softmax ce 損失函數部分,替換為 linear_chain_crf 即可,具體代碼如下:

def create_model(args, pyreader_name, ernie_config, is_prediction=False):  pyreader = fluid.layers.py_reader(  capacity=50,  shapes=[[-1, args.max_seq_len, 1], [-1, args.max_seq_len, 1],  [-1, args.max_seq_len, 1], [-1, args.max_seq_len, 1],  [-1, args.max_seq_len, 1], [-1, args.max_seq_len, 1], [-1, 1]],  dtypes=[  'int64', 'int64', 'int64', 'int64', 'float32', 'int64', 'int64'  ],  lod_levels=[0, 0, 0, 0, 0, 0, 0],  name=pyreader_name,  use_double_buffer=True)    (src_ids, sent_ids, pos_ids, task_ids, input_mask, labels,  seq_lens) = fluid.layers.read_file(pyreader)    ernie = ErnieModel(  src_ids=src_ids,  position_ids=pos_ids,  sentence_ids=sent_ids,  task_ids=task_ids,  input_mask=input_mask,  config=ernie_config,  use_fp16=args.use_fp16)    enc_out = ernie.get_sequence_output()  enc_out = fluid.layers.dropout(  x=enc_out, dropout_prob=0.1, dropout_implementation="upscale_in_train")  logits = fluid.layers.fc(  input=enc_out,  size=args.num_labels,  num_flatten_dims=2,  param_attr=fluid.ParamAttr(  name="cls_seq_label_out_w",  initializer=fluid.initializer.TruncatedNormal(scale=0.02)),  bias_attr=fluid.ParamAttr(  name="cls_seq_label_out_b",  initializer=fluid.initializer.Constant(0.)))  infers = fluid.layers.argmax(logits, axis=2)    ret_infers = fluid.layers.reshape(x=infers, shape=[-1, 1])  lod_labels = fluid.layers.sequence_unpad(labels, seq_lens)  lod_infers = fluid.layers.sequence_unpad(infers, seq_lens)  lod_logits = fluid.layers.sequence_unpad(logits, seq_lens)    (_, _, _, num_infer, num_label, num_correct) = fluid.layers.chunk_eval(  input=lod_infers,  label=lod_labels,  chunk_scheme=args.chunk_scheme,  num_chunk_types=((args.num_labels-1)//(len(args.chunk_scheme)-1)))    probs = fluid.layers.softmax(logits)  crf_loss = fluid.layers.linear_chain_crf(  input=lod_logits,  label=lod_labels,  param_attr=fluid.ParamAttr(  name='crf_w',  initializer=fluid.initializer.TruncatedNormal(scale=0.02)))  loss = fluid.layers.mean(x=crf_loss)    graph_vars = {  "inputs": src_ids,  "loss": loss,  "probs": probs,  "seqlen": seq_lens,  "num_infer": num_infer,  "num_label": num_label,  "num_correct": num_correct,  }    for k, v in graph_vars.items():  v.persistable = True    return pyreader, graph_vars

我們將已經修改好的數據和代碼,預先放在work/lesson/3 目錄中,可以替換掉ERNIE項目中對應的文件,然後嘗試運行

%cd ~  !cp -r work/lesson/3/sequence_label.py ERNIE/finetune/sequence_label.py  %cd ERNIE  !ln -s ../task_data  !ln -s ../ERNIE1.0  %env TASK_DATA_PATH=task_data  %env MODEL_PATH=ERNIE1.0  !sh script/zh_task/ernie_base/run_msra_ner_columnwise.sh

修改後重新運行finetune腳本:

sh script/zh_task/ernie_base/run_msra_ner.sh等待運行完後,取最後一次評估結果,對比如下:

以上便是實戰課程的全部操作,直接fork可點擊下方鏈接:
https://aistudio.baidu.com/aistudio/projectdetail/117030

劃重點!
查看ERNIE模型使用的完整內容和教程,請點擊下方鏈接,建議Star收藏到個人主頁,方便後續查看。
GitHub:https://github.com/PaddlePaddle/ERNIE

版本迭代、最新進展都會在GitHub第一時間發佈,歡迎持續關注!

也邀請大家加入ERNIE官方技術交流**QQ群:760439550**,可在群內交流技術問題,會有ERNIE的研發同學為大家及時答疑解惑。