ITPub博客

首页 > IT职业 > IT职场 > keras自定义损失函数

keras自定义损失函数

IT职场 作者:liangyh 时间:2021-09-09 09:16:22 0 删除 编辑

Keras是一个搭积木式的深度学习框架,用它可以很方便且直观地搭建一些常见的深度学习模型。在tensorflow出来之前,Keras就已经几乎是当时最火的深度学习框架,以theano为后端,而如今Keras已经同时支持四种后端:theano、tensorflow、cntk、mxnet(前三种官方支持,mxnet还没整合到官方中),由此可见Keras的魅力。

Keras是很方便,然而这种方便不是没有代价的,最为人诟病之一的缺点就是灵活性较低,难以搭建一些复杂的模型。的确,Keras确实不是很适合搭建复杂的模型,但并非没有可能,而是搭建太复杂的模型所用的代码量,跟直接用tensorflow写也差不了多少。但不管怎么说,Keras其友好、方便的特性(比如那可爱的训练进度条),使得我们总有使用它的场景。这样,如何更灵活地定制Keras模型,就成为一个值得研究的课题了。这篇文章我们来关心自定义loss。

loss函数,如mse、交叉熵等,直接调用即可。而要自定义loss,最自然的方法就是仿照Keras自带的loss进行改写。

比如,我们做分类问题时,经常用的就是softmax输出,然后用交叉熵作为loss。然而这种做法也有不少缺点,其中之一就是分类太自信,哪怕输入噪音,分类的结果也几乎是非1即0,这通常会导致过拟合的风险,还会使得我们在实际应用中没法很好地确定置信区间、设置阈值。因此很多时候我们也会想办法使得分类别太自信,而修改loss也是手段之一。

如果不修改loss,我们就是使用交叉熵去拟合一个one hot的分布。交叉熵的公式是


其中是预测的分布,而是真实的分布,比如输出为,目标为,那么

只要已经是的最大值,那么我们总可以“变本加厉”——通过增大训练参数,使得增加足够大的比例(等价地,即增大向量的模长),从而足够接近1(等价地,loss足够接近0)。这就是通常softmax过于自信的来源:只要盲目增大模长,就可以降低loss,训练器肯定是很乐意了,这代价太低了。为了使得分类不至于太自信,一个方案就是不要单纯地去拟合one hot分布,分一点力气去拟合一下均匀分布,即改为新loss:

这样,盲目地增大比例使得接近于1,就不再是最优解了,从而可以缓解softmax过于自信的情况,不少情况下,这种策略还可以增加测试准确率(防止过拟合)。


那么,在Keras中应该怎么写呢?其实挺简单的:

from keras.layers import Input,Embedding,LSTM,Densefrom keras.models import Modelfrom keras import backend as K

word_size = 128nb_features = 10000nb_classes = 10encode_size = 64input = Input(shape=(None,))embedded = Embedding(nb_features,word_size)(input)encoder = LSTM(encode_size)(embedded)predict = Dense(nb_classes)(encoder)def mycrossentropy(y_true, y_pred, e=0.1):
    return (1-e)*K.categorical_crossentropy(y_pred,y_true) + e*K.categorical_crossentropy(y_pred, K.ones_like(y_pred)/nb_classes)model = Model(inputs=input, outputs=predict)model.compile(optimizer='adam', loss=mycrossentropy)

也就是自定义一个输入为y_pred,y_true的loss函数,放进模型compile即可。这里的mycrossentropy,第一项就是普通的交叉熵,第二项中,先通过K.ones_like(y_pred)/nb_classes构造了一个均匀分布,然后算y_pred与均匀分布的交叉熵。就这么简单~

《词向量与Embedding究竟是怎么回事?》。这里需要反复强调的是,虽然词向量叫Word Embedding,但是,Embedding层不是词向量,跟词向量没有半毛钱关系!!!不要有“怎么就跟词向量扯上关系了”这样的傻问题,Embedding层从来就没有跟词向量有过任何直接联系(只不过在训练词向量时可以用它)。对于Embedding层,你可以有两种理解:1、是one hot输入的全连接层的加速版本,也就是说,它就是一个以one hot为输入的Dense层,数学上完全等价;2、它就是一个矩阵查找操作,输入一个整数,输出对应下标的向量,只不过这个矩阵是可训练的。(你看,哪里跟词向量有联系了?)

这部分我们来关心center loss。前面已经说了,做分类时,一般是softmax+交叉熵做,用矩阵的写法,softmax就是


