完整代码参见:https://github.com/tdaajames/aitest/blob/main/fine_tune.py
首先要准备一些fine tune用的训练样本,这个可以通过tensorflow dataset获得,也可以自己解析文本获得。
glue_train = bert_encode(glue['train'], tokenizer)
glue_train_labels = glue['train']['label']
然后,设定一系列的训练参数
epochs = 3
batch_size = 32
train_data_size = len(glue_train_labels)
steps_per_epoch = int(train_data_size / batch_size)
num_train_steps = steps_per_epoch * epochs
warmup_steps = int(epochs * train_data_size * 0.1 / batch_size)
# creates an optimizer with learning rate schedule
optimizer = nlp.optimization.create_optimizer(
2e-5, num_train_steps=num_train_steps, num_warmup_steps=warmup_steps)
metrics = [tf.keras.metrics.SparseCategoricalAccuracy('accuracy', dtype=tf.float32)]
loss = tf.keras.losses.SparseCategoricalCrossentropy(from_logits=True)
loss函数SparseCategoricalCrossentropy
Computes the crossentropy loss between the labels and predictions
https://www.tensorflow.org/api_docs/python/tf/keras/losses/SparseCategoricalCrossentropy
metrics:度量函数,计算预测正确率
Calculates how often predictions match integer labels.
https://www.tensorflow.org/api_docs/python/tf/keras/metrics/SparseCategoricalAccuracy
最最关键的,创建模型
def build_classifier_model():
class Classifier(tf.keras.Model):
def __init__(self, encoder_inputs, net, encoder):
super(Classifier, self).__init__(inputs=encoder_inputs, outputs=net, name="prediction")
self.encoder = encoder
def call(self, preprocessed_text, training=False):
x = super(Classifier, self).call(preprocessed_text)
return x
input_word_ids = tf.keras.layers.Input(
shape=(None,), dtype=tf.int32, name='input_word_ids')
input_mask = tf.keras.layers.Input(
shape=(None,), dtype=tf.int32, name='input_mask')
input_type_ids = tf.keras.layers.Input(
shape=(None,), dtype=tf.int32, name='input_type_ids')
encoder_inputs = {
'input_word_ids': input_word_ids,
'input_mask': input_mask,
'input_type_ids': input_type_ids
}
encoder = hub.KerasLayer(hub_url_bert, trainable=True)
outputs = encoder(encoder_inputs)
net = outputs['pooled_output']
net = tf.keras.layers.Dropout(0.1)(net)
net = tf.keras.layers.Dense(2, activation=None, name='classifier')(net)
tf.keras.Model()
return Classifier(encoder_inputs, net, encoder)
classifier_model = build_classifier_model()
这里我遇到了两个坑:
- 一开始使用完全的子类来定义模型,类似官方的定义了里面一系列行为,那么fit以后拿的模型,会自动对input shape进行一个自动长度设定,我遇到的是(None,79),这样相当于对输入向量限定了最大长度,模型用起来很不方便。
——
于是,我想办法将input shape设定成(None,None),发现定义encoder_inputs可以实现,但是,直接使用tf.keras.Model这个类来定义模型,那么训练出来模型后,我又无法访问里面的encoder,于是,我就通过扩展子类增加encoder,完全使用父类方法的这个方案来实现我既定义了input shape,又拿到了我想要的encoder方法。当然,我也尝试过其它像子类定义self.inputs来设定input shape的方法,但是,没有效果。
- 另外,还有一个注意的子类的call方法要定义个training的入参,def call(self, preprocessed_text, training=False),否则会报错。
最后,开始训练并保存模型
classifier_model = build_classifier_model()
classifier_model.compile(
optimizer=optimizer,
loss=loss,
metrics=metrics)
classifier_model.fit(
glue_train, glue_train_labels,
batch_size=32,
epochs=epochs)
export_dir = './exported_model'
tf.saved_model.save(classifier_model, export_dir=export_dir)