第一种场景,希望预测出来的类别有着不同的侧重点
这个自定义损失函数的背景:(一般回归用的损失函数是MSE, 但要看实际遇到的情况而有所改变)
我们现在想要做一个回归,来预估某个商品的销量,现在我们知道,一件商品的成本是1元,售价是10元。
如果我们用均方差来算的话,如果预估多一个,则损失一块钱,预估少一个,则损失9元钱(少赚的)。
显然,我宁愿预估多了,也不想预估少了。
所以,我们就自己定义一个损失函数,用来分段地看,当yhat 比 y大时怎么样,当yhat比y小时怎么样。(yhat沿用吴恩达课堂中的叫法)
import tensorflow as tf
from numpy.random import RandomState
batch_size = 8
# 两个输入节点
x = tf.placeholder(tf.float32, shape=(None, 2), name="x-input")
# 回归问题一般只有一个输出节点
y_ = tf.placeholder(tf.float32, shape=(None, 1), name="y-input")
# 定义了一个单层的神经网络前向传播的过程,这里就是简单加权和
w1 = tf.Variable(tf.random_normal([2, 1], stddev=1, seed=1))
y = tf.matmul(x, w1)
# 定义预测多了和预测少了的成本
loss_less = 10
loss_more = 1
#在windows下,下面用这个where替代,因为调用tf.select会报错
loss = tf.reduce_sum(tf.where(tf.greater(y, y_), (y - y_)*loss_more, (y_-y)*loss_less))
train_step = tf.train.AdamOptimizer(0.001).minimize(loss)
#通过随机数生成一个模拟数据集
rdm = RandomState(1)
dataset_size = 128
X = rdm.rand(dataset_size, 2)
"""
设置回归的正确值为两个输入的和加上一个随机量,之所以要加上一个随机量是
为了加入不可预测的噪音,否则不同损失函数的意义就不大了,因为不同损失函数
都会在能完全预测正确的时候最低。一般来说,噪音为一个均值为0的小量,所以
这里的噪音设置为-0.05, 0.05的随机数。
"""
Y = [[x1 + x2 + rdm.rand()/10.0-0.05] for (x1, x2) in X]
with tf.Session() as sess:
init = tf.global_variables_initializer()
sess.run(init)
steps = 5000
for i in range(steps):
start = (i * batch_size) % dataset_size
end = min(start + batch_size, dataset_size)
sess.run(train_step, feed_dict={x:X[start:end], y_:Y[start:end]})
print(sess.run(w1))
[[ 1.01934695]
[ 1.04280889]
最终结果如上面所示。
因为我们当初生成训练数据的时候,y是x1 + x2,所以回归结果应该是1,1才对。
但是,由于我们加了自己定义的损失函数,所以,倾向于预估多一点。
如果,我们将loss_less和loss_more对调,我们看一下结果:
[[ 0.95525807]
[ 0.9813394 ]]
通过这个例子,我们可以看出,对于相同的神经网络,不同的损失函数会对训练出来的模型产生重要的影响。
第二种应用场景,针对于样本不均衡的应用场景
自定义损失函数是损失函数章节的结尾,学习自定义损失函数,对于提高分类分割等问题的准确率很有帮助,同时探索新型的损失函数也可以让你文章多多。这里我们介绍构建自定义损失函数的方法,并且介绍可以均衡正负例的loss,以及在多分类中可以解决样本数量不均衡的loss的方法。
首先为了有足够的知识学会自定义损失函数,我们需要知道tensorflow都能实现什么样的操作。其实答案是你常见的数学运算都可以,所以说只要你能把心中的损失函数表达为数学式的形式,那么你就能够将其转变为损失函数的形式。下面介绍一些常见的函数:
四则运算:tf.add(Tensor1,Tensor2),tf.sub(Tensor1,Tensor2), tf.mul(Tensor1,Tensor2),tf.div(Tensor1,Tensor2)这里的操作也可以被正常的加减乘除的负号所取代。这里想要指出的是乘法和除法的规则和numpy库是一样的,是matlab中的点乘而不是矩阵的乘法,矩阵的乘法是tf.matmul(Tensor1, Tensor2)
基础运算: 取模运算(tf.mod()),绝对值(tf.abs()),平方(tf.square()),四舍五入取整(tf.round()),取平方根(tf.sqrt()) ,自然对数的幂(tf.exp()) ,取对数(tf.log()) ,幂(tf.pow()) ,正弦运算(tf.sin())。以上的这些数学运算以及很多没有被提及的运算在tensorflow中都可以自己被求导,所以大家不用担心还需要自己写反向传播的问题,只要你的操作是由tensorflow封装的基础操作实现的,那么反向传播就可以自动的实现。
条件判断,通过条件判断语句我们就可以实现分段的损失函数,利用tf.where(condition, tensor_x, tensor_y) 如果说条件condition是True那么就会返回tensor_x,如果是False则返回tensor_y。注:旧版本的tensorflow可以用tf.select实现这个操作。
比较操作,为了获得condition这个参数,我们可以用tf.greater( tensor_x, tensor_y)如果说tensor_x大于tensor_y则返回True。
tf.reduce_sum(),tf.reduce_mean()这两个操作是重要的loss操作,因为loss是一个数字,而通常计算得到的是一个高维的矩阵,因此用降维加法和降维取平均,可以将一个高维的矩阵变为一个数字。
有了上面的这些操作,我们就可以实现基本的损失函数的构建了,比如我们构建交叉熵损失函数:
cross_entropy = tf.reduce_mean(-tf.reduce_sum(y_ * tf.log(y)+(y_-1)* tf.log(1-y)))
再介绍一个均衡正负样本数量的loss,首先在训练数据中,通常来讲正例比较少,负例比较多,如疾病筛查中,有病的占少数而绝大部分人是健康的,这种数量不均衡的数据可能会让分类器倾向于将所有的示例都分为健康人,因为这样整体的准确率可能就能达到90%以上,为此,可以用调整loss权重的方式来缓解样本数量不均衡的问题,如:
pos_ratio=num_of_positive/num_all # 病人占总体的比例,较小如0.1
neg_ratio=num_of_negative/num_all # 正常人占总体的比例,较大如0.9
cross_entropy = tf.reduce_mean(-neg_ratio*tf.reduce_sum(y_ * tf.log(y)+pos_ratio*(y_-1)* tf.log(1-y)))
在这里我们给病人的损失项乘了一个较大的系数,使得一旦占少数的病人被错分为健康人的时候,代价就非常的大。同样的给正常人的损失项乘了一个较小的系数,使其诊断错误时对网络的影像较小。
这也符合实际情况,即使健康人在筛查时被通知可能患病,只要再进一步检查就可以。但是如果在筛查的时候将病人误分为健康人,那么付出的就可能是生命的代价了。
以上是二分类的例子,那么在多分类的时候应该如何做呢?我们也可以通过乘系数这样的方式解决问题,这里我们认为标签是one_hot形式的如:
class1_weight=0.2 # 第一类的权重系数
class2_weight=0.5 # 第二类的权重系数
class3_weight=0.3 # 第三类的权重系数
cross_entropy=tf.reduce_mean(-class1_weight*tf.reduce_sum(y_[:,0]*tf.log(y[:,0])
-class2_weight*tf.reduce_sum(y_[:,1] * tf.log(y[:,1])
-class3_weight*tf.reduce_sum(y_[:,2] * tf.log(y[:,2]))
因为标签和预测的结果都是one_hot的形式,因此在这里y[:,0]就是第一类的概率值,其中第一个维度的长度是minibatch的大小。同理y[:,0]就是第二类的概率值,我们在不同的项上乘上不同类别的权重系数,就可以一定程度上解决样本数量不均衡所带来的困扰。