keras自定义损失函数

匿名 (未验证) 提交于 2019-12-03 00:19:01

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

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

Keras的模型是函数式的,即有输入,也有输出,而loss即为预测值与真实值的某种误差函数。Keras本身也自带了很多loss函数,如mse、交叉熵等,直接调用即可。而要自定义loss,最自然的方法就是仿照Keras自带的loss进行改写。

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

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

S(q|p)=iqilogpi

其中pipi是预测的分布,而qiqi是真实的分布,比如输出为[z1,z2,z3][z1,z2,z3],目标为[1,0,0][1,0,0],那么
loss=log(ez1/Z),Z=ez1+ez2+ez3

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

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

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

from keras.layers import Input,Embedding,LSTM,Dense from keras.models import Model from keras import backend as K  word_size = 128 nb_features = 10000 nb_classes = 10 encode_size = 64  input = 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与均匀分布的交叉熵。就这么简单~

前面已经说了,Keras的模型有固定的输入和输出,并且loss即为预测值与真实值的某种误差函数,然而,很多模型并非这样的,比如问答模型与triplet loss。

这个的问题是指有固定的答案库的FAQ形式的问答。一种常见的做问答模型的方法就是:先分别将答案和问题都encode成为一个同样长度的向量,然后比较它们的\cos值,\cos越大就越匹配。这种做法很容易理解,是一个比较通用的框架,比如这里的问题和答案都不需要一定是问题,图片也行,反正只不过是encode的方法不一样,最终只要能encode出一个向量来即可。但是怎么训练呢?我们当然希望正确答案的\cos值越大越好,错误答案的\cos值越小越好,但是这不是必要的,合理的要求应该是:正确答案的\cos值比所有错误答案的\cos值都要大,大多少无所谓,一丁点都行。因此,这就导致了triplet loss:

loss=max(0,m+cos(q,Awrong)cos(q,Aright))

其中mm是一个大于零的正数。

怎么理解这个loss呢?要注意我们要最小化loss,所以只看m+cos(q,Awrong)cos(q,Aright)这部分,我们知道目的是拉大正确与错误答案的差距,但是,一旦cos(q,Aright)cos(q,Awrong)>m,也就是差距大于mm时,由于maxmax的存在,loss就等于0,这时候就自动达到最小值,就不会优化它了。所以,triplet loss的思想就是:只希望正确比错误答案的差距大一点(并不是越大越好),超过mm就别管它了,集中精力关心那些还没有拉开的样本吧

我们已经有问题和正确答案,错误答案只要随机挑就行,所以这样训练样本是很容易构造的。不过Keras中怎么实现triplet loss呢?看上去是一个单输入、双输出的模型,但并不是那么简单,Keras中的双输出模型,只能给每个输出分别设置一个loss,然后加权求和,但这里不能简单表示成两项的加权求和。那应该要怎么搭建这样的模型呢?下面是一个例子:

from keras.layers import Input,Embedding,LSTM,Dense,Lambda from keras.layers.merge import dot from keras.models import Model from keras import backend as K  word_size = 128 nb_features = 10000 nb_classes = 10 encode_size = 64 margin = 0.1  embedding = Embedding(nb_features,word_size) lstm_encoder = LSTM(encode_size)  def encode(input):     return lstm_encoder(embedding(input))  q_input = Input(shape=(None,)) a_right = Input(shape=(None,)) a_wrong = Input(shape=(None,)) q_encoded = encode(q_input) a_right_encoded = encode(a_right) a_wrong_encoded = encode(a_wrong)  q_encoded = Dense(encode_size)(q_encoded) #一般的做法是,直接讲问题和答案用同样的方法encode成向量后直接匹配,但我认为这是不合理的,我认为至少经过某个变换。  right_cos = dot([q_encoded,a_right_encoded], -1, normalize=True) wrong_cos         
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!