https://zhuanlan.zhihu.com/p/50773178

概述

本文基于 pytorch-pretrained-BERT(huggingface)版本的复现,探究如下几个问题:

  1. pytorch-pretrained-BERT的基本框架和使用
  2. 如何利用BERT将句子转为词向量
  3. 如何使用BERT训练模型(针对SQuAD数据集的问答模型,篇幅问题,可能下篇再写)

因为已经有很多文章对BERT的结构和效果做了详尽的介绍,所以对于模型的效果和结构就不在这里赘述了。

基本框架和使用

环境

首先,利用pip安装包:

1    pip install pytorch-pretrained-bert

这种安装方法可能会导致一个编码问题,具体细节可以参考这里。作者已经把这个错误改了过来,但是没有发布新的版本(当前版本为0.1.2),因此需要我们先从github上下载源码,然后安装:

1    pip install [--editable] .

结构

Google提供了6种预训练的模型,具体细节如下:

  • bert-base-uncased: 12-layer, 768-hidden, 12-heads, 110M parameters
  • bert-large-uncased: 24-layer, 1024-hidden, 16-heads, 340M parameters
  • bert-base-cased: 12-layer, 768-hidden, 12-heads , 110M parameters
  • bert-base-multilingual: 102 languages, 12-layer, 768-hidden, 12-heads, 110M parameters
  • bert-base-chinese: Chinese Simplified and Traditional, 12-layer, 768-hidden, 12-heads, 110M parameters

作者对于每个预训练的模型都提供了6个model类和3个tokenizer类供我们使用。具体的模型简介和参数可以参照这里README中pytorch model和Tokenizer部分。

1    tokenizer = BertTokenizer.from_pretrained('bert-base-uncased')
2 model = BertModel.from_pretrained('bert-base-uncased')

初始化

上面就是对model和tokenizer进行初始化的代码,“BertTokenizer”和“BertModel”可以替换为自己需要的模型和分词器,后面函数的参数对应6中预训练的模型。由于是预训练的模型,所以肯定是要下模型和词表的,作者把资源放到了亚马逊的云上,链接写在了一个环境变量里,如果第一次使用,要提前下载,下载后的文件存放在cache文件夹:~/.pytorch_pretrained_bert/下。

1    PRETRAINED_MODEL_ARCHIVE_MAP = {
2 'bert-base-uncased': "https://s3.amazonaws.com/models.huggingface.co/bert/bert-base-uncased.tar.gz",
3 'bert-large-uncased': "https://s3.amazonaws.com/models.huggingface.co/bert/bert-large-uncased.tar.gz",
4 'bert-base-cased': "https://s3.amazonaws.com/models.huggingface.co/bert/bert-base-cased.tar.gz",
5 'bert-base-multilingual': "https://s3.amazonaws.com/models.huggingface.co/bert/bert-base-multilingual.tar.gz",
6 'bert-base-chinese': "https://s3.amazonaws.com/models.huggingface.co/bert/bert-base-chinese.tar.gz",
7 }

不知道其他人下载情况如何,反正在我这边利用requeset去下就很慢,所以肯定是想提前下好,然后放过去的。这是我踩的一个大坑。在Folx上下好模型后,我欣喜的关掉了那个贼慢的python程序(虽然显示已经下了一半了),一波美滋滋的拷贝之后,发现并没有卵用,它并没有识别我下好的文件,又重新开始request了。只能先让它慢悠悠的下着,看看下下来的东西是啥,然后去读读源码。

后来发现大概的流程是这样的,它会把文件先下到一个tmp文件夹中,然后复制到cache文件夹下。(但我已经把资源放到里面了啊,并无卵用!)在读代码的同时,tokenizer的东西下好了,经过代码和文件的双重验证:它会把链接名和一堆烂七八糟的tag加起来做MD5,然后作为文件名,过程很复杂,看不太懂。

经过再一次深入的阅读,我发现了它大概的流程:它会先判断 from_pretrained 函数的参数,如果是上文中 PRETRAINED_MODEL_ARCHIVE_MAP 已有的,就会去cache里找;如果不是,就会判断它是不是一个路径,会在这个路径下找需要的文件,一个config文件和一个bin文件,正好和我们下载的文件对应。

 1    """
2 Params:
3 pretrained_model_name: either:
4 - a str with the name of a pre-trained model to load selected in the list of:
5 . `bert-base-uncased`
6 . `bert-large-uncased`
7 . `bert-base-cased`
8 . `bert-base-multilingual`
9 . `bert-base-chinese`
10 - a path or url to a pretrained model archive containing:
11 . `bert_config.json` a configuration file for the model
12 . `pytorch_model.bin` a PyTorch dump of a BertForPreTraining instance
13 *inputs, **kwargs: additional input for the specific Bert class
14 (ex: num_labels for BertForSequenceClassification)
15 """
16

接下来的操作就简单了,直接贴代码:

