本篇笔记将介绍损失函数与梯度,上一篇链接:https://blog.csdn.net/m0_65639009/article/details/145813564?fromshare=blogdetail&sharetype=blogdetail&sharerId=145813564&sharerefer=PC&sharesource=m0_65639009&sharefrom=from_link

批处理(这部分可跳过)

在上一篇文章中只是单个样本依次处理,这里设置一个batch_size,按照batch为单位进行处理。

def main():
    # 获取数据
    x_test, t_test = get_data()

    # 初始化网络
    network = init_network()

    # 对测试集中的每一张图片进行预测
    correct_count = 0

    batch_size = 100 # 批数量

    for i in range(0,len(x_test),batch_size):
        x_batch = x_test[i:i + batch_size]
        y_batch = predict(network, x_batch)
        p = np.argmax(y_batch, axis=1)
        correct_count += np.sum(p == t_test[i:i + batch_size])

    # 输出准确率
    accuracy = correct_count / len(x_test) * 100
    print(f"Test accuracy: {accuracy:.2f}%")
def get_data():
    (x_train, t_train), (x_test, t_test) = load_mnist(normalize=True, flatten=True, one_hot_label=False)
    t_test = np.array(t_test) #增加一行代码:确保t_test是数组,能和p匹配
    return x_test, t_test

特征量(这部分可跳过)

定义:“特征量”是指可以从输入数据(输入图像)中准确地提取本质数据(重要的数据)的转换器。

深度 学 习 有 时 也 称 为 端 到 端 机 器 学 习(end-to-end machine learning)。这里所说的端到端是指从一端到另一端的意思,也就是从原始数据(输入)中获得目标结果(输出)的意思。

通过不断地学习所提供的数据,尝试发现待求解的问题的模式。也就是说,与待处理的问题无关,神经网络可以将数据直接作为原始数据,进行“端对端”的学习。 

损失函数

神经网络的学习中所用的指标称为损失函数(loss function)。这个损失函数可以使用任意函数,
但一般用均方误差和交叉熵误差等。

损失函数是表示神经网络性能的“恶劣程度”的指标,即当前的神经网络对监督数据在多大程度上不拟合,在多大程度上不一致。

损失函数乘上一个负值,就可以解释为“在多大程度上不坏”,即“性能有多好”。并且,“使性能的恶劣程度达到最小”和“使性能的优良程度达到最大”是等价的。

def mean_squared_error(y, t):
    return 0.5 * np.sum((y-t)**2)

因此损失函数越大,性能越差。

def cross_entropy_error(y, t):
    delta = 1e-7
    return -np.sum(t * np.log(y + delta))

函数内部在计算 np.log 时,加上了一个微小值 delta。这是因为,当出现 np.log(0) 时,np.log(0) 会变为负无限大的 -inf,这样一来就会导致后续计算无法进行。

可能有人要问:“为什么要导入损失函数呢?”以数字识别任务为例,我们想获得的是能提高识别精度的参数,特意再导入一个损失函数不是有些重复劳动吗?也就是说,既然我们的目标是获得使识
别精度尽可能高的神经网络,那不是应该把识别精度作为指标吗?

对该权重参数的损失函数求导,表示的是“如果稍微改变这个权重参数的值,损失函数的值会如何变化”。如果导数的值为负,通过使该权重参数向正方向改变,可以减小损失函数的值;反过来,如果导数的值为正,则通过使该权重参数向负方向改变,可以减小损失函数的值。不过,当导数的值为 0 时,无论权重参数向哪个方向变化,损失函数的值都不会改变,此时该权重参数的更新会停在此处。

之所以不能用识别精度作为指标,是因为这样一来绝大多数地方的导数都会变为 0,导致参数无法更新。

损失函数关于参数值的变化是连续的,识别精度是离散的。

梯度

导函数

数值微分

梯度与偏导数

而成的向量称为梯度(gradient)。梯度可以像下面这样来实现

def numerical_gradient(f, x):
    h = 1e-4 # 0.0001
    grad = np.zeros_like(x) # 生成和x形状相同的数组
    for idx in range(x.size):
        tmp_val = x[idx]
        # f(x+h)的计算
        x[idx] = tmp_val + h
        fxh1 = f(x)

        #f(x-h)的计算
        x[idx] = tmp_val - h
        fxh2 = f(x)
        grad[idx] = (fxh1 - fxh2) / (2*h)
        x[idx] = tmp_val # 还原值
    return grad

函数 numerical_gradient(f, x) 的实现看上去有些复杂,但它执行的处理和求单变量的数值微分基本没有区别。需要补充说明一下的是,np.zeros_like(x) 会生成一个形状和 x相同、所有元素都为 0 的数组。函数 numerical_gradient(f, x) 中,参数 f 为函数,x 为 NumPy 数组,该函数对 NumPy 数组 x 的各个元素求数值微分。

下面,我们用 Python 来实现梯度下降法:

def gradient_descent(f, init_x, lr=0.01, step_num=100):
    x = init_x
    for i in range(step_num):
        grad = numerical_gradient(f, x)
        x -= lr * grad
    return x

