前言:这不是一个完整的笔记记录,偏向于有一定基础,通过浏览笔记建立认知,这里记录的都是我认为学完这一章之后应该掌握的理论,核心是读懂代码。
第4章 神经网络的学习
4.1 前置科普
“学习”是指从训练数据中 自动获取最优权重参数的过程。前几页科普神经网络和别的方法的不同之处(无需人工设计特征或规则,直接从数据中自动学习权重参数,实现 “端到端” 学习) 数据划分为训练数据和测试数据
-
数据划分目的:避免过拟合,确保模型的泛化能力(处理未见过的数据的能力)。
-
训练数据:用于学习权重参数,让模型拟合数据规律。
-
测试数据:仅用于评估模型性能,不参与参数更新,判断模型是否仅 “记住” 训练数据。
4.2 损失函数
t是监督数据,y是神经网络的输出
一共介绍了这两种lossfuction,MSE好理解,交叉熵的交叉是真实与预测的交叉,熵是衡量不确定性的指标,交叉熵也是衡量匹配度的
mini——btach:比如,从 60000 个训练数据中随机 选择 100 笔,再用这 100 笔数据进行学习。这种学习方式称为 mini-batch 学习。 再来一个简答
简答题:为什么神经网络的学习不能用 “识别精度” 作为指标,而需要引入 “损失函数”?
参考答案:
- 识别精度是离散值,梯度多数情况下为 0:识别精度仅能表示 “正确分类样本数 / 总样本数”(如 32%、33%),微小调整参数时,精度大概率不变(比如从 32% 到 32%),导致参数梯度为 0,无法指导参数更新。
- 损失函数是连续值,梯度始终有效:损失函数(如均方误差、交叉熵误差)能输出连续的实数值(如 0.92、0.59),参数微小变化会导致损失值连续波动,梯度不会恒为 0,可通过梯度下降法持续调整参数以减小误差。
- 激活函数的兼容性要求:神经网络使用 sigmoid、ReLU 等平滑激活函数,需搭配连续的损失函数才能发挥梯度传递作用;若用离散的识别精度,即便激活函数平滑,参数变化也无法通过梯度反馈,学习会停滞。
- 核心结论:损失函数的连续性确保了梯度始终有意义,是参数更新的 “导航标”,而识别精度的离散性导致无法提供有效梯度,因此必须用损失函数作为学习指标。
4.3 微分求导略
4.4 梯度
神经网络中的梯度是损失函数对参数(权重、偏置)的偏导数组成的向量,它给出参数变化时损失变化的最快方向和最大速率。训练时沿负梯度方向更新参数,让损失变小(梯度下降)。
书里给了微分方法求梯度的代码
# coding: utf-8
import sys, os
sys.path.append(os.pardir) # 为了导入父目录中的文件而进行的设定
import numpy as np
from common.functions import softmax, cross_entropy_error
from common.gradient import numerical_gradient
# 定义一个简单的神经网络类(无隐藏层,仅一层权重)
class simpleNet: # 类的构造函数,创建实例时自动执行
def __init__(self): # 初始化权重参数W:生成2行3列的随机数(服从标准正态分布)
# 2行对应输入x的维度(2维),3列对应输出的类别数(3类)
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
# 定义输入数据:2维向量(对应权重W的2行)
x = np.array([0.6, 0.9])
# 定义真实标签:one-hot编码(3维向量,只有第3个位置为1,表示正确类别是第3类)
t = np.array([0, 0, 1])
net = simpleNet()
f = lambda w: net.loss(x, t)
dW = numerical_gradient(f, net.W)
print(dW)
这个程序最后两句话我一直没看懂,后来发现是因为我略过了微分求导部分,另一个程序中定义了微分计算,如下所示:
也就是说,定义的数值梯度函数需要两个参数,一个f,一个x,f可以实现f(x),所以f在上面的程序中是f = lambda w: net.loss(x, t) 给了一个占位符w,满足 numerical_gradient 的调用格式。
def numerical_gradient(f, x):
h = 1e-4 # 0.0001
grad = np.zeros_like(x)
it = np.nditer(x, flags=['multi_index'], op_flags=['readwrite']) #迭代取出x中的元素
while not it.finished: #没取完就一直执行下面的程序
idx = it.multi_index #获取当前元素的多级索引
tmp_val = x[idx] #保存当前位置的原始值(避免修改后影响后续计算)
x[idx] = float(tmp_val) + h
fxh1 = f(x) # f(x+h)
x[idx] = tmp_val - h
fxh2 = f(x) # f(x-h)
grad[idx] = (fxh1 - fxh2) / (2*h)
x[idx] = tmp_val # 还原值
it.iternext()
return grad
如果看不懂别细究,知道这个代码定义了数值微分。上一个代码定了f的表达式和对什么求导
mini-batch
上面说的是随机梯度下降(SGD),是输入一个x然后更新一次梯度计算太复杂 于是有了mini-batch,拿一批数据算一次梯度,遍历所有数据,算一次epoch。实际上会进行多轮训练,直到损失不再下降、模型收敛。
# coding: utf-8
import sys, os
sys.path.append(os.pardir) # 为了导入父目录的文件而进行的设定
import numpy as np
import matplotlib.pyplot as plt
from dataset.mnist import load_mnist
from two_layer_net import TwoLayerNet
# 读入数据
(x_train, t_train), (x_test, t_test) = load_mnist(normalize=True, one_hot_label=True)
network = TwoLayerNet(input_size=784, hidden_size=50, output_size=10)
'''- `input_size=784`:输入层神经元数量(对应 MNIST 图片展平后的维度);
- `hidden_size=50`:隐藏层神经元数量(超参数,可调整);
- `output_size=10`:输出层神经元数量(对应 10 个数字分类);'''
iters_num = 10000 # 适当设定循环的次数
train_size = x_train.shape[0] # 训练集样本总数:60000
batch_size = 100 # 每次随机抽取的批量样本数(小批量梯度下降)
learning_rate = 0.1
train_loss_list = []
train_acc_list = []
test_acc_list = []
iter_per_epoch = max(train_size / batch_size, 1) # 每个epoch需要的迭代次数:60000/100=600
for i in range(iters_num):
batch_mask = np.random.choice(train_size, batch_size) #生成随机索引
x_batch = x_train[batch_mask]
t_batch = t_train[batch_mask]
# 计算梯度
#grad = network.numerical_gradient(x_batch, t_batch)
grad = network.gradient(x_batch, t_batch)
# 更新参数
for key in ('W1', 'b1', 'W2', 'b2'): #这里写一个循环,key依次为字符串W1,B1,W2,B2
network.params[key] -= learning_rate * grad[key]
loss = network.loss(x_batch, t_batch)
train_loss_list.append(loss)
if i % iter_per_epoch == 0:
train_acc = network.accuracy(x_train, t_train)
test_acc = network.accuracy(x_test, t_test)
train_acc_list.append(train_acc)
test_acc_list.append(test_acc)
print("train acc, test acc | " + str(train_acc) + ", " + str(test_acc))
# 绘制图形
markers = {'train': 'o', 'test': 's'}
x = np.arange(len(train_acc_list))
plt.plot(x, train_acc_list, label='train acc')
plt.plot(x, test_acc_list, label='test acc', linestyle='--')
plt.xlabel("epochs")
plt.ylabel("accuracy")
plt.ylim(0, 1.0)
plt.legend(loc='lower right')
plt.show()
可以发现随着学习的进行,损失函数的值在不断减小。这是学习正常进行的信号,表示神经网络的权重参数在逐渐拟合数据。也就是说,神经网络的确在学习!通过反复地向它浇灌(输入)数据,神经网络正在逐渐向最优参数靠近。
基于测试数据的评价
防止过拟合,引入准确度,经过一个epoch记录一次训练数据的识别精度。 也就是上面程序中的这部分:
if i % iter_per_epoch == 0: #精准判断 “是否完成 1 个 Epoch”,只有完成 1 个 Epoch 时,才计算一次准确率
train_acc = network.accuracy(x_train, t_train)
test_acc = network.accuracy(x_test, t_test)
train_acc_list.append(train_acc)
test_acc_list.append(test_acc)
print("train acc, test acc | " + str(train_acc) + ", " + str(test_acc))