如何在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