最近在做BERT的fine-tune工作,記錄一下閱讀專案https://github.com/weizhepei/BERT-NER時梳理的訓練pipline,該專案基於Google的Transformers程式碼構建
前置知識
bert的DataLoader簡介(真的很簡介)
https://zhuanlan.zhihu.com/p/384469908
yield介紹
https://www.runoob.com/w3cnote/python-yield-used-analysis.html
這是一種提高程式碼複用性的方法
帶yield的函式被稱為 generator(生成器),呼叫next()方法可使其執行至函式內部的yield處中斷並返回一個迭代值
Pipeline
訓練部分
① 執行build_dataset_tags.py將原始資料集處理為txt文字儲存(生成原始資料集文字)
②資料流
注:“XX.py--->”代表該過程由XX.py發起
1、train.py--->class DataLoader[data_loader.py]--->train_data(d)
通過data_loader.py中的load_data,再呼叫load_sentences_tags
load_sentences_tags返回一個字典d,包含:
-
使用tokenizer對原始句子的token
-
token對應的id
-
token對應的tag
-
句子的長度
2、train.py--->train_and_evaluate(train_data, val_data)--->2個generator--->evaluate()[evaluate.py]
此處生成的兩個生成器分別用於在訓練和測試時以迭代方式獲取batch資料
3、train.py--->evaluate(generator)[evaluate.py]--->batch_data, batch_token_starts, batch_tags--->將batch輸入model[在train.py處例項化]中--->loss、batch_output、batch_tags--->計算出F1值返回給train_and_evaluate()
在得到F1值後,根據設定的引數決定是否滿足停止訓練的條件
資料迭代器
data_loader.py--->data_iterator(train/val/test_data)
--->計算會產生的batch的數量(由train/val/test_data中記錄的句子長度size和class DataLoader中人為設定的batch_size引數決定)--->提取train/val/test_data中的sentences、tags
# 計算batch數
if data['size'] % self.batch_size == 0:
BATCH_NUM = data['size']//self.batch_size
else:
BATCH_NUM = data['size']//self.batch_size + 1
# one pass over data
# 提取一個batch,由batch_size個sentences構成
for i in range(BATCH_NUM):
# fetch sentences and tags
if i * self.batch_size < data['size'] < (i+1) * self.batch_size:
sentences = [data['data'][idx] for idx in order[i*self.batch_size:]]
if not interMode:
tags = [data['tags'][idx] for idx in order[i*self.batch_size:]]
else:
sentences = [data['data'][idx] for idx in order[i*self.batch_size:(i+1)*self.batch_size]]
if not interMode:
tags = [data['tags'][idx] for idx in order[i*self.batch_size:(i+1)*self.batch_size]]
--->計算batch中最大的句子長度--->將資料轉換為np矩陣(numpy array)
--->將資料拷貝到另一個np矩陣,使得所有資料的長度與最大句子長度保持一致(即完成了padding)
# prepare a numpy array with the data, initialising the data with pad_idx
# batch_data的形狀為:最長句子長度X最長句子長度(batch_len X batch_len),元素全為0
batch_data = self.token_pad_idx * np.ones((batch_len, max_subwords_len))
batch_token_starts = []
# copy the data to the numpy array
for j in range(batch_len):
cur_subwords_len = len(sentences[j][0])
if cur_subwords_len <= max_subwords_len:
batch_data[j][:cur_subwords_len] = sentences[j][0]
else:
batch_data[j] = sentences[j][0][:max_subwords_len]
token_start_idx = sentences[j][-1]
token_starts = np.zeros(max_subwords_len)
token_starts[[idx for idx in token_start_idx if idx < max_subwords_len]] = 1
batch_token_starts.append(token_starts)
max_token_len = max(int(sum(token_starts)), max_token_len)
--->將所有索引格式的(我理解就是numpy array形式的)資料轉換為torch LongTensors
--->返回batch_data, batch_token_starts, batch_tags(這就是用於直接輸入模型的資料)