如何在keras构造的分类模型中将bert预训练出的句子向量(两行代码即可得出)作为一部分输入加入模型

分三步走:

第一步:下载预训练好的bert模型并安装bert-as-service

1.首先需要先下载bert

git clone https://github.com/google-research/bert.git

2.然后下载好预训练好的bert模型

我做的是中文分类任务,所以在网址https://storage.googleapis.com/bert_models/2018_11_03/chinese_L-12_H-768_A-12.zip下载了预训练好的中文bert模型。

3.安装bert-as-service(直接将句子转成向量的必要操作)

pip install bert-serving-server # server
pip install bert-serving-client # client, independent of 'bert-serving-server'

第二步:利用bert将句子转成句子向量

1.启动服务

bert-serving-start -model_dir /media/ganjinzero/Code/bert/chinese_L-12_H-768_A-12 -num_worker=4

model_dir后面的参数是bert预训练模型所在的文件夹。num_worker的数量应该取决于你的CPU/GPU数量。

2.调用命令

from bert_serving.client import BertClient
bc = BertClient()
bc.encode(['注意此处将所有句子放到list中', '如果是多句话必须放入list中'])

将bc.encode([])打印出来即可看到句子向量,shape为(sents_num, 768)

前两步可以参考

第三步:已有句子向量如何加入现有的Keras模型中

1.设置接受句子向量的Input

先看Keras的Model类的API,参考https://keras.io/zh/models/model/

from keras.models import Model
from keras.layers import Input, Dense

a = Input(shape=(32,))
b = Dense(32)(a)
model = Model(inputs=a, outputs=b)

 

这个模型将包含从 a 到 b 的计算的所有网络层。

在多输入或多输出模型的情况下,你也可以使用列表:

model = Model(inputs=[a1, a2], outputs=[b1, b3, b3])

上面的a1可以视为你原有模型的输入,利用这个特性,可将句子向量作为a2传进来。

在我的真实代码中是这样的

input_layer = Input(shape=(sents_len,),dtype='int32', name='main_input')
bert_input_layer = Input(shape=(768,),dtype='float32', name='bert_input')
emb_layer = Embedding(max_features,
                      embedding_dims,
                      input_length=maxlen
                      )(input_layer)

其中Input是模型的输入,代码中用了两次Input()即可接收两个输入。其中input_layer在日常nlp任务中往往是一个id句子,即用id表示每个词,然后原来的汉语句子变成了id序列。input_layer的shape为(?,句子长度)之后再用input_layer进行lookup,即对应Keras中的Embedding()。

可以看到bert_input_layer的初始化Input的shape为768,这是因为bert转换每个句子默认向量长度为768,我们用bert_input_layer来接收句子向量。

2.在model.fit()函数中传入bert句子向量

可以还在这里看model.fit的API 参考https://keras.io/zh/models/model/

fit(x=None, y=None, batch_size=None, epochs=1, verbose=1, callbacks=None, validation_split=0.0, validation_data=None, shuffle=True, class_weight=None, sample_weight=None, initial_epoch=0, steps_per_epoch=None, validation_steps=None)
  • x: 训练数据的 Numpy 数组(如果模型只有一个输入), 或者是 Numpy 数组的列表(如果模型有多个输入)。 如果模型中的输入层被命名,你也可以传递一个字典,将输入层名称映射到 Numpy 数组。 如果从本地框架张量馈送(例如 TensorFlow 数据张量)数据,x 可以是 None(默认)。

从中我们可以看到在fit中传入input数据也可以通过传入list,将原始数据还有句子向量一起传入,在真实代码中是这样的

model.fit([X_train,np.array(sents['bert_train'])], y_train,
              batch_size=batch_size,
              nb_epoch=nb_epoch,
              callbacks=[checkpoint],
              validation_data=([X_val,np.array(sents['bert_val'])] ,y_val))

其中X_train即原始模型中训练集中所有句子对应的id序列,而np.array(sents['bert_train'])是我传入的训练集中所有句子向量组成的list再转成array。两者放入数组中共同作为x传入。

注意

在model.fit函数中的参数validation_data即验证集也要是[X_val,np.array(sents['bert_val'])]的格式来传入。

3.函数model.evaluate()的输入也要采用相同的格式传入x

真实代码中是这样的

score, acc = model.evaluate([X_test,np.array(sents)], y_test, batch_size=batch_size)

其中sents是放有n个句子向量的数组,shape为(n,769).

需要注意的是:

1.构造模型的Model(),

2.传入数据并开始训练的model.fit(),

3.model.fit()中作为参数传入的验证集validation_data,

4.预测结果的model.evaluate()中传入的x要保持一致,且和声明的Input()的数量一致且数据类型和shape要分别对应上,bert求出的句子向量默认为(sent_num,769),数据类型为float32