用numpy构建多种损失函数

吸一吸板鸭精神爽

友情链接

结合numpy及mnist库的简单神经网络演练
使用Numpy实现简单二层神经网络
实现二层神经网络反向传播
python实现梯度下降优化算法

写在前面

上篇通过numpy简单做了一个mnist的前馈神经网络的例子,这一次为了实现反馈神经网络的想法,就需要构建一个非常重要的函数——损失函数来衡量参数预测的准确度,进而通过梯度下降等方式训练参数缓缓达到损失函数的局部最小值,那么这接主要通过numpy函数来整理损失函数。
本节重点不是讲解理论而是实现,当然下面有理论的相关连接,想刨根问底的同学可以看一下。

损失函数介绍

损失函数和代价函数的基本含义一致,都是衡量神经网络预测结果好坏的基本标志,损失函数可以认为是衡量单个样本的预测结果误差;而代价函数则是整个预测结果的好坏程度,两者只是差在一个求和取平均的过程。但是基本思想即计算预测值与实际标志的差值,差值大则表示误差大,神经网络需要进一步增强训练,差值小则会渐进至局部最小值。

主要损失函数

均方误差(二次代价函数)

均方误差函数是比较容易理解的函数形式,其主要形式就是
二次代价函数基本形式
即对所有预测结果求差平方最终将其累加的过程,是ANN开始时比较适用的一种函数。
当然对这个函数有着更深的理解后就可以发现,其性能存在着缺陷:即如果初始权重与最终权重距离较大的话,对于sigmoid函数作为神经网络激活函数的网络中,其开始训练时趋近最终权重的速度会非常慢,即需要大量数据才能进行拟合;而如果开始时权重就比较接近,则拟合速度会快上很多。(如果想知道为什么的话看一下这篇博客的解释,写的很不错:损失函数理论解读

交叉熵代价函数

有了对二次代价函数改进的想法,自然也就出现了一个更为优秀的代价函数,交叉熵代价函数。
而在我写这篇博客的时候,我才发现交叉熵代价函数不只有一个,而是有两种交叉熵代价函数,其主要区别在于输出层的前一层是使用了softmax函数还是sigmoid函数,这两种函数的原理存在着比较大的差距:
(1)softmax输出的结果可以近似为概率形式,所以可以说其更像是所有分类预测遵从一个分布;所有的分类概率加起来为1的性质非常好。
(2)sigmoid多用于判断是否这样问题的场景中,如明天是否下雨、网络是否出现攻击这样的预测。sigmoid的输出则不满足softmax那样输出概率和为1的情况,其预测输出则是每一个分类遵从一个分布,即某分类大于0.5则说明可能的几率比较大,而小于0.5则说明可能的几率比较小,基本上可以理解为二项分布的形式,即神经元不断试验本类别出现次数,最终得出二项分布的概率预测值。
(这部分参考了这篇博客,写的虽然有些模糊,但是让我对其有了一定了解:比较两种交叉熵损失函数

softmax输出的交叉熵代价函数

softmax版代价函数
这种表示形式主要针对的是one-hot编码的yi标签值,如mnist库的简单one-hot举例标签为:
[0, 0, 0, 0, 0, 1, 0, 0 ,0 ,0],即表示编号5
可以看出,这个公式只关注one-hot中编码为1的分类的概率,其他类别的概率并不是特别关心,如果对应标签概率越接近1,则说明误差越小,非常容易理解。

sigmoid输出的交叉熵代价函数

sigmoid版代价函数
这个函数在上面分享的微博中有着非常明确的推导,其主要目的就是解决sigmoid函数配合二次代价函数的硬伤而开发的,非常巧妙,由于配合sigmoid函数进行使用,而sigmoid函数对于0.5这个临界值具有非常深的意义,如果对本代价函数图像有所了解的话,对于特定标签正反例数量基本一致的y0,y1,yi…ym,当所有a取到0.5左右时取得会误差最大值,而越接近0或1,其值越小也就是说通过信息论可以理解,这时的信息熵最大,信息是最为混乱的,所以要向着熵减的方向进行优化,即需要向着误差减小的方向进行。
从中可以发现一条有趣的现象,如果训练用例中标定状态1(或者状态0)的标签越多的话,其训练方向将会偏向状态1的方向,因为当极端情况训练样本全为状态1时,公式将消去后面一项,则ai越偏向1,则误差越小。
这可能就是当今神经网络使用交叉熵函数比较少的原因之一吧,虽然其解决了二次代价函数的拟合慢问题,但是对数据集是有一定要求的。

正题

知识点介绍的比较多,那么我们就来看一下如何更好的实现这些损失函数。
我们的测试集结果还是使用mnist数据集的形式,即0-9的10个分类(如果想了解mnist的一些操作,可以看我的这一篇博客:结合numpy及mnist库的简单神经网络演练
首先为了方便测试,先写一个用于生成测试数据的函数get_random_data()
此处使用 np.random.randint() 函数,三个参数分别为起始index、中止index,生成int的个数
有意思的是这个库中有一个 random_integers() 的函数,用途和randint一致,唯一区别为randint为[start,end)区间,而random_integers为[start, end]区间
其中_change_one_hot_label()为mnist.py中自带函数,用途即将数字label转为one-hot编码,在我的上一篇博客有提及源码。

# 用户生成测试结果以及测试标签集的函数
# count :测试集与标签集条数
# is_one_hot :标签集是否使用one-hot
def get_random_data(count, is_one_hot=False):
	random_results = []
	label_results = []
	for i in range(count):
		temp_result = np.random.randint(0,10,10)
		temp_sum = np.sum(temp_result)
		temp_result = temp_result/temp_sum
		# 默认选择其中概率最大值为标签值
		label_results.append(np.argmax(temp_result))
		random_results.append(temp_result)
	# 如果count为1,则需要将二维数组转换成一维数组
	if count == 1:
		random_results = random_results[0]
	if is_one_hot: 
		return np.array(random_results), _change_one_hot_label(np.array(label_results))
	return np.array(random_results), np.array(label_results)
		

其中的_change_one_hot_label看过实现后就知道其返回值必然是一个二维数组,不管传入结果条数是1条还是多条。

均方误差损失函数

在进行多分类预测的过程中,我们一般使用one-hot编码来作为计算损失函数的label,但是总会有特殊情况出现,下面我们将编写一个统配one-hot和数字label以及无论结果个数的损失函数
注意:将一维数组进行转置后会变成二维数组

def mean_squared_errors(results, labels):
	#如果results为一维,需要将向量形式进行转置
	if results.ndim == 1:
		results = results.reshape(1, results.shape[0])
	#此时如果两个维数不相等,则说明输入labels为数字形式,需要转成one-hot编码
	if not results.ndim == labels.ndim:
		labels = _change_one_hot_label(labels)
	batch_size = results.shape[0]
	return 0.5*np.sum((results-labels)**2)/batch_size
		

实现非常简单!

softmax为输出的交叉熵损失函数

从以上的函数形式来看,显然其计算方式希望输出为one-hot形式,我们依然可以使用和均方误差损失函数一样的判定,而也可以使用下面的这种形式:

# 交叉熵函数
def cross_entropy_error(results, labels):
	# 将一维数组转为二维数组
    if results.ndim == 1:
        results = results.reshape(1, results.shape[0])
     #log函数不能传入0值,所以需要给一个小数以至于不报错
    delta = 1e-7
    batch_size = results.shape[0]
    if results.ndim is labels.ndim:
    	# 将one-hot转换为数字labels
        labels = np.argmax(labels, axis=1)
      # 计算数字labels形式的结果
    return -np.sum(np.log(results[np.arange(batch_size), labels]+delta))/batch_size

其中使用了一个巧妙的数组取值方式:

results[np.arange(batch_size), labels]

我们假设两个数组,内容为a = [0,1,2]和b = [5,6,7],则results[a, b]就是取results[0,5],results[1,6],results[2,7]的值并组成数组(但是必须保证results为二维数组才可以这样使用)。

使用get_random_data()函数的测试结果为:

测试结果集: [0.08108108 0.         0.05405405 0.13513514 0.05405405 0.02702703
 0.         0.24324324 0.24324324 0.16216216]
标记结果集: [7]
误差: 1.4136929241969785
one-hot编码误差: 1.4136929241969785

ok的!

sigmoid为输出的交叉熵损失函数

既然介绍了上面的两种处理方式,现在最后一个就比较简单了,但是sigmoid的交叉熵损失函数从理论上与softmax的有所差别,所以上面的get_random_data并不适用了,并且这个交叉熵函数从理论上是并不能适用one-hot编码的,因为其基本适用场景基本为二分类,所以考虑的事情反而减少了。

def cross_entropy_error_anothor(results, labels):
	# 首先构造(1-y)部分
	trans_results = np.ones(results.shape,dtype=np.int)-results
	trans_labels = np.ones(labels.shape,dtype=np.int)-labels
	delta = 1e-7
	batch_size = trans_results.shape[0]
	return -np.sum(np.dot(labels,np.log(results+delta))+
		np.dot(trans_labels,np.log(trans_results+delta)))/batch_size

进行简单测试:

测试集: [0.5 0.6 0.7 0.8]
测试标签集: [1 0 0 1]
误差计算结果: 0.7591383399352568

完成!

这是最近大数据分析课的一些心得,如果以后有机会还会继续分享!
  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值