bert4keras快速使用以及对抗训练

文章目录

  • 1. bert4keras快速上手
  • 2.对抗训练

1. bert4keras快速上手

下面是一个调用bert base模型来编码句子的简单例子:

from bert4keras.models import build_transformer_model
from bert4keras.tokenizers import Tokenizer
import numpy as np

config_path = '/root/kg/bert/chinese_L-12_H-768_A-12/bert_config.json'
checkpoint_path = '/root/kg/bert/chinese_L-12_H-768_A-12/bert_model.ckpt'
dict_path = '/root/kg/bert/chinese_L-12_H-768_A-12/vocab.txt'

tokenizer = Tokenizer(dict_path, do_lower_case=True)  # 建立分词器
model = build_transformer_model(config_path, checkpoint_path)  # 建立模型,加载权重

# 编码测试
token_ids, segment_ids = tokenizer.encode(u'语言模型')

print('\n ===== predicting =====\n')
print(model.predict([np.array([token_ids]), np.array([segment_ids])]))

这个例子虽然简短,但事实上已经包含了在Keras中使用Bert模型的完整流程。当model = build_transformer_model(config_path, checkpoint_path)这一步成功执行后,一个基于Keras的Bert模型就已经搭建完毕,剩下的就是Keras的使用了。

从例子可以看出,其实代码分为两部分:第一部分是tokenizer的建立,bert4keras.tokenizers里面包含了对原版Bert的tokenizer的完整复现,同时还补充了一些常用功能;第二部分就是bert模型的建立,其主要函数是build_transformer_model。其定义如下:

def build_transformer_model(
    config_path=None,  # 模型的配置文件(对应的文件为json格式)
    checkpoint_path=None,  # 模型的预训练权重(tensorflow的ckpt格式)
    model='bert',  # 模型的类型(bert、albert、albert_unshared、nezha、electra、gpt2_ml、t5)
    application='encoder',  # 模型的用途(encoder、lm、unilm)
    return_keras_model=True,  # 返回Keras模型,还是返回bert4keras的模型类
    **kwargs  # 其他传递参数

2.对抗训练

github地址:https://github.com/bojone/bert4keras/blob/master/examples/task_iflytek_adversarial_training.py
当前,说到深度学习中的对抗,一般会有两个含义:一个是生成对抗网络(Generative Adversarial Networks,GAN),代表着一大类先进的生成模型;另一个则是跟对抗攻击、对抗样本相关的领域,它跟GAN相关,但又很不一样,它主要关心的是模型在小扰动下的稳健性

  • 对抗攻击:其实就是想办法造出更多的对抗样本
  • 对抗防御:就是想办法让模型能正确识别更多的对抗样本。
    • 对抗训练,则是属于对抗防御的一种,它构造了一些对抗样本加入到原数据集中,希望增强模型对对抗样本的鲁棒性;同时,如本文开篇所提到的,在NLP中它通常还能提高模型的表现。
      bert4keras快速使用以及对抗训练_第1张图片

那么,什么样的样本才是好的对抗样本呢?对抗样本一般需要具有两个特点:(Min-Max)

  • 相对于原始输入,所添加的扰动是微小的(min);
  • 能最大可能地使模型犯错(max)。
    公式:
    bert4keras快速使用以及对抗训练_第2张图片
    其中D代表训练集,x代表输入,y代表标签,θ是模型参数,L(x,y;θ)是单个样本的loss,Δx是对抗扰动,Ω是扰动空间。这个统一的格式首先由论文《Towards Deep Learning Models Resistant to Adversarial Attacks》提出。

2.2 在Embedding层进行对抗扰动
思路分析:
对于CV任务来说,一般输入张量的shape是(b, h, w, c),这时候我们需要固定模型的batch size,即b,然后给原始输入加上一个shape同样为(b, h, w, c)、全零初始化的Variable,比如就叫做 Δ x Δx Δx,那么我们可以直接求loss对x的梯度,然后根据梯度给 Δ x Δx Δx赋值,来实现对输入的干扰,完成干扰之后再执行常规的梯度下降。
对于nlp任务来说,原则上也要对Embedding层的输出进行同样的操作,Embedding层的输出shape为(b, n, d),所以也要在Embedding层的输出加上一个shape为(b, n, d)的Variable,然后进行上述步骤,但需要拆解重构模型,对使用者不够友好。
退而求其次。Embedding层的输出是直接取自于Embedding参数矩阵的,因此我们可以直接对Embedding参数矩阵进行扰动。这样得到的对抗样本的多样性会少一些(因为不同样本的同一个token共用了相同的扰动)。但仍能起到正则化的作用,实现起来容易得多。
核心代码:

def adversarial_training(model, embedding_name, epsilon=1):
    """给模型添加对抗训练
    其中model是需要添加对抗训练的keras模型,embedding_name
    则是model里边Embedding层的名字。要在模型compile之后使用。
    """
    if model.train_function is None:  # 如果还没有训练函数
        model._make_train_function()  # 手动make
    old_train_function = model.train_function  # 备份旧的训练函数

    # 查找Embedding层
    for output in model.outputs:
        embedding_layer = search_layer(output, embedding_name)
        if embedding_layer is not None:
            break
    if embedding_layer is None:
        raise Exception('Embedding layer not found')

    # 求Embedding梯度
    embeddings = embedding_layer.embeddings  # Embedding矩阵
    gradients = K.gradients(model.total_loss, [embeddings])  # Embedding梯度
    gradients = K.zeros_like(embeddings) + gradients[0]  # 转为dense tensor

    # 封装为函数
    inputs = (model._feed_inputs +
              model._feed_targets +
              model._feed_sample_weights)  # 所有输入层
    embedding_gradients = K.function(
        inputs=inputs,
        outputs=[gradients],
        name='embedding_gradients',
    )  # 封装为函数

    def train_function(inputs):  # 重新定义训练函数
        grads = embedding_gradients(inputs)[0]  # Embedding梯度
        delta = epsilon * grads / (np.sqrt((grads**2).sum()) + 1e-8)  # 计算扰动
        K.set_value(embeddings, K.eval(embeddings) + delta)  # 注入扰动
        outputs = old_train_function(inputs)  # 梯度下降
        K.set_value(embeddings, K.eval(embeddings) - delta)  # 删除扰动
        return outputs

    model.train_function = train_function  # 覆盖原训练函数

定义好上述函数后,给Keras模型增加对抗训练就只需要一行代码了:

# 写好函数后,启用对抗训练只需要一行代码
adversarial_training(model, 'Embedding-Token', 0.5)

以上内容来自:

【炼丹技巧】功守道:NLP中的对抗训练 + PyTorch实现
对抗训练浅谈:意义、方法和思考(附Keras实现)

你可能感兴趣的:(NLP)