自然语言处理之bert

一、简介

  • BERT(Bidirectional Encoder Representations from Transformers)是Google AI团队最新开源的NLP模型,正如家林大咖所言:这是2018年人工智能领域最重要的事件!对于技术人员而言,这是整个人工智能领域接下来五年最重要的机遇!
    BERT模型具有以下两个特点:
  • 第一,是这个模型非常的深,12层,并不宽(wide),中间层只有1024,而之前的Transformer模型中间层有2048。这似乎又印证了计算机图像处理的一个观点——深而窄比 浅而宽 的模型更好。
  • 第二,MLM(Masked Language Model),同时利用左侧和右侧的词语,这个在ELMo上已经出现了,绝对不是原创。其次,对于Mask(遮挡)在语言模型上的应用,已经被Ziang Xie提出了(我很有幸的也参与到了这篇论文中):[1703.02573] Data Noising as Smoothing in Neural Network Language Models。这也是篇巨星云集的论文:Sida Wang,Jiwei Li(香侬科技的创始人兼CEO兼史上发文最多的NLP学者),Andrew Ng,Dan Jurafsky都是Coauthor。但很可惜的是他们没有关注到这篇论文。用这篇论文的方法去做Masking,相信BRET的能力说不定还会有提升。
    如何理解BERT模型?BERT 要解决什么问题?
  • 通常情况 transformer 模型有很多参数需要训练。譬如 BERT BASE 模型: L=12, H=768, A=12,需要训练的模型参数总数是 12 * 768 * 12 = 110M。这么多参数需要训练,自然需要海量的训练语料。如果全部用人力标注的办法,来制作训练数据,人力成本太大。
  • 受《A Neural Probabilistic Language Model》论文的启发,BERT 也用 unsupervised 的办法,来训练 transformer 模型。神经概率语言模型这篇论文,主要讲了两件事儿,1. 能否用数值向量(word
    vector)来表达自然语言词汇的语义?2. 如何给每个词汇,找到恰当的数值向量?
  • 常用的中文汉字有 3500 个,这些字组合成词汇,中文词汇数量高达 50 万个。假如词向量的维度是 512,那么语言模型的参数数量,至少是 512 * 50万 = 256M 模型参数数量这么大,必然需要海量的训练语料。从哪里收集这些海量的训练语料?《A Neural Probabilistic Language Model》这篇论文说,每一篇文章,天生是训练语料。难道不需要人工标注吗?回答,不需要。
  • 我们经常说,“说话不要颠三倒四,要通顺,要连贯”,意思是上下文的词汇,应该具有语义的连贯性。基于自然语言的连贯性,语言模型根据前文的词,预测下一个将出现的词。如果语言模型的参数正确,如果每个词的词向量设置正确,那么语言模型的预测,就应该比较准确。天下文章,数不胜数,所以训练数据,取之不尽用之不竭。
  • 深度学习四大要素,1. 训练数据、2. 模型、3. 算力、4. 应用。训练数据有了,接下去的问题是模型。关于模型,BERT提出了五个关键词 Pre-training、Deep、Bidirectional、Transformer、Language Understanding 。
    BERT的五个关键词分别是什么意思?
  • 《A Neural Probabilistic Language Model》这篇论文讲的 Language Model,严格讲是语言生成模型(Language Generative Model),预测语句中下一个将会出现的词汇。语言生成模型能不能直接移用到其它 NLP 问题上去?譬如,淘宝上有很多用户评论,能否把每一条用户转换成评分?-2、-1、0、1、2,其中 -2 是极差,+2 是极好。假如有这样一条用户评语,“买了一件鹿晗同款衬衫,没想到,穿在自己身上,不像小鲜肉,倒像是厨师”,请问这条评语,等同于 -2,还是其它?
  • 语言生成模型,能不能很好地解决上述问题?进一步问,有没有 “通用的” 语言模型,能够理解语言的语义,适用于各种 NLP 问题?BERT 这篇论文的题目很直白,《BERT: Pre-training of Deep Bidirectional Transformers for Language Understanding》,一眼看去,就能猜得到这篇文章会讲哪些内容。
  • 这个题目有五个关键词,分别是 Pre-training、Deep、Bidirectional、Transformers、和 Language Understanding。其中 pre-training 的意思是,作者认为,确实存在通用的语言模型,先用文章预训练通用模型,然后再根据具体应用,用 supervised 训练数据,精加工(fine tuning)模型,使之适用于具体应用。为了区别于针对语言生成的 Language Model,作者给通用的语言模型,取了一个名字,叫语言表征模型 Language Representation Model。
  • 能实现语言表征目标的模型,可能会有很多种,具体用哪一种呢?作者提议,用 Deep Bidirectional Transformers 模型。假如给一个句子 “能实现语言表征[mask]的模型”,遮盖住其中“目标”一词。从前往后预测[mask],也就是用“能/实现/语言/表征”,来预测[mask];或者,从后往前预测[mask],也就是用“模型/的”,来预测[mask],称之为单向预测 unidirectional。单向预测,不能完整地理解整个语句的语义。于是研究者们尝试双向预测。把从前往后,与从后往前的两个预测,拼接在一起 [mask1/mask2],这就是双向预测 bi-directional。细节参阅《Neural Machine Translation by Jointly Learning to Align and Translate》。
  • BERT 的作者认为,bi-directional 仍然不能完整地理解整个语句的语义,更好的办法是用上下文全向来预测[mask],也就是用 “能/实现/语言/表征/…/的/模型”,来预测[mask]。BERT 作者把上下文全向的预测方法,称之为 deep bi-directional。如何来实现上下文全向预测呢?BERT 的作者建议使用 Transformer 模型。这个模型由《Attention Is All You Need》一文发明。
  • 这个模型的核心是聚焦机制,对于一个语句,可以同时启用多个聚焦点,而不必局限于从前往后的,或者从后往前的,序列串行处理。不仅要正确地选择模型的结构,而且还要正确地训练模型的参数,这样才能保障模型能够准确地理解语句的语义。BERT 用了两个步骤,试图去正确地训练模型的参数。第一个步骤是把一篇文章中,15% 的词汇遮盖,让模型根据上下文全向地预测被遮盖的词。假如有 1 万篇文章,每篇文章平均有 100 个词汇,随机遮盖 15% 的词汇,模型的任务是正确地预测这 15 万个被遮盖的词汇。通过全向预测被遮盖住的词汇,来初步训练 Transformer 模型的参数。然后,用第二个步骤继续训练模型的参数。譬如从上述 1 万篇文章中,挑选 20 万对语句,总共 40 万条语句。挑选语句对的时候,其中 2乘10万对语句,是连续的两条上下文语句,另外 2x10 万对语句,不是连续的语句。然后让 Transformer 模型来识别这 20 万对语句,哪些是连续的,哪些不连续。
  • 这两步训练合在一起,称为预训练 pre-training。训练结束后的 Transformer 模型,包括它的参数,是作者期待的通用的语言表征模型。
    BERT模型解析
  • BERT的新语言表示模型,它代表Transformer的双向编码器表示。与最近的其他语言表示模型不同,BERT旨在通过联合调节所有层中的上下文来预先训练深度双向表示。因此,预训练的BERT表示可以通过一个额外的输出层进行微调,适用于广泛任务的最先进模型的构建,比如问答任务和语言推理,无需针对具体任务做大幅架构修改。

