はじめに
tensorflow1では、keras-contribのcrf_lossとcrf_accuracyを使うことで、簡単に学習が可能でした。
tensorflow2以降、CRFを使った学習が分かりづらくなりました。
日本語での解説記事はほとんど見つかりませんでした。
tensorflow2では、lossとaccuracyを定義したモデルを自分で作る必要があるようです。
環境
tensorflow 2.4.1
tensorflow-addons 0.12.1
モデル
import tensorflow as tf
from tensorflow.keras import layers as L
from tensorflow_addons.layers.crf import CRF
from tensorflow.python.keras.engine import data_adapter
from tensorflow_addons.text.crf import crf_log_likelihood
class ModelWithCRFLoss(tf.keras.Model):
def __init__(self, output_names, *args, **kwargs):
self.__output_names = output_names
super().__init__( *args, **kwargs)
self._accuracy_func = [tf.keras.metrics.Accuracy(name=name + "_accuracy") for name in self.__output_names]
def crf_loss(self, y_true, y_pred):
viterbi_sequence, potentials, sequence_length, chain_kernel = y_pred
loss = -crf_log_likelihood(potentials, y_true, sequence_length, chain_kernel)[0] / tf.cast(sequence_length, tf.float32)
return viterbi_sequence, sequence_length, tf.reduce_mean(loss)
def compute_loss(self, x, y, sample_weight, training=False):
y_preds = self(x, training=training)
total_loss = 0.0
results = []
for y_true, pred in zip(y, y_preds):
viterbi_sequence, sequence_length, loss = self.crf_loss(y_true, pred)
total_loss += loss
results.append([viterbi_sequence, sequence_length, loss])
return results, total_loss
def train_step(self, data):
data = data_adapter.expand_1d(data)
x, y, sample_weight = data_adapter.unpack_x_y_sample_weight(data)
with tf.GradientTape() as tape:
results, total_loss = self.compute_loss(x, y, sample_weight, training=True)
gradients = tape.gradient(total_loss, self.trainable_variables)
self.optimizer.apply_gradients(zip(gradients, self.trainable_variables))
m = {"loss": total_loss}
for i in range(len(results)):
m[self.__output_names[i] + "_loss"] = results[i][-1]
for i in range(len(results)):
self._accuracy_func[i].update_state(y[i], results[i][0])
m[self._accuracy_func[i].name] = self._accuracy_func[i].result()
return m
def test_step(self, data):
data = data_adapter.expand_1d(data)
x, y, sample_weight = data_adapter.unpack_x_y_sample_weight(data)
with tf.GradientTape() as tape:
results, total_loss = self.compute_loss(x, y, sample_weight, training=False)
m = {"loss": total_loss}
for i in range(len(results)):
m[self.__output_names[i] + "_loss"] = results[i][-1]
for i in range(len(results)):
self._accuracy_func[i].update_state(y[i], results[i][0])
m[self._accuracy_func[i].name] = self._accuracy_func[i].result()
return m
def create_model():
input_layer = L.Input(shape=(None, 256))
x = L.Dense(256)(input_layer)
x = L.LSTM(256, return_sequences=True)(x)
x = L.Dense(256)(x)
x = CRF(13, name="out")(x)
# output_namesには、出力層の名前を指定します。
# 出力が複数のレイヤーの場合は、順序を間違えないようにしてください。
model = ModelWithCRFLoss(inputs=[input_layer], outputs=[x], output_names=["out"])
return model
Model: "model_with_crf_loss"
_________________________________________________________________
Layer (type) Output Shape Param #
=================================================================
input_1 (InputLayer) [(None, None, 256)] 0
_________________________________________________________________
dense (Dense) (None, None, 256) 65792
_________________________________________________________________
lstm (LSTM) (None, None, 256) 525312
_________________________________________________________________
dense_1 (Dense) (None, None, 256) 65792
_________________________________________________________________
out (CRF) [(None, None), (None, Non 3536
=================================================================
Total params: 660,434
Trainable params: 660,432
Non-trainable params: 2
_________________________________________________________________
使い方は、tf.keras.ModelをModelWithCRFLossに置き換えるだけです。
出力は2次元(batch、time_steps)になることに注意してください。
one-hotベクトルの (batch, time_steps, classes) は利用できません。
訓練
訓練は、通常のモデルと同じように行えます。
lossとaccuracyはモデルで定義されているので、compileではオプティマイザを指定するだけです。
from model import create_model
from tensorflow_addons.optimizers import RAdam
model = create_model()
optimizer = RAdam()
# optimizerだけを指定
model.compile(optimizer)
おわりに
tensorflow2でCRFを使ったトレーニングができるようになりました。
ですが、keras-contribのCRFに比べて倍近く速度が遅いです。
早く改善されることを期待してます。