数字识别之---介绍损失函数
时间:2022-10-14 14:30:00
损失函数:
模型优化的目标是识别许多参数值中最理想的值。在训练过程的代码中计算损失函数,每轮模型训练过程相同,分为以下三个步骤:
1.首先根据输入数据预测输出
2.根据预测值和真实值计算损失
3.最后,根据损失反向传输梯度更新参数
损失函数的选择:
不同的深度学习任务需要有自己合适的损失函数。例如,在房价预测模型中,输入是13x来预测房价,即输出是y。这项任务显然属于回归任务。在手写数字识别任务中,输入28*28图像输出其标签,即0~9之间的10个整数属于分类任务。
使用均方误差作为分类任务的损失函数存在逻辑和效果上的欠缺。
在房价预测的情况下,由于房价本身是一个连续的实际值,模型输出值和实际价格差距被用作损失函数(LOSS)是符合道理的。但对于分类问题,真实结果是分类标签,而模型输出是实数值,导致两者相减作为损失不具备意义。
那么,分类任务的合理输出是什么呢?分类任务本质上是特征组合下的分类概率,以下是一个简单的案例描述。如图所示
图 :观测数据与背后规律的关系
在这种情况下,医生根据肿瘤x作为肿瘤性质y的参考判断(判断因素很多,肿瘤大小只是其中之一),因此我们观察到该模型的结果是x和y标签(1为恶性,0为良性)。该数据背后的规律是不同大小的肿瘤,属于恶性肿瘤的概率。观测数据是抽样真实规律的结果,分类模型应拟合真实规律,输出属于分类标签的概率。
Softmax函数:
如果模型能够输出10个标签的概率,则真实标签的概率输出应尽可能接近100%,而其他标签的概率输出应尽可能接近0%,所有输出概率之和应为1。这是一个更合理的假设!相应地,真实的标签值可以转换为10维one-hot在对应数字的位置上,向量为1,其余位置为0,如标签6可转换为[0、0、0、0、0、1、0、0、0、0]。
为了实现上述思路,需要引入Softmax函数可以将原始输出转换为相应标签的概率,公式如下CCC是标签类别的数量。
从公式的形式可以看出,每个输出的范围为0~1之间,所有输出之和等于1。对应于代码,我们需要在网络定义部分修改输出层:self.fc = FC(name_scope,size=10,act='softmax即全连接层FC加一个输出softmax运算。
下图使用了三个标签的分类模型softmax从输出层可以看出,原始输出的数字是3、1、-3,通过softmax三个概率值0转换为加和1.88、0.12.、0.
图 :网络输出层改为softmax函数
交叉熵
当模型输出是分类标签的概率时,直接使用标签和概率是不合适的,人们更习惯于使用交叉熵误差作为分类问题的损失衡量。
交叉熵的公式如下
其中,log\loglog表示以eee自然对数是底数。yky_kyk代表模型输出,tkt_ktk代表各个标签。tkt_ktk只有正确的标签是1,其余的是0(one-hot表示)。
因此,交叉熵只计算对应正确解标签输出的自然对数。例如,假设正确标签的索引是2.6.交叉熵误差为log0.6=0.51?\log 0.6 = 0.51?log0.6=0.51;如果2对应的输出为0.1.交叉熵误差为log0.1=2.30?\log 0.1 = 2.30?log0.1=2.30。由此可见,交叉熵误差的值是由正确标签对应的输出结果决定的。
以下代码可以实现自然对数的函数曲线。
import matplotlib.pyplot as plt import numpy as np x = np.arange(0.01,1,0.01) y = np.log(x) plt.title("y=log(x)") plt.xlabel("x") plt.ylabel("y") plt.plot(x,y) plt.show() plt.figure()
结果如下所示
如自然对数图所示,当x等于1时,y为0;随着x向0,y逐渐变小。因此,标签对应的输出越大,交叉熵值越接近0;当输出为1时,交叉熵误差为0。相反,标签对应的输出越小,交叉熵值越大。
实现交叉熵代码
需要注意的是,‘需要注意的是’
- 在读取数据部分,需要修改标签类型,并将标签类型设置为int,这反映了它是一个标签,而不是一个真实的值(桨框默认将标签处理成标签)Integrate4).
- 在网络定义部分,将输出层改为输出十个标签的概率模式
- 在训练过程中,将损失函数从均方误差转换为交叉熵
在数据处理部分,需要修改标签变量Label代码如下:
从label=np.reshape(labels[i],[1].astype('float32'))
到label=np.reshape(labels[i],[1].astype('int64'))
#从float32到int64 import os import random import paddle import paddle.fluid as fluid from paddle.fluid.dygraph.nn import Conv2D, Pool2D, Linear import numpy as np from PIL import Image import gzip import json # 定义数据集读取器 def load_data(mode='train'): # 数据文件 datafile = './work/mnist.json.gz' print('loading mnist dataset from {} ...'.format(datafile)) data = json.load(gzip.open(datafile)) train_set, val_set, eval_set = data # 数据集相关参数,图片高度IMG_ROWS, 图片宽度IMG_COLS IMG_ROWS = 28 IMG_COLS = 28 if mode == 'train': imgs = train_set[0] labels = train_set[1] elif mode == 'valid': imgs = val_set[0] labels = val_set[1] elif mode == 'eval': imgs = eval_set[0] labels = eval_set[1] imgs_length = len(imgs) assert len(imgs) == len(labels), \ "length of train_imgs({}) should be the same as train_labels({})".format( len(imgs), len(labels)) index_list = list(range(imgs_length)) # 读取数据时使用的batchsize BATCHSIZE = 100 # 定义数据生成器 def data_geerator():
if mode == 'train':
random.shuffle(index_list)
imgs_list = []
labels_list = []
for i in index_list:
img = np.reshape(imgs[i], [1, IMG_ROWS, IMG_COLS]).astype('float32')
label = np.reshape(labels[i], [1]).astype('int64')
imgs_list.append(img)
labels_list.append(label)
if len(imgs_list) == BATCHSIZE:
yield np.array(imgs_list), np.array(labels_list)
imgs_list = []
labels_list = []
# 如果剩余数据的数目小于BATCHSIZE,
# 则剩余数据一起构成一个大小为len(imgs_list)的mini-batch
if len(imgs_list) > 0:
yield np.array(imgs_list), np.array(labels_list)
return data_generator
在网络定义部分,需要修改输出层结构,代码如下:
从self.fc=FC(name_scope,size=1,act=None)
到self.fc=FC(name_scope,size=1,act='softmax')
# 定义模型结构
class MNIST(fluid.dygraph.Layer):
def __init__(self, name_scope):
super(MNIST, self).__init__(name_scope)
name_scope = self.full_name()
# 定义一个卷积层,使用relu激活函数
self.conv1 = Conv2D(num_channels=1, num_filters=20, filter_size=5, stride=1, padding=2, act='relu')
# 定义一个池化层,池化核为2,步长为2,使用最大池化方式
self.pool1 = Pool2D(pool_size=2, pool_stride=2, pool_type='max')
# 定义一个卷积层,使用relu激活函数
self.conv2 = Conv2D(num_channels=20, num_filters=20, filter_size=5, stride=1, padding=2, act='relu')
# 定义一个池化层,池化核为2,步长为2,使用最大池化方式
self.pool2 = Pool2D(pool_size=2, pool_stride=2, pool_type='max')
# 定义一个全连接层,输出节点数为10
self.fc = Linear(input_dim=980, output_dim=10, act='softmax')
# 定义网络的前向计算过程
def forward(self, inputs):
x = self.conv1(inputs)
x = self.pool1(x)
x = self.conv2(x)
x = self.pool2(x)
x = fluid.layers.reshape(x, [x.shape[0], 980])
x = self.fc(x)
return x
修改损失函数,从均方误差(常用于回归问题)到交叉熵(常用于分类问题),代码如下所示
从loss=fluid.layers.square_error_cost(predict,label)
到loss=fluid.layers.cross_entropy(predict,label)
#仅修改计算损失的函数,从均方误差(常用于回归问题)到交叉熵误差(常用于分类问题)
with fluid.dygraph.guard():
model = MNIST("mnist")
model.train()
#调用加载数据的函数
train_loader = load_data('train')
optimizer = fluid.optimizer.SGDOptimizer(learning_rate=0.01, parameter_list=model.parameters())
EPOCH_NUM = 5
for epoch_id in range(EPOCH_NUM):
for batch_id, data in enumerate(train_loader()):
#准备数据,变得更加简洁
image_data, label_data = data
image = fluid.dygraph.to_variable(image_data)
label = fluid.dygraph.to_variable(label_data)
#前向计算的过程
predict = model(image)
#计算损失,使用交叉熵损失函数,取一个批次样本损失的平均值
loss = fluid.layers.cross_entropy(predict, label)
avg_loss = fluid.layers.mean(loss)
#每训练了200批次的数据,打印下当前Loss的情况
if batch_id % 200 == 0:
print("epoch: {}, batch: {}, loss is: {}".format(epoch_id, batch_id, avg_loss.numpy()))
#后向传播,更新参数的过程
avg_loss.backward()
optimizer.minimize(avg_loss)
model.clear_gradients()
#保存模型参数
fluid.save_dygraph(model.state_dict(), 'mnist')