[英]Multioutput-Multiclass Classification in Custom Scratch Training in TF.Keras
我想從頭開始訓練多輸出和多類分類模型(使用自定義fit()
)。 我想要一些建議。 為了學習機會,我在這里更詳細地演示整個場景。 希望它可以對任何人有所幫助。
我正在使用這里的數據; 這是一個孟加拉手寫字符識別挑戰,每個樣本有3 個相互關聯的輸出以及每個的多個類。 請看下圖:
在上圖中,如您所見,ক্ট্রো 由 3 個組件(ক্ট 、 ো 、 ্র)組成,分別是字素根、元音變音符號和輔音變音符號,它們統稱為Graphe 。 同樣,字素根也有168 個不同的類別,也與其他類別相同( 11和7 )。 增加的復雜性導致了大約 13,000 個不同的字素變化(與英語的 250 個字素單位相比)。
目標是對每個圖像中字素的組件進行分類。
我在這里實現了一個訓練管道, 在那里它使用舊的keras
(不是tf.keras
)進行了演示,它具有一個方便的功能,例如model.compile
、 callbacks
等。我定義了一個自定義數據生成器並定義了一個類似於下面的模型架構。
input_tensor = Input(input_dim)
curr_output = base_model(input_tensor)
oputput1 = Dense(168, activation='softmax', name='gra') (curr_output)
oputput2 = Dense(11, activation='softmax', name='vow') (curr_output)
oputput3 = Dense(7, activation='softmax', name='cons') (curr_output)
output_tensor = [oputput1, oputput2, oputput3]
model = Model(input_tensor, output_tensor)
並編譯模型如下:
model.compile(
optimizer = Adam(learning_rate=0.001),
loss = {'gra' : 'categorical_crossentropy',
'vow' : 'categorical_crossentropy',
'cons': 'categorical_crossentropy'},
loss_weights = {'gra' : 1.0,
'vow' : 1.0,
'cons': 1.0},
metrics={'gra' : 'accuracy',
'vow' : 'accuracy',
'cons': 'accuracy'}
)
如您所見,我可以通過特定的loss
、 loss_weights
和accuracy
明確控制每個輸出。 並且使用.fit()
方法,可以為模型使用任何callbacks
函數。
現在,我想用tf.keras
的新功能重新實現它。 例如模型子類化和自定義擬合訓練。 但是,數據加載器沒有變化。 該模型定義如下:
def __init__(self, dim):
super(Net, self).__init__()
self.efnet = EfficientNetB0(input_shape=dim,
include_top = False,
weights = 'imagenet')
self.gap = KL.GlobalAveragePooling2D()
self.output1 = KL.Dense(168, activation='softmax', name='gra')
self.output2 = KL.Dense(11, activation='softmax', name='vow')
self.output3 = KL.Dense(7, activation='softmax', name='cons')
def call(self, inputs, training=False):
x = self.efnet(inputs)
x = self.gap(x)
y_gra = self.output1(x)
y_vow = self.output2(x)
y_con = self.output3(x)
return [y_gra, y_vow, y_con]
現在我面臨的主要問題是為我的每個輸出正確定義metrics
、 loss
和loss_weights
函數。 但是,我是這樣開始的:
optimizer = tf.keras.optimizers.Adam(learning_rate=0.05)
loss_fn = tf.keras.losses.CategoricalCrossentropy(from_logits=True)
train_acc_metric = tf.keras.metrics.Accuracy()
@tf.function
def train_step(x, y):
with tf.GradientTape(persistent=True) as tape:
logits = model(x, training=True) # Logits for this minibatch
train_loss_value = loss_fn(y, logits)
grads = tape.gradient(train_loss_value, model.trainable_weights)
optimizer.apply_gradients(zip(grads, model.trainable_weights))
train_acc_metric.update_state(y, logits)
return train_loss_value
for epoch in range(2):
# Iterate over the batches of the dataset.
for step, (x_batch_train, y_batch_train) in enumerate(train_generator):
train_loss_value = train_step(x_batch_train, y_batch_train)
# Reset metrics at the end of each epoch
train_acc_metric.reset_states()
除了上述設置之外,我還嘗試了其他多種方法來處理此類問題案例。 例如,我定義了 3 個損失函數和 3 個指標,但事情無法正常工作。 loss/acc
變成了nan
類型的東西。
在這種情況下,這是我的幾個直接查詢:
loss
、 metrics
和loss_weights
callbacks
功能只是為了學習機會,如果它有額外的回歸類型輸出(連同其余3 個多輸出,總共4 個); 如何對付它們都在定制fit
? 我訪問過這個SO ,為不同類型的輸出( classification
+ regression
)提供了一些提示。
你只需要做一個自定義的訓練循環,但一切都需要做 3 次(如果你還有一個連續變量,+ 1)。 這是使用四路輸出架構的示例:
import tensorflow as tf
import numpy as np
(xtrain, train_target), (xtest, test_target) = tf.keras.datasets.mnist.load_data()
# 10 categories, one for each digit
ytrain1 = tf.keras.utils.to_categorical(train_target, num_classes=10)
ytest1 = tf.keras.utils.to_categorical(test_target, num_classes=10)
# 2 categories, if the digit is odd or not
ytrain2 = tf.keras.utils.to_categorical((train_target % 2 == 0).astype(int),
num_classes=2)
ytest2 = tf.keras.utils.to_categorical((test_target % 2 == 0).astype(int),
num_classes=2)
# 4 categories, based on the interval of the digit
ytrain3 = tf.keras.utils.to_categorical(np.digitize(train_target, [3, 6, 8]),
num_classes=4)
ytest3 = tf.keras.utils.to_categorical(np.digitize(test_target, [3, 6, 8]),
num_classes=4)
# Regression, the square of the digit
ytrain4 = tf.square(tf.cast(train_target, tf.float32))
ytest4 = tf.square(tf.cast(test_target, tf.float32))
# train dataset
train_ds = tf.data.Dataset. \
from_tensor_slices((xtrain, ytrain1, ytrain2, ytrain3, ytrain4)). \
shuffle(32). \
batch(32).map(lambda a, *rest: (tf.divide(a[..., None], 255), rest)). \
prefetch(tf.data.experimental.AUTOTUNE)
# test dataset
test_ds = tf.data.Dataset. \
from_tensor_slices((xtest, ytest1, ytest2, ytest3, ytest4)). \
shuffle(32). \
batch(32).map(lambda a, *rest: (tf.divide(a[..., None], 255), rest)). \
prefetch(tf.data.experimental.AUTOTUNE)
# architecture
class Net(tf.keras.Model):
def __init__(self):
super(Net, self).__init__()
self.conv1 = tf.keras.layers.Conv2D(filters=16, kernel_size=(3, 3),
strides=(1, 1), input_shape=(28, 28, 1),
activation='relu')
self.maxp1 = tf.keras.layers.MaxPool2D(pool_size=(2, 2))
self.conv2 = tf.keras.layers.Conv2D(filters=32, kernel_size=(3, 3),
strides=(1, 1),
activation='relu')
self.maxp2 = tf.keras.layers.MaxPool2D(pool_size=(2, 2))
self.conv3 = tf.keras.layers.Conv2D(filters=64, kernel_size=(3, 3),
strides=(1, 1),
activation='relu')
self.maxp3 = tf.keras.layers.MaxPool2D(pool_size=(2, 2))
self.gap = tf.keras.layers.Flatten()
self.dense = tf.keras.layers.Dense(64, activation='relu')
self.output1 = tf.keras.layers.Dense(10, activation='softmax')
self.output2 = tf.keras.layers.Dense(2, activation='softmax')
self.output3 = tf.keras.layers.Dense(4, activation='softmax')
self.output4 = tf.keras.layers.Dense(1, activation='linear')
def call(self, inputs, training=False, **kwargs):
x = self.conv1(inputs)
x = self.maxp1(x)
x = self.conv2(x)
x = self.maxp2(x)
x = self.conv3(x)
x = self.maxp3(x)
x = self.gap(x)
x = self.dense(x)
out1 = self.output1(x)
out2 = self.output2(x)
out3 = self.output3(x)
out4 = self.output4(x)
return out1, out2, out3, out4
model = Net()
optimizer = tf.keras.optimizers.Adam(learning_rate=0.001)
# the three losses
loss_1 = tf.losses.CategoricalCrossentropy()
loss_2 = tf.losses.CategoricalCrossentropy()
loss_3 = tf.losses.CategoricalCrossentropy()
loss_4 = tf.losses.MeanAbsoluteError()
# mean object that keeps track of the train losses
loss_1_train = tf.metrics.Mean(name='tr_loss_1')
loss_2_train = tf.metrics.Mean(name='tr_loss_2')
loss_3_train = tf.metrics.Mean(name='tr_loss_3')
loss_4_train = tf.metrics.Mean(name='tr_loss_4')
# mean object that keeps track of the test losses
loss_1_test = tf.metrics.Mean(name='ts_loss_1')
loss_2_test = tf.metrics.Mean(name='ts_loss_2')
loss_3_test = tf.metrics.Mean(name='ts_loss_3')
loss_4_test = tf.metrics.Mean(name='ts_loss_4')
# accuracies for printout
acc_1_train = tf.metrics.CategoricalAccuracy(name='tr_acc_1')
acc_2_train = tf.metrics.CategoricalAccuracy(name='tr_acc_2')
acc_3_train = tf.metrics.CategoricalAccuracy(name='tr_acc_3')
# accuracies for printout
acc_1_test = tf.metrics.CategoricalAccuracy(name='ts_acc_1')
acc_2_test = tf.metrics.CategoricalAccuracy(name='ts_acc_2')
acc_3_test = tf.metrics.CategoricalAccuracy(name='ts_acc_3')
# custom training loop
@tf.function
def train_step(x, y1, y2, y3, y4):
with tf.GradientTape(persistent=True) as tape:
out1, out2, out3, out4 = model(x, training=True)
loss_1_value = loss_1(y1, out1)
loss_2_value = loss_2(y2, out2)
loss_3_value = loss_3(y3, out3)
loss_4_value = loss_4(y4, out4)
losses = [loss_1_value, loss_2_value, loss_3_value, loss_4_value]
# a list of losses is passed
grads = tape.gradient(losses, model.trainable_variables)
# gradients are applied
optimizer.apply_gradients(zip(grads, model.trainable_variables))
# losses are updated
loss_1_train(loss_1_value)
loss_2_train(loss_2_value)
loss_3_train(loss_3_value)
loss_4_train(loss_4_value)
# accuracies are updated
acc_1_train.update_state(y1, out1)
acc_2_train.update_state(y2, out2)
acc_3_train.update_state(y3, out3)
@tf.function
def test_step(x, y1, y2, y3, y4):
out1, out2, out3, out4 = model(x, training=False)
loss_1_value = loss_1(y1, out1)
loss_2_value = loss_2(y2, out2)
loss_3_value = loss_3(y3, out3)
loss_4_value = loss_4(y4, out4)
loss_1_test(loss_1_value)
loss_2_test(loss_2_value)
loss_3_test(loss_3_value)
loss_4_test(loss_4_value)
acc_1_test.update_state(y1, out1)
acc_2_test.update_state(y2, out2)
acc_3_test.update_state(y3, out3)
for epoch in range(5):
# train step
for inputs, outputs1, outputs2, outputs3, outputs4 in train_ds:
train_step(inputs, outputs1, outputs2, outputs3, outputs4)
# test step
for inputs, outputs1, outputs2, outputs3, outputs4 in test_ds:
test_step(inputs, outputs1, outputs2, outputs3, outputs4)
metrics = [acc_1_train, acc_1_test,
acc_2_train, acc_2_test,
acc_3_train, acc_3_test,
loss_4_train, loss_4_test]
# printing metrics
for metric in metrics:
print(f'{metric.name}:{metric.result():=6.4f}', end=' ')
print()
# resetting the states of the metrics
loss_1_train.reset_states()
loss_2_train.reset_states()
loss_3_train.reset_states()
loss_1_test.reset_states()
loss_2_test.reset_states()
loss_3_test.reset_states()
acc_1_train.reset_states()
acc_2_train.reset_states()
acc_3_train.reset_states()
acc_1_test.reset_states()
acc_2_test.reset_states()
acc_3_test.reset_states()
ts_acc_1:0.9495 ts_acc_2:0.9685 ts_acc_3:0.9589 ts_loss_4:5.5617
ts_acc_1:0.9628 ts_acc_2:0.9747 ts_acc_3:0.9697 ts_loss_4:4.8953
ts_acc_1:0.9697 ts_acc_2:0.9758 ts_acc_3:0.9733 ts_loss_4:4.5209
ts_acc_1:0.9715 ts_acc_2:0.9796 ts_acc_3:0.9745 ts_loss_4:4.2175
ts_acc_1:0.9742 ts_acc_2:0.9834 ts_acc_3:0.9775 ts_loss_4:3.9825
我不知道如何在自定義訓練循環中使用 Keras 回調,關於這個主題的最流行的問題也不知道。 如果您想使用 EarlyStopping,我個人使用collections.deque
,並在最小損失為倒數第 n 個時中斷。 下面是一個例子:
from collections import deque
import numpy as np
epochs = 100
early_stopping = 5
loss_hist = deque(maxlen=early_stopping)
for epoch in range(epochs):
loss_value = np.random.rand()
loss_hist.append(loss_value)
print('Last 5 values: ', *np.round(loss_hist, 3))
if len(loss_hist) == early_stopping and loss_hist.popleft() < min(loss_hist):
print('Early stopping. No loss decrease in %i epochs.\n' % early_stopping)
break
Last 5 values: 0.456
Last 5 values: 0.456 0.153
Last 5 values: 0.456 0.153 0.2
Last 5 values: 0.456 0.153 0.2 0.433
Last 5 values: 0.456 0.153 0.2 0.433 0.528
Last 5 values: 0.153 0.2 0.433 0.528 0.349
Early stopping. No loss decrease in 5 epochs.
可以看到,最后一次,最里面的值是最小的,所以驗證損失沒有增加。 這就是停止條件。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.