参数 f 是要进行最优化的函数,init_x 是初始值,lr 是学习率 learningrate,step_num 是梯度法的重复次数。numerical_gradient(f,x) 会求函数的梯度,用该梯度乘以学习率得到的值进行更新操作,由 step_num 指定重复的次数。
使用这个函数可以求函数的极小值,顺利的话,还可以求函数的最小值。

梯度与导数的关系与区别-CSDN博客

作为导数到梯度的衔接,大家可以看看这篇文章。】

代码实战:

import numpy as np


# 定义目标函数 f(x0, x1) = x0^2 + x1^2
def f(x):
    return np.sum(x ** 2)  # 对数组 x 中的每个元素平方并求和


# 定义数值梯度函数
def numerical_gradient(f, x):
    h = 1e-4  # 步长(很小的值,用于计算数值梯度)
    grad = np.zeros_like(x)  # 创建一个与 x 形状相同的零数组,用于存储梯度
    for idx in range(x.size):  # 遍历每一个元素
        tmp_val = x[idx]  # 保存当前 x 的值

        # 计算 f(x+h)
        x[idx] = tmp_val + h
        fxh1 = f(x)

        # 计算 f(x-h)
        x[idx] = tmp_val - h
        fxh2 = f(x)

        # 使用中心差分法计算梯度
        grad[idx] = (fxh1 - fxh2) / (2 * h)
        x[idx] = tmp_val  # 恢复原始的 x 值
    return grad  # 返回梯度


# 定义梯度下降函数
def gradient_descent(f, init_x, lr=0.1, step_num=100):
    x = init_x  # 初始化 x 值
    for i in range(step_num):  # 迭代更新
        grad = numerical_gradient(f, x)  # 计算梯度
        x -= lr * grad  # 使用梯度更新 x 值,lr 是学习率
    return x  # 返回最优解


# 初始化 x 值并运行梯度下降
init_x = np.array([3.0, 4.0])  # 设置初始点 x0=3.0, x1=4.0
result = gradient_descent(f, init_x, lr=0.1, step_num=100)  # 执行梯度下降

print(result)  # 返回梯度下降得到的最小值对应的 x0 和 x1

运行结果:

这些值非常接近于原点 (0,0),说明梯度下降法成功找到了该函数的最小值,符合预期结果。

神经网络的梯度

在知道了目标函数(最小化误差函数),梯度计算方法后,怎么和前篇的神经网络呼应,求神经网络参数的梯度呢,本小节将对此做出介绍。

下面,我们以一个简单的神经网络为例,来实现求梯度的代码。为此,我们要实现一个名为 simpleNet 的类:

class simpleNet:
    def __init__(self):
        self.W = np.random.randn(2,3) # 用高斯分布进行初始化
    def predict(self, x):
        return np.dot(x, self.W)
    def loss(self, x, t):
        z = self.predict(x)
        y = softmax(z)
        loss = cross_entropy_error(y, t)
        return loss

它有两个方法,一个是用于预测的 predict(x),另一个是用于求损失函数值的 loss(x,t)。这里参数 x 接收输入数据,t接收正确解标签。现在我们来试着用一下这个 simpleNet。

# 创建 simpleNet 对象
net = simpleNet()

# 输出权重参数
print("权重参数 W:")
print(net.W)

# 输入一个样本
x = np.array([0.6, 0.9])

# 设置正确解标签(one-hot 编码)
t = np.array([0, 0, 1])

# 计算并输出损失值
loss = net.loss(x, t)
print("损失值:")
print(loss)

返回如下结果:

接下来求梯度。

#计算梯度
dW = numerical_gradient(net.loss(x, t), net.W)
print(dW)

完整代码:

import numpy as np
from learn2 import numerical_gradient

# Softmax 函数
def softmax(a):
    c = np.max(a)
    exp_a = np.exp(a - c)  # 溢出对策,减去最大值来防止溢出
    sum_exp_a = np.sum(exp_a)
    y = exp_a / sum_exp_a
    return y

# 交叉熵损失函数
def cross_entropy_error(y, t):
    delta = 1e-7  # 防止log(0)出现
    return -np.sum(t * np.log(y + delta))

# simpleNet 类
class simpleNet:
    def __init__(self):
        self.W = np.random.randn(2, 3)  # 用高斯分布初始化权重参数

    def predict(self, x):
        return np.dot(x, self.W)  # 使用权重参数进行预测

    def loss(self, x, t):
        z = self.predict(x)  # 计算预测值
        y = softmax(z)  # 计算softmax输出
        loss = cross_entropy_error(y, t)  # 计算交叉熵损失
        return loss  # 返回损失值


# 示例使用代码

# 创建 simpleNet 对象
net = simpleNet()

# 输出权重参数
print("权重参数 W:")
print(net.W)

# 输入一个样本
x = np.array([0.6, 0.9])

# 设置正确解标签(one-hot 编码)
t = np.array([0, 0, 1])

# 计算并输出损失值
loss = net.loss(x, t)
print("损失值:")
print(loss)

#计算梯度
dW = numerical_gradient(net.loss(x, t), net.W)
print(dW)

运行结果:

至此关于 神 经 网 络 学 习 的 基 础 知 识,到 这 里 就 全 部 介 绍 完 了。下一章将介绍完整的学习算法实现以及误差反向传播。

Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