寒假博客日记——第三天

keras多参数损失函数的写法

今天算是找到点写多参数自定义损失函数的思路了。一种比较灵活的方法是将层与loss结合。即自定义一个layer,让这个layer来计算损失函数,这样就可以将模型的中间变量给计算到损失函数里面。

下面是一个将中间变量计算到损失函数里的例子:

寒假博客日记——第三天_第1张图片

该模型用于进行猫狗分类。模型有三个输入,第一二个输入是原始图片、加了噪声的图片(只加了很微小的均匀噪声,其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

``

寒假博客日记——第三天_第2张图片

你可能感兴趣的:(寒假博客日记,深度学习,神经网络,python)