共计 6099 个字符,预计需要花费 16 分钟才能阅读完成。
本文概述:复现知乎 -KG 开源我的项目集中的 BERT-NER-pytorch 我的项目之后,进行的一些学习记录,对同样刚入行的小白来说有参考意义。
材料:对于 BERT 模型中的 transformer 介绍,必须分享的是 Jay Alammar 的动画图,看完后我捶胸顿首的为什么没有早早看到这样的国外佳作?
一、筹备工作:
1、数据集
数据集的组织形式、解决形式都是深度畛域的重头戏,能够说一个算法工程师 80% 的工夫都在和数据打交道。当初咱们来介绍一下数据集:
- 获取形式:间接用 google 扩大 gitzip 从 git 间接下载的,
生数据 只有 3 项,是
其中 train 集里有 45000 个 句子,test 集是 3442,咱们须要人为划分出 val 集。
- 生数据模式 - 相似上面这个样子,咱们来看一下 msra_train_bio 的前 17 行(一共 176042 行):
中 B-ORG
共 I-ORG
中 I-ORG
央 I-ORG
致 O
中 B-ORG
国 I-ORG
致 I-ORG
公 I-ORG
党 I-ORG
十 I-ORG
一 I-ORG
大 I-ORG
的 O
贺 O
词 O
各 O
- tags(只有三种实体:机构,人,地位):
O
B-ORG
I-PER
B-PER
I-LOC
I-ORG
B-LOC
ps:能够看到,采纳的是 BIO 标注法,咱们当然能够批改!
- 待会儿要划分数据集为(3 个目录):
Dataset | Number |
---|---|
training set | 42000 |
validation set | 3000 |
test set | 3442 |
失去三个目录????。
- 数据处理后的模式(各取前两条):
sentences.txt文件:
如 何 解 决 足 球 界 长 期 存 在 的 诸 多 矛 盾,重 振 昔 日 津 门 足 球 的 雄 风,成 为 天 津 足 坛 上 下 内 外 到 处 议 论 的 话 题。该 县 一 手 抓 农 业 技 术 推 广,一 手 抓 农 民 科 技 教 育 和 农 技 水 平 的 提 高。而 创 新 的 关 键 就 是 知 识 和 信 息 的 生 产、传 播、使 用。
相应的,tags.txt文件:
O O O O O O O O O O O O O O O O O O O O O B-LOC I-LOC O O O O O O O O B-LOC I-LOC O O O O O O O O O O O O O O
O O O O O O O O O O O O O O O O O O O O O O O O O O O O O O
O O O O O O O O O O O O O O O O O O O O O O O
2、筹备模型和训练好的模型参数
在试验中尝试过屡次之后,发现模型参数获取其实也不难,
作者复现代码的时候,没有间接可获取的 pt 下的模型参数,当初就是一个参数的事。
train.py 代码中,创立 model 时候有这么一句:
model = BertForTokenClassification.from_pretrained()
咱们点进去查看 from_pretrained() 办法,在 pytorch_pretrained_bert 目录下的 modeling.py 文件中。
能够通过路径名或者 url 来获取模型和参数(起初训练中有一点小插曲,我就舍弃了作者提供的,本人从外面下载了压缩包,前面会提到):
PRETRAINED_MODEL_ARCHIVE_MAP = {
'bert-base-uncased': "https://s3.amazonaws.com/models.huggingface.co/bert/bert-base-uncased.tar.gz",
'bert-large-uncased': "https://s3.amazonaws.com/models.huggingface.co/bert/bert-large-uncased.tar.gz",
'bert-base-cased': "https://s3.amazonaws.com/models.huggingface.co/bert/bert-base-cased.tar.gz",
'bert-large-cased': "https://s3.amazonaws.com/models.huggingface.co/bert/bert-large-cased.tar.gz",
'bert-base-multilingual-uncased': "https://s3.amazonaws.com/models.huggingface.co/bert/bert-base-multilingual-uncased.tar.gz",
'bert-base-multilingual-cased': "https://s3.amazonaws.com/models.huggingface.co/bert/bert-base-multilingual-cased.tar.gz",
'bert-base-chinese': "https://s3.amazonaws.com/models.huggingface.co/bert/bert-base-chinese.tar.gz",
}
ps:先获取 tf 的模型参数再转换为 pt,费了我好大劲,间接从 Transformer 库下载了一个 convert_tf_checkpoint_to_pytorch.py 文件到 tf 模型参数目录下,操作了一番失去了 pt 的.bin 文件,好在当初一句话的事。
3、train.py 文件解读
数据集的解决和超参数的设置简略,不波及到咱们文章的外围,这里就不做过多记录,想理解则去看 github 我的项目,传送门,
这个文件放的货色比拟多,有
①parse,params 设置,logger 日志
②dataloader,model,optimizer 和 train_and_evaluate
- 参数 parse(这个参数解析是运行.py 文件时候的参数)
parser = argparse.ArgumentParser()
parser.add_argument('--data_dir', default='NER-BERT-pytorch-data-msra', help="Directory containing the dataset")
parser.add_argument('--bert_model_dir', default='pt_things', help="Directory containing the BERT model in PyTorch")
……
之后在 main 中,记录参数到内存:args = parser.parse_args()
在接下来的应用中通过 args.param 来获取参数,
- logger 日志
相干的解决放在了 utils.py 中,在 train.py 中间接:
# 创立
utils.set_logger(os.path.join(args.model_dir, 'train.log'))
# 须要记录的时候:logging.info("device: {}, n_gpu: {}, 16-bits training: {}".format(params.device, params.n_gpu, args.fp16))
- model
简略,两句话:
model = BertForTokenClassification.from_pretrained(args.bert_model_dir, num_labels=len(params.tag2idx))
model.to(params.device)
里边包含模型的加载和参数的加载,在训练时咱们在看到 modelconfig 之后,还会看到两句提醒:
Weights of BertForTokenClassification not initialized from pretrained model: ['classifier.weight', 'classifier.bias']
Weights from pretrained model not used in BertForTokenClassification: ['cls.predictions.bias', 'cls.predictions.transform.dense.weight', 'cls.predictions.transform.dense.bias', 'cls.predictions.decoder.weight', 'cls.seq_relationship.weight', 'cls.seq_relationship.bias', 'cls.predictions.transform.LayerNorm.weight', 'cls.predictions.transform.LayerNorm.bias']
(开始我还认为这是模型参数导入失败,就去查找解决办法。
我还一度认为 pytorch_model.bin 文件中的参数和模型不匹配,从新从源码提供的链接下载了压缩包,但还有这两句话。
后果在 Google 上一个论坛发现这两句话是胜利调用参数的意思。)
解谜:这个 model 里边包含了 embedding 和 bert NER 两个局部,其中模型的参数应该是 embedding 局部的,而具体的 NER 工作应该应用咱们本人的数据来训练!
读 model 源码:各层具体解释
查看 model(BertForTokenClassification):
有三层:
①BertModel
BertEmbeddings:里边含有多层
(position_embeddings): Embedding(512, 768)
(token_type_embeddings): Embedding(2, 768)
(LayerNorm): BertLayerNorm()
(dropout): Dropout(p=0.1, inplace=False)
BertEncoder:里边有 12 层 encoder,每一个 encoder 都是一个 BertLayer:
(attention): BertAttention #重中之重,有机会肯定要刷
(intermediate): BertIntermediate
(output): BertOutput
BertPooler
②DropOut
③Linear
- optimizer
full_finetuning
4、单机多卡并行和 fp16
- 多卡
要把 模型和数据 都调配到多卡;
①指定虚构 gpu:os.environ["CUDA_VISIBLE_DEVICES"] = '1,2,3,0'
代表虚拟地址对应的物理地址为 1,2,3,0(过后因为师兄的主 gpu 是物理 0 号卡,所以避开这个残余显存绝对较小的卡)
ps:截图时师兄曾经不再用了。
②在筹备模型和数据之前,放这一句:params.device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
预计 ’cuda’ 前面的局部是作者不想看到报错……
留神:如果要写 ’cuda:[num]’,请写后面指定 gpu 时候的第一个,在这里就是 1!
③随机种子调配到各个 gpu:
# 设置可反复试验的随机种子
random.seed(args.seed)
torch.manual_seed(args.seed) #给 cpu 设置
if params.n_gpu > 0:
torch.cuda.manual_seed_all(args.seed) # set random seed for all GPUs
ps:如果单卡,只是去掉 all;
④给 model 调配多卡:
model = BertForTokenClassification.from_pretrained(args.bert_model_dir, num_labels=len(params.tag2idx))
model.to(params.device)
if params.n_gpu > 1 and args.multi_gpu:
model = torch.nn.DataParallel(model)
⑤最初,给数据调配多卡:
# Initialize the DataLoader
data_loader = DataLoader(args.data_dir, args.bert_model_dir, params, token_pad_idx=0)
# Load training data and test data
train_data = data_loader.load_data('train')
val_data = data_loader.load_data('val')
……
在 train()函数之前:# data iterator for training
train_data_iterator = data_loader.data_iterator(train_data, shuffle=True)
# Train for one epoch on training set
train(model, train_data_iterator, optimizer, scheduler, params)
在 data_iterator()中设置的给每个 batch 调配的卡:
# shift tensors to GPU if available
batch_data, batch_tags = batch_data.to(self.device), batch_tags.to(self.device)
yield batch_data, batch_tags
这里的 self.device 是类承受的参数。
- fp16
参考:一篇讲 fp16 减速原理的 CSDN
fp16 应用 2 字节编码存储
长处:内存占用少(主)+ 减速计算
毛病:加法操作容易高低溢出
(有机会能够专门试验一下)
5、进度条工具
此处计算出每一个 epoch 下计算 1400 个 batch,
所以把进度条放到每一个 epoch 中:
t = trange(params.train_steps)
for i in t:
# fetch the next training batch
batch_data, batch_tags = next(data_iterator)
……
loss = model(~)……
t.set_postfix(loss='{:05.3f}'.format(loss_avg()))
后果展现:
6、评估指标库 metrics
能够应用的后果评估指标(冰山一角):
from metrics import f1_score
from metrics import accuracy_score
from metrics import classification_report
模板:
metrics = {}
f1 = f1_score(true_tags, pred_tags)
accuracy=accuracy_score(true_tags, pred_tags)
metrics['loss'] = loss_avg()
metrics['f1'] = f1
metrics['accuracy']=accuracy
metrics_str = ";".join("{}: {:05.2f}".format(k, v) for k, v in metrics.items())
logging.info("- {} metrics:".format(mark) + metrics_str)
后果展现:
如何辨别准确率和召回率?
7、屡次试验存在的问题
我的 f1 分数始终都在 50 以下,然而准确率始终都在 97% 左近,最近因为要肝链接抽取,所以这部分先放一下把。回头补全