其中可以理解为提取的特征,而是最后的全连接层的权重,整个模型是一起训练的。问题是,这样的方案所训练出来的特征模型,具有怎样的形态呢?


有一些情况下,我们更关心特征而不是最后的分类结果,比如人脸识别场景,假如我们有10万个不同的人的人脸数据库,每个人有若干张照片,那么我们就可以训练一个10万分类模型,对于给定的照片,我们可以判断它是10万个中的哪一个。但这仅仅是训练场景,那么怎么应用呢?到了具体的应用环境,比如一个公司内部,可能有只有几百人;在公共安全检测场景,可能有数百万人,所以前面做好的10万分类模型基本上是没有意义的,但是在这个模型softmax之前的特征,也就是前一段所说的,可能还是很有意义的。如果对于同一个人(也就是同一类),基本一样,那么实际应用中,我们就可以把训练好的模型当作特征提取工具,然后把提取出来的特征直接用KNN(最邻近距离)来做就行了。

设想很美好,但事实很残酷,直接训练softmax的话,事实上得到的特征不一定具有聚类特性,相反,它们会尽量布满整个空间(没有给其他人留出位置,参考center loss的相关论文和文章,比如这篇。)。那么,怎样训练才使得结果有聚类特性呢?center loss使用了一种简单粗暴但是却很有效的方案——加聚类惩罚项。完整地写出来,就是


其中对应着正确的类别。可以看到,第一项就是普通的softmax交叉熵,第二项就是额外的惩罚项,它给每个类定义了可训练的中心,要求每个类要跟各自的中心靠得很近。所以,总的来说,第一项负责拉开不同类之间的距离,第二项负责缩小同一类之间的距离。


那么,Keras中要怎么实现这个方案?关键是,怎么存放聚类中心?答案就是Embedding层!这部分的开头已经提示了,Embedding就是一个待训练的矩阵罢了,正好可以存放聚类中心参数。于是,模仿第二部分的写法,就得到

from keras.layers import Input,Conv2D, MaxPooling2D,Flatten,Dense,Embedding,Lambdafrom keras.models import Modelfrom keras import backend as K

nb_classes = 100feature_size = 32input_image = Input(shape=(224,224,3))cnn = Conv2D(10, (2,2))(input_image)cnn = MaxPooling2D((2,2))(cnn)cnn = Flatten()(cnn)feature = Dense(feature_size, activation='relu')(cnn)predict = Dense(nb_classes, activation='softmax', name='softmax')(feature) #至此,得到一个常规的softmax分类模型input_target = Input(shape=(1,))centers = Embedding(nb_classes, feature_size)(input_target) #Embedding层用来存放中心l2_loss = Lambda(lambda x: K.sum(K.square(x[0]-x[1][:,0]), 1, keepdims=True), name='l2_loss')([feature,centers])model_train = Model(inputs=[input_image,input_target], outputs=[predict,l2_loss])model_train.compile(optimizer='adam', loss=['sparse_categorical_crossentropy',lambda y_true,y_pred: y_pred], loss_weights=[1.,0.2], metrics={'softmax':'accuracy'})model_predict = Model(inputs=input_image, outputs=predict)model_predict.compile(optimizer='adam', loss='sparse_categorical_crossentropy', metrics=['accuracy'])model_train.fit([train_images,train_targets], [train_targets,random_y], epochs=10)#TIPS:这里用的是sparse交叉熵,这样我们直接输入整数的类别编号作为目标,而不用转成one hot形式。所以Embedding层的输入,跟softmax的目标,都是train_targets,都是类别编号,而random_y是任意形状为(len(train_images),1)的矩阵。

读者可能有疑问,为什么不像第二部分的triplet loss模型那样,将整体的loss写成一个单一的输出,然后搭建模型,而是要像目前这样变成双输出呢?

事实上,Keras爱好者钟情于Keras,其中一个很重要的原因就是它的进度条——能够实时显示训练loss、训练准确率。如果像第二部分那样写,那么就不能设置metrics参数,那么训练过程中就不能显示准确率了,这不能说是一个小遗憾。而目前这样写,我们就依然能够在训练过程中看到训练准确率,还能分别看到交叉熵loss、l2_loss、总的loss分别是多少,非常舒服。

原文出处

来自 “ ITPUB博客 ” ,链接:https://blog.itpub.net/2260/viewspace-2801606/ ,如需转载,请注明出处,否则将追究法律责任。

请登录后发表评论 登录
全部评论

注册时间:2019-04-16

  • 博文量
    86
  • 访问量
    834