2019年3月,百度正式發佈NLP模型ERNIE,其在中文任務中全面超越BERT一度引起業界普遍關注和探討。通過短短几個月時間,百度ERNIE再升級,發佈持續學習的語義理解框架ERNIE 2.0,及基於此框架的ERNIE 2.0預訓練模型。繼1.0後,ERNIE英文任務方面取得全新突破,在共計16箇中英文任務上超越了BERT和XLNet, 取得了SOTA效果。git
本篇內容能夠說是史上最強實操課程,由淺入深完整帶你們試跑ERNIE,你們可前往AI Studio fork代碼 (https://aistudio.baidu.com/aistudio/projectdetail/117030),運行便可獲贈12小時GPU算力,天天都有哦~github
step1:下載ERNIE代碼。舒適提示:若是下載慢,暫停重試json
!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:下載預訓模型app
!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代碼部分的準備以後,讓咱們一塊兒以一個序列標註任務來舉例。less
什麼是序列標註任務?ide
下面這張圖能夠歸納性的讓你們理解序列標註任務:函數
序列標註的任務能夠用來作什麼?學習
能夠:信息抽取、數據結構化,幫助搜索引擎搜索的更精準
能夠:…
序列標註任務: 一塊兒來看看這個任務的數據長什麼樣子吧?
序列標註任務輸入數據包含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應用於序列化標註
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
在finetune過程當中,會自動保存對test集的預測結果,咱們能夠查看預測結果是否符合預期。
因爲Finetune須要一些時間,因此不等Finetune完了,直接查看咱們以前已經Finetune收斂後的模型與test集的預測結果
%cd ~ show_ner_prediction('work/lesson/test_result.5.final')
腳本進階:模型太大,沒法徹底放進顯存的狀況下,如何只使用前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
數據進階:如何修改輸入格式?
假設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的數據處理流程:
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
模型進階:如何將序列標註任務的損失函數替換爲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的研發同窗爲你們及時答疑解惑。