今天算是找到点写多参数自定义损失函数的思路了。一种比较灵活的方法是将层与loss结合。即自定义一个layer,让这个layer来计算损失函数,这样就可以将模型的中间变量给计算到损失函数里面。
下面是一个将中间变量计算到损失函数里的例子:
该模型用于进行猫狗分类。模型有三个输入,第一二个输入是原始图片、加了噪声的图片(只加了很微小的均匀噪声,其AGR值在0-2之间均匀分布),因为只是为了练习损失函数的自定义写法,所以就没仔细处理数据集了。第三个输入是数据的标签,由于是二分类,输出使用的是sigmoid的损失函数,仅有一个数值。
刚接触的时候可能会有疑惑,为什么要把标签传入到模型里面进行训练呢?
这是因为我们要使用模型的layer计算损失函数,如果不把标签传进模型,就没法计算交叉熵了。所以使用该方法自定义损失函数的第一个特点就出来了:必须要标签传入模型。
图中的model是卷积神经网络,里面包含了几个卷积层,最终会输出一个长度为512的向量。dense_1是整个模型的输出,模型的输出还要送入same_loss自定义层计算损失函数,损失函数由两部分组成,用于分类的交叉熵,以及用于增强泛化能力的KL散度,KL散度是指model输出的两个长度为512的向量之间的KL散度。一个向量来自原始图片,另一个向量来自加噪声的图片。这么做没有太多的道理,只是一个尝试,实际上结果也没有变得更好。
实现的代码如下:
首先是自定义的损失函数层:
class SameLoss(Layer):
def __init__(self,**kwargs):
super(SameLoss,self).__init__(**kwargs)
def call(self, inputs, *args, **kwargs):
"""
# inputs:Input tensor, or list/tuple of input tensors.
如上,父类KL.Layer的call方法明确要求inputs为一个tensor,或者包含多个tensor的列表/元组
所以这里不能直接接受多个入参,需要把多个入参封装成列表/元组的形式然后在函数中自行解包,否则会报错。
"""
# 解包入参
y_true,y_pred,y1,y2=inputs
#复杂的损失函数
loss_ce=keras.losses.binary_crossentropy(y_true,y_pred)
loss_kl=keras.losses.kl_divergence(y1,y2)
loss=loss_ce+loss_kl
accuracy=keras.metrics.binary_accuracy(y_true,y_pred)
# 重点:把自定义的loss添加进层使其生效,同时加入metric方便在KERAS的进度条上实时追踪
self.add_loss(loss,inputs=True)
self.add_metric(accuracy,'acc')
self.add_metric(loss_ce,'loss_ce')
self.add_metric(loss_kl,'loss_kl')
return loss
然后是模型的定义:
def my_model2(input_shape):
input1=Input(input_shape)
input2=Input(input_shape)
y=Input((1,))
x0=Input(input_shape)
x=BatchNormalization()(x0)
x=Conv2D(64,kernel_size=(3,3),activation='relu')(x)
x=MaxPooling2D()(x)
x=Conv2D(128,kernel_size=(3,3),activation='relu')(x)
x=MaxPooling2D()(x)
x=Conv2D(256,kernel_size=(3,3),activation='relu')(x)
x=MaxPooling2D()(x)
x=Flatten()(x)
x=Dense(512,'relu')(x)
cnn=Model(x0,x)
predict = Dense(1, 'sigmoid')
y1=cnn(input1)
y2=cnn(input2)
x=cnn(input1)
y_pred=predict(x)
loss_layer=SameLoss()([y,y_pred,y1,y2])
model=Model(inputs=[input1,input2,y],outputs=[y_pred,loss_layer])
model.compile(Adam())
return model
最后是模型的训练,input_2输入的是加噪声的图片,为了保证噪声在时刻变化,每训练10个epoch就变化一次数据(这里只加了0-0.5的均匀噪声,实际上非常微小)。
x_train,x_test,y_train,y_test=train_test_split(x,y,test_size=0.2,random_state=42)
print(x.shape)
model=my_model2(x.shape[1:])
model.summary()
plot_model(model,to_file="model.png",show_shapes=True,dpi=500)
y_train.reshape((-1,1))
for i in range(30):
x_train2 = 0.5 * np.random.random(x_train.shape) + x_train
model.fit([x_train,x_train2,y_train],[y_train,y_train],batch_size=256,validation_data=([x_test,x_test,y_test],[y_test,y_test]),epochs=10)
del x_train2
上面这个例子似乎没有起到任何优化的效果,下面改一下KL散度计算的时机,计算input_1的图片和input_2的图片所引起的预测值之间的散度,并将其加入到损失函数中。
class SameLoss(Layer):
def __init__(self,**kwargs):
super(SameLoss,self).__init__(**kwargs)
def call(self, inputs, *args, **kwargs):
"""
# inputs:Input tensor, or list/tuple of input tensors.
如上,父类KL.Layer的call方法明确要求inputs为一个tensor,或者包含多个tensor的列表/元组
所以这里不能直接接受多个入参,需要把多个入参封装成列表/元组的形式然后在函数中自行解包,否则会报错。
"""
# 解包入参
y_true,y1,y2=inputs
#复杂的损失函数
loss_ce1=keras.losses.binary_crossentropy(y_true,y1)
loss_ce2=keras.losses.binary_crossentropy(y_true,y2)
loss_kl=keras.losses.kl_divergence(y1,y2)
loss=loss_ce1+loss_ce2+loss_kl
accuracy=keras.metrics.binary_accuracy(y_true,y1)
# 重点:把自定义的loss添加进层使其生效,同时加入metric方便在KERAS的进度条上实时追踪
self.add_loss(loss,inputs=True)
self.add_metric(accuracy,'acc')
self.add_metric(loss_kl,'loss_kl')
return loss
模型的定义如下:
def my_model2(input_shape):
input1=Input(input_shape)
input2=Input(input_shape)
y=Input((1,))
x0=Input(input_shape)
x=BatchNormalization()(x0)
x=Conv2D(64,kernel_size=(3,3),activation='relu')(x)
x=MaxPooling2D()(x)
x=Conv2D(128,kernel_size=(3,3),activation='relu')(x)
x=MaxPooling2D()(x)
x=Conv2D(256,kernel_size=(3,3),activation='relu')(x)
x=MaxPooling2D()(x)
x=Flatten()(x)
x=Dense(512,'relu')(x)
predict=Dense(1,'sigmoid')(x)
cnn=Model(x0,predict)
y1=cnn(input1)
y2=cnn(input2)
loss_layer=SameLoss()([y,y1,y2])
model=Model(inputs=[input1,input2,y],outputs=[y1,loss_layer])
model.compile(Adam())
return model
input2)
loss_layer=SameLoss()([y,y1,y2])
model=Model(inputs=[input1,input2,y],outputs=[y1,loss_layer])
model.compile(Adam())
return model
``