1    UNCASED='./bert-base-uncased' # your path for model and vocab
2 VOCAB='bert-base-uncased-vocab.txt'
3 tokenizer=BertTokenizer.from_pretrained(os.path.join(UNCASED,VOCAB))
4 model = BertModel.from_pretrained(UNCASED)

需要注意的是,Tokenizer需要的是词表,我们看到的model需要的是文件,词表的链接如下:

1    PRETRAINED_VOCAB_ARCHIVE_MAP = {
2 'bert-base-uncased': "https://s3.amazonaws.com/models.huggingface.co/bert/bert-base-uncased-vocab.txt",
3 'bert-large-uncased': "https://s3.amazonaws.com/models.huggingface.co/bert/bert-large-uncased-vocab.txt",
4 'bert-base-cased': "https://s3.amazonaws.com/models.huggingface.co/bert/bert-base-cased-vocab.txt",
5 'bert-base-multilingual': "https://s3.amazonaws.com/models.huggingface.co/bert/bert-base-multilingual-vocab.txt",
6 'bert-base-chinese': "https://s3.amazonaws.com/models.huggingface.co/bert/bert-base-chinese-vocab.txt",
7 }

利用BERT获取词向量

作者其实在README中的 Usage给出了将句子转为词向量的demo,但是有点简略。同时,作者在extract_features.py中提供了详细的使用方法,接下来的内容就是对这里面的代码进行解读了。

因为Usage中的注释已经很详细了,所以接下来重点关注两个点:

  1. 如何批量处理(将文件中的句子转为词向量)
  2. 获得的词向量结构如何,是模型的哪个部分的输出

批量处理

1    layer_indexes = [int(x) for x in args.layers.split(",")]
2 tokenizer = BertTokenizer.from_pretrained(args.bert_model)
3 examples = read_examples(args.input_file)
4 features = convert_examples_to_features(
5 examples=examples, seq_length=args.max_seq_length, tokenizer=tokenizer)
6 unique_id_to_feature = {}
7 for feature in features:
8 unique_id_to_feature[feature.unique_id] = feature
9 model = BertModel.from_pretrained(args.bert_model)

其实处理的流程很简单,就是读入文件,然后将其转为需要的格式(InputFeatures类),然后利用模型进行处理。重点在于文件读取的函数中:

 1    def read_examples(input_file):
2 """Read a list of `InputExample`s from an input file."""
3 examples = []
4 unique_id = 0
5 with open(input_file, "r") as reader:
6 while True:
7 line = convert_to_unicode(reader.readline())
8 if not line:
9 break
10 line = line.strip()
11 text_a = None
12 text_b = None
13 m = re.match(r"^(.*) \|\|\| (.*)$", line)
14 if m is None:
15 text_a = line
16 else:
17 text_a = m.group(1)
18 text_b = m.group(2)
19 examples.append(
20 InputExample(unique_id=unique_id, text_a=text_a, text_b=text_b))
21 unique_id += 1
22 return examples

最关键的部分就是13行的那个正则表达式,模型中对于文本会分成两部分,part A 和 part B。在这个函数中,将两个部分的文本以“ ||| ” 分割(注意前后有空格),所以文件中的每一行应该是“I love you ||| Hello world”这样。

词向量结构

 1    all_encoder_layers, _ = model(input_ids, token_type_ids=None, attention_mask=input_mask)
2 all_encoder_layers = all_encoder_layers
3
4 for b, example_index in enumerate(example_indices):
5 feature = features[example_index.item()]
6 unique_id = int(feature.unique_id)
7 # feature = unique_id_to_feature[unique_id]
8 output_json = collections.OrderedDict()
9 output_json["linex_index"] = unique_id
10 all_out_features = []
11 for (i, token) in enumerate(feature.tokens):
12 all_layers = []
13 for (j, layer_index) in enumerate(layer_indexes):
14 layer_output = all_encoder_layers[int(layer_index)].detach().cpu().numpy()
15 layer_output = layer_output[b]
16 layers = collections.OrderedDict()
17 layers["index"] = layer_index
18 layers["values"] = [
19 round(x.item(), 6) for x in layer_output[i]
20 ]
21 all_layers.append(layers)
22 out_features = collections.OrderedDict()
23 out_features["token"] = token
24 out_features["layers"] = all_layers
25 all_out_features.append(out_features)
26 output_json["features"] = all_out_features
27 writer.write(json.dumps(output_json) + "\n")

19行得到的是一个list,长度为网络的层数,每个元素是[batch,sequence,embedding]的向量。

对于每个序列,序列中每个向量的token,分别获取它相应层(由参数arg.layers控制,本文中是最后四层)的编码。最后对于每个序列中的每个token会获取到[layer,dimension]大小的向量作为features。