二、pytorch版本的BERT分类代码

  • 先安装pytorch的BERT代码
 pip install pytorch-pretrained-bert

算法流程
自然语言处理之bert_第1张图片
读取数据
以输入数据为json文件时为例,数据读取模块包含两个部分:
基类DataProcessor:

class DataProcessor(object):		
    def get_train_examples(self, data_dir):
        raise NotImplementedError()

    def get_dev_examples(self, data_dir):
        raise NotImplementedError()

    def get_test_examples(self, data_dir):
        raise NotImplementedError()

    def get_labels(self):
        raise NotImplementedError()

    @classmethod
    def _read_json(cls, input_file, quotechar=None):
        """Reads a tab separated value file."""
        dicts = []
        with codecs.open(input_file, 'r', 'utf-8') as infs:
            for inf in infs:
                inf = inf.strip()
                dicts.append(json.loads(inf))
        return dicts

用于数据读取的模块MyPro:

class MyPro(DataProcessor):
    def get_train_examples(self, data_dir):
        return self._create_examples(
            self._read_json(os.path.join(data_dir, "train.json")), 'train')

    def get_dev_examples(self, data_dir):
        return self._create_examples(
            self._read_json(os.path.join(data_dir, "val.json")), 'dev')

    def get_test_examples(self, data_dir):
        return self._create_examples(
            self._read_json(os.path.join(data_dir, "test.json")), 'test')

    def get_labels(self):
        return [0, 1]

    def _create_examples(self, dicts, set_type):
        examples = []
        for (i, infor) in enumerate(dicts):
            guid = "%s-%s" % (set_type, i)
            text_a = infor['question']
            label = infor['label']
            examples.append(
                InputExample(guid=guid, text_a=text_a, label=label))
        return examples

说明:
data_dir目录下应包含名为train、val、test的三个文件,根据文件格式不同需要对读取方式进行修改
get_labels()返回的是所有可能的类别label_list,比如[‘数学’, ‘英语’, ‘语文’]、[1, 2, 3]…
模块最终返回一个名为examples的列表,每个列表元素中包含序号、中文文本、类别三个元素
特征转换
convert_examples_to_features是用于将examples转换为特征,也即features的函数。
features包含4个数据:

  • input_ids:分词后每个词语在vocabulary中的id,补全符号对应的id为0,[CLS]和[SEP]的id分别为101和102。应注意的是,在中文BERT模型中,中文分词是基于字而非词的分词。
  • input_mask:真实字符/补全字符标识符,真实文本的每个字对应1,补全符号对应0,[CLS]和[SEP]也为1。
  • segment_ids:句子A和句子B分隔符,句子A对应的全为0,句子B对应的全为1。但是在多数文本分类情况下并不会用到句子B,所以基本不用管。
  • label_id :将label_list中的元素利用字典转换为index标识,即
label_map = {}
for (i, label) in enumerate(label_list):
    label_map[label] = i

转换完成后的特征值就可以作为输入,用于模型的训练和测试

模型训练

  • 完成读取数据、特征转换之后,将特征送入模型进行训练,训练算法为BERT专用的Adam算法,训练集、测试集、验证集比例为6:2:2。
  • 每一个epoch后会在验证集上进行验证,并给出相应的f1值,如果f1值大于此前最高分则保存模型参数,否则flags加1。如果flags大于6,也即连续6个epoch模型的性能都没有继续优化,停止训练过程。
f1 = val(model, processor, args, label_list, tokenizer, device)
if f1 > best_score:
    best_score = f1
    print('*f1 score = {}'.format(f1))
    flags = 0
    checkpoint = {
        'state_dict': model.state_dict()
    }
    torch.save(checkpoint, args.model_save_pth)
else:
    print('f1 score = {}'.format(f1))
    flags += 1
    if flags >=6:
        break

具体代码参考:github

你可能感兴趣的:(自学)