BERT-Pytorch demo初探的更多相关文章

  1. ReactJS.NET 之 Demo 初探

    ReactJS.NET 是专对 .NET 平台开发者设计,让我们不只可以在前端去Render出页面,也可以在Server端去Render页面.开发 ReactJS 可以用一般JS 库,也可以通过类似X ...

  2. BERT(Bidirectional Encoder Representations from Transformers)

    BERT的新语言表示模型,它代表Transformer的双向编码器表示.与最近的其他语言表示模型不同,BERT旨在通过联合调节所有层中的上下文来预先训练深度双向表示.因此,预训练的BERT表示可以通过 ...

  3. 我爱自然语言处理bert ner chinese

    BERT相关论文.文章和代码资源汇总 4条回复 BERT最近太火,蹭个热点,整理一下相关的资源,包括Paper, 代码和文章解读. 1.Google官方: 1) BERT: Pre-training ...

  4. 桥接PyTorch和TVM

    桥接PyTorch和TVM 人工智能最引人入胜的一些应用是自然语言处理.像BERT或GPT-2之类的模型及其变体,可以获住足够多的文本信息. 这些模型属于称为Transformers的神经网络类体系结 ...

  5. 基于netty框架的Socket传输

    一.Netty框架介绍 什么是netty?先看下百度百科的解释:         Netty是由JBOSS提供的一个java开源框架.Netty提供异步的.事件驱动的网络应用程序框架和工具,用以快速开 ...

  6. 从网络架构方面简析循环神经网络RNN

    一.前言 1.1 诞生原因 在普通的前馈神经网络(如多层感知机MLP,卷积神经网络CNN)中,每次的输入都是独立的,即网络的输出依赖且仅依赖于当前输入,与过去一段时间内网络的输出无关.但是在现实生活中 ...

  7. 优质中文NLP资源集合,做项目一定用得到!

    今天要给大家在推荐 Github 上一个优质的中文 NLP 工具和资源集合项目——funNLP,已经获得了 5.3k Stars,1k+ Forks. 项目作者 杨洋,一枚水博&互联网民工,目 ...

  8. Amazon SageMaker和NVIDIA NGC加速AI和ML工作流

    Amazon SageMaker和NVIDIA NGC加速AI和ML工作流 从自动驾驶汽车到药物发现,人工智能正成为主流,并迅速渗透到每个行业.但是,开发和部署AI应用程序是一项具有挑战性的工作.该过 ...

  9. pytorch bert 源码解读

    https://daiwk.github.io/posts/nlp-bert.html 目录 概述 BERT 模型架构 Input Representation Pre-training Tasks ...

随机推荐

  1. day36 06-Hibernate抓取策略:set集合上的抓取策略

    你在做查询的时候它可以帮你关联出它的一些相应的关联对象.那么它关联这个对象的时候是在什么时候发送的这些语句以及它是如何把这些数据拿出来的? 知道延迟检索是怎么回事了,而且它也能够产生这个代理对象.当你 ...

  2. Adding basic files · lcobucci/jwt@aad22ed · GitHub

    Skip to content   Features Business Explore Marketplace Pricing   This repository Sign in or Sign up ...

  3. LintCode_181 将整数A转换为B

    题目 如果要将整数A转换为B,需要改变多少个bit位? 如把31转换为14,需要改变2个bit位. ()10=()2 ()10=()2 思路 要考虑负数的问题 如果 一正一负 将他们去全部变成正数 后 ...

  4. twisted(转)

    reactor.protocol 这两个类都在 twisted.internet 命名空间中 reactor对象是Twisted编程当中的第一步,它就是一个反应器,专门负责与服务端的连接以及监听与服务 ...

  5. C++学习笔记(2)---2.5 C++函数编译原理和成员函数的实现

    转载自:http://c.biancheng.NET/cpp/biancheng/view/2996.html点击打开链接 从上节的例子可以看出,对象的内存模型中只保留了成员变量,除此之外没有任何其他 ...

  6. C++ 实现十大排序算法

    教你手撕排序,这里有一个概念就是稳定排序.假定在待排序的记录序列中,存在多个具有相同的关键字的记录,若经过排序,这些记录的相对次序保持不变,即在原序列中,r[i]=r[j],且r[i]在r[j]之前, ...

  7. python 运算符重复

  8. 微信小程序分析见解

    前两天朋友圈都快被小程序给刷爆了: 对于小程序这方面,  由于没有公测的资格.所以翻阅了许许多多的资料,来了解一下小程序: 微信小程序: 小程序是一种不需要下载安装即可使用的应用,它实现了应用&quo ...

  9. python自定义函数和内置函数

    函数 1.定义 函数是组织好的,可重复使用的,用来实现单一,或相关联功能的代码段. 先定义,后使用 1.2分类 系统函数 自定义函数 1.3语法: def functionname(parameter ...

  10. Java Annotation入门

    Java Annotation入门作者:cleverpig 版权声明:本文可以自由转载,转载时请务必以超链接形式标明文章原始出处和作者信息及本声明作者:cleverpig(作者的Blog:http:/ ...