找回密码
 立即注册
首页 业界区 业界 深度学习入门

深度学习入门

尚腱埂 3 天前
从梯度下降到神经网络学习

本次学习通过对《深度学习入门:基于Python的理论与实践》该书前四章进行理论研究,及在ai大模型协助下进行可训练神经网络框架的书写,深刻理解深度学习。以下是相关的学习成果:
1.理论问题回答

一、学习与模型(第 1 章)

Q1. 神经网络训练过程中,哪些量是已知的,哪些量是未知的?
已知量:
1.训练数据和测试数据(或监督数据和测试数据);
2.网络结构:神经元的层数、各层神经元数量;人为设定的结构参数。
未知量:
权重参数W和偏置参数b。
学习的目标到底是什么?
通过调整未知的权重参数和偏置参数,最小化损失函数的值,让模型具备对测试数据的泛化识别能力,实现模型对未知数据的稳定预测。
二、线性模型与非线性(第 2 章)

Q2. 为什么单层感知机只能解决线性可分问题?
因为单层感知机只能表示由直线分割的线性空间,对于异或门这类非线性可分问题无法用一条直线划分两类样本。
**Q3. 为什么必须引入非线性激活函数? **
因为线性激活函数有局限性,只能等价于单层线性模型,无法学习复杂的非线性关系。为了发挥叠加层所带来的优势,激活函数必须使用非线性函数。
如果把神经网络中所有激活函数都去掉,会发生什么?
无法学习非线性模式,仅能处理线性可分问题。
三、神经网络的前向计算(第 3 章)

Q4. 神经网络的前向传播,本质上在做什么数学运算?
本质上是矩阵乘法和激活函数运算的交替执行。
先通过矩阵乘法计算输入信号与权重的加权和,并叠加偏置,再通过激活函数对结果进行非线性转换,逐层传递至输出层。
Q5. 为什么分类问题中,输出层通常使用 Softmax? Softmax 在概率意义上做了什么?
核心原因:将神经网络输出的“得分”(未归一化值)转换为概率分布,使输出值总和为1,便于直观解读类别概率,且能与交叉熵误差配合,使反向传播时梯度计算更简洁(直接得到输出与标签的差分)。
在概率上,它对于每个类别i,都计算其相对概率,既保留了各得分的相对大小关系,又将输出归一化到[0,1]区间,满足概率的基本性质(非负性、总和为1)。
四、损失函数与梯度(第 4 章 · 核心)

**Q6. 为什么“准确率”不能作为训练时的优化目标? **
因为准确率是离散指标,多数情况下梯度为0,无法引导参数更新。例如,微小调整权重可能不会改变分类结果,导致准确率不变,参数更新停滞;且准确率的变化不连续,无法反映参数变化对模型性能的连续影响。
为什么必须引入损失函数?
因为它是连续可微的函数,能量化模型预测与真实标签的差异,其梯度可指导参数沿“减小误差”的方向更新;同时,损失函数的连续变化能反映参数调整的效果,确保学习过程持续推进。
五、梯度下降的本质(第 4 章 · 灵魂)

Q7. 梯度在几何意义上代表什么?
在几何意义上,梯度是损失函数在当前参数点处的方向导数最大值方向,即函数值增长最快的方向,其模长表示增长的速率。
为什么沿着负梯度方向更新参数?
因为神经网络学习的目标是最小化损失函数,负梯度方向是损失函数值减小最快的方向,沿该方向更新参数能高效逼近损失函数的最小值(或局部最小值)。
**Q8. 学习率在梯度下降中起什么作用? **
作用:控制参数更新的步长,决定每次迭代中参数沿负梯度方向调整的幅度,是平衡学习速度与收敛效果的关键超参数。
学习率过大会怎样?过小又会怎样?
学习率过大:参数更新步长过大,可能导致损失函数值震荡不收敛,甚至发散(如参数值超出最优范围,损失函数值持续增大)。
学习率过小:参数更新步长过小,学习速度极慢,需要大量迭代才能逼近最优解;且可能陷入局部最小值或鞍点,无法抵达全局最优。
2. 完整可运行代码

1.激活函数 & 损失函数:

使用 numpy 实现:Sigmoid,ReLU,Softmax
实现:交叉熵损失(支持 batch 输入)
  1. import numpy as np
  2. def sigmoid(x):
  3. """
  4. Sigmoid 激活函数:
  5. 公式: h(x) = 1 / (1 + exp(-x))
  6. 参数:
  7. x: 输入数据
  8. 返回:
  9. Sigmoid 输出
  10. """
  11. return 1 / (1 + np.exp(-x))
  12. def relu(x):
  13. """
  14. ReLU 激活函数:
  15. 公式: h(x) = max(0, x)
  16. 参数:
  17. x: 输入数据 (numpy array)
  18. 返回:
  19. ReLU 输出
  20. """
  21. return np.maximum(0, x)
  22. def softmax(x):
  23. """
  24. Softmax 激活函数:
  25. 公式: y_k = exp(a_k) / sum(exp(a_i))
  26. 参数:
  27. x: 输入数据 (numpy array)
  28.    如果是 1D 数组,视为单个样本。
  29.    如果是 2D 数组,视为 batch 样本。
  30. 返回:
  31. Softmax 输出
  32. """
  33. if x.ndim == 2:
  34.     # Batch 处理
  35.     x = x.T
  36.     x = x - np.max(x, axis=0) # 稳定性优化(减去最大值,防止 exp 溢出)
  37.     y = np.exp(x) / np.sum(np.exp(x), axis=0)
  38.     return y.T
  39. # 单个样本处理
  40. x = x - np.max(x)
  41. return np.exp(x) / np.sum(np.exp(x))
  42. def cross_entropy_error(y, t):
  43. """
  44. 交叉熵损失函数:
  45. 公式: E = -sum(t_k * log(y_k))
  46. 参数:
  47. y: 神经网络的输出 (概率分布),经过 Softmax
  48. t: 监督数据 (标签)
  49.    可以是 one-hot 向量 (例如 [0, 1, 0, 0, ...])
  50.    也可以是标签索引 (例如 1)
  51. 返回:
  52. 损失值 (标量)
  53. """
  54. if y.ndim == 1:
  55.     t = t.reshape(1, t.size)
  56.     y = y.reshape(1, y.size)
  57.    
  58. # 如果 t 是 one-hot 向量,转换为标签索引
  59. if t.size == y.size:
  60.     t = t.argmax(axis=1)
  61.    
  62. batch_size = y.shape[0]
  63. # 添加一个微小值 delta 防止 log(0)
  64. delta = 1e-7
  65. return -np.sum(np.log(y[np.arange(batch_size), t] + delta)) / batch_size
复制代码
2.数值梯度
  1. import numpy as np
  2. def numerical_gradient(f, x):
  3. """
  4. 数值梯度计算函数
  5. 使用中心差分法近似计算梯度: (f(x+h) - f(x-h)) / 2h
  6. 参数:
  7. f: 目标函数
  8. x: 输入变量 (numpy array)
  9. 返回:
  10. 梯度 (与 x 形状相同)
  11. """
  12. h = 1e-4 #设置一个很小的数
  13. grad = np.zeros_like(x) # 生成和 x 形状相同的数组,用于存放梯度
  14. #遍历x的每一个元素
  15. it = np.nditer(x, flags=['multi_index'], op_flags=['readwrite'])
  16. while not it.finished:
  17.     idx = it.multi_index
  18.     tmp_val = x[idx]
  19.    
  20.     # 计算 f(x+h)
  21.     x[idx] = float(tmp_val) + h
  22.     fxh1 = f(x)
  23.    
  24.     # 计算 f(x-h)
  25.     x[idx] = float(tmp_val) - h
  26.     fxh2 = f(x)
  27.    
  28.     # 计算梯度
  29.     grad[idx] = (fxh1 - fxh2) / (2*h)
  30.    
  31.     # 还原值
  32.     x[idx] = tmp_val
  33.     it.iternext()
  34.    
  35. return grad
复制代码
3.搭建网络

层与反向传播
  1. import numpy as np
  2. from src.functions import softmax,
  3. cross_entropy_error
  4. class Relu:
  5. """
  6. ReLU 层
  7. 前向传播: out = x (x > 0), 0 (x <= 0)
  8. 反向传播: dx = dout (x > 0), 0 (x <= 0)
  9. """
  10. def __init__(self):
  11.     self.mask = None # 用于记录 x 中小于等于 0 的位置
  12. def forward(self, x):
  13.     """
  14.     前向传播
  15.     """
  16.     self.mask = (x <= 0)
  17.     out = x.copy()
  18.     out[self.mask] = 0
  19.     return out
  20. def backward(self, dout):
  21.     """
  22.     反向传播
  23.     dout: 上一层传下来的梯度
  24.     """
  25.     dout[self.mask] = 0
  26.     dx = dout
  27.     return dx
  28. class Affine:
  29. """
  30. Affine 层 (全连接层)
  31. 前向传播: out = np.dot(x, W) + b
  32. """
  33. def __init__(self, W, b):
  34.     self.W = W # 权重
  35.     self.b = b # 偏置
  36.     self.x = None # 保存输入,用于反向传播
  37.     self.dW = None # 权重的梯度
  38.     self.db = None # 偏置的梯度
  39. def forward(self, x):
  40.     # 如果输入是张量 (N, C, H, W),需要展平为 (N, D)
  41.     self.original_x_shape = x.shape
  42.     x = x.reshape(x.shape[0], -1)
  43.     self.x = x
  44.    
  45.     out = np.dot(self.x, self.W) + self.b
  46.     return out
  47. def backward(self, dout):
  48.     dx = np.dot(dout, self.W.T)
  49.     self.dW = np.dot(self.x.T, dout)
  50.     self.db = np.sum(dout, axis=0)
  51.    
  52.     dx = dx.reshape(*self.original_x_shape) # 还原输入形状
  53.     return dx
  54. class SoftmaxWithLoss:
  55. """
  56. SoftmaxWithLoss 层
  57. 结合了 Softmax 激活函数和 Cross Entropy Loss
  58. """
  59. def __init__(self):
  60.     self.loss = None
  61.     self.y = None # Softmax 的输出
  62.     self.t = None # 监督数据 (One-hot 或 标签索引)
  63. def forward(self, x, t):
  64.     self.t = t
  65.     self.y = softmax(x)
  66.     self.loss = cross_entropy_error(self.y, self.t)
  67.     return self.loss
  68. def backward(self, dout=1):
  69.     batch_size = self.t.shape[0]
  70.    
  71.     # 处理 one-hot 编码和标签索引两种情况
  72.     if self.t.size == self.y.size: # one-hot
  73.         dx = (self.y - self.t) / batch_size
  74.     else:
  75.         dx = self.y.copy()
  76.         dx[np.arange(batch_size), self.t] -= 1
  77.         dx = dx / batch_size
  78.         
  79.     return dx
复制代码
4.数据加载及主训练循环

数据加载
  1. import numpy as np
  2. from collections import OrderedDict
  3. from src.layers import *
  4. from src.gradient import numerical_gradient
  5. class TwoLayerNet:
  6. """
  7. 两层神经网络
  8. 结构: Input -> Affine -> ReLU -> Affine -> SoftmaxWithLoss
  9. """
  10. def __init__(self, input_size, hidden_size, output_size, weight_init_std=0.01):
  11.     """
  12.     初始化网络权重
  13.     参数:
  14.     input_size: 输入层神经元数量
  15.     hidden_size: 隐藏层神经元数量
  16.     output_size: 输出层神经元数量
  17.     weight_init_std: 权重初始化标准差
  18.     """
  19.     # 初始化权重
  20.     self.params = {}
  21.     self.params['W1'] = weight_init_std * np.random.randn(input_size, hidden_size)
  22.     self.params['b1'] = np.zeros(hidden_size)
  23.     self.params['W2'] = weight_init_std * np.random.randn(hidden_size, output_size)
  24.     self.params['b2'] = np.zeros(output_size)
  25.     # 生成层
  26.     self.layers = OrderedDict()
  27.     self.layers['Affine1'] = Affine(self.params['W1'], self.params['b1'])
  28.     self.layers['Relu1'] = Relu()
  29.     self.layers['Affine2'] = Affine(self.params['W2'], self.params['b2'])
  30.     self.lastLayer = SoftmaxWithLoss()
  31.    
  32. def predict(self, x):
  33.     """
  34.     前向传播 (预测)
  35.     """
  36.     for layer in self.layers.values():
  37.         x = layer.forward(x)
  38.     return x
  39.    
  40. def loss(self, x, t):
  41.     """
  42.     计算损失函数值
  43.    
  44.     参数:
  45.     x: 输入数据
  46.     t: 监督数据 (标签)
  47.     """
  48.     y = self.predict(x)
  49.     return self.lastLayer.forward(y, t)
  50. def accuracy(self, x, t):
  51.     """
  52.     计算精度
  53.     """
  54.     y = self.predict(x)
  55.     y = np.argmax(y, axis=1)
  56.     if t.ndim != 1 : t = np.argmax(t, axis=1)
  57.    
  58.     accuracy = np.sum(y == t) / float(x.shape[0])
  59.     return accuracy
  60.    
  61. def numerical_gradient(self, x, t):
  62.     """
  63.     使用数值微分计算梯度 (速度较慢,用于验证)
  64.     """
  65.     loss_W = lambda W: self.loss(x, t)
  66.    
  67.     grads = {}
  68.     grads['W1'] = numerical_gradient(loss_W, self.params['W1'])
  69.     grads['b1'] = numerical_gradient(loss_W, self.params['b1'])
  70.     grads['W2'] = numerical_gradient(loss_W, self.params['W2'])
  71.     grads['b2'] = numerical_gradient(loss_W, self.params['b2'])
  72.    
  73.     return grads
  74.    
  75. def gradient(self, x, t):
  76.     """
  77.     使用误差反向传播法计算梯度 (速度快)
  78.     """
  79.     # 1. 前向传播
  80.     self.loss(x, t)
  81.     # 2. 反向传播
  82.     dout = 1
  83.     dout = self.lastLayer.backward(dout)
  84.    
  85.     layers = list(self.layers.values())
  86.     layers.reverse()
  87.     for layer in layers:
  88.         dout = layer.backward(dout)
  89.     # 3. 收集梯度
  90.     grads = {}
  91.     grads['W1'] = self.layers['Affine1'].dW
  92.     grads['b1'] = self.layers['Affine1'].db
  93.     grads['W2'] = self.layers['Affine2'].dW
  94.     grads['b2'] = self.layers['Affine2'].db
  95.     return grads
复制代码
}
  1. import os
  2. import gzip
  3. import numpy as np
  4. import urllib.request
  5. # MNIST 数据集下载链接
  6. url_base = 'https://ossci-        datasets.s3.amazonaws.com/mnist/'
  7. key_file = {
  8. 'train_img': 'train-images-idx3-ubyte.gz',
  9. 'train_label': 'train-labels-idx1-ubyte.gz',
  10. 'test_img': 't10k-images-idx3-ubyte.gz',
  11. 'test_label': 't10k-labels-idx1-ubyte.gz'
复制代码
主训练循环
  1. dataset_dir = os.path.dirname(os.path.abspath(__file__))
  2. save_file = dataset_dir + "/mnist.pkl"
  3. train_num = 60000
  4. test_num = 10000
  5. img_dim = (1, 28, 28)
  6. img_size = 784
  7. def _download(file_name):
  8. file_path = dataset_dir + "/" + file_name
  9. if os.path.exists(file_path):
  10.     return
  11. print("Downloading " + file_name + " ... ")
  12. # 使用 header 模拟浏览器,防止某些服务器拒绝
  13. headers = {'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 Safari/537.3'}
  14. req = urllib.request.Request(url_base + file_name, headers=headers)
  15. try:
  16.     with urllib.request.urlopen(req) as response, open(file_path, 'wb') as out_file:
  17.         data = response.read()
  18.         out_file.write(data)
  19.     print("Done")
  20. except Exception as e:
  21.     print(f"Failed to download {file_name}: {e}")
  22.     # 如果下载失败,尝试备用镜像或提示用户
  23.     print("Please try to download manually and place in: ", dataset_dir)
  24. def download_mnist():
  25. for v in key_file.values():
  26.     _download(v)
  27. def _load_label(file_name):
  28. file_path = dataset_dir + "/" + file_name
  29. print("Converting " + file_name + " to NumPy Array ...")
  30. with gzip.open(file_path, 'rb') as f:
  31.     labels = np.frombuffer(f.read(), np.uint8, offset=8)
  32. print("Done")
  33. return labels
  34. def _load_img(file_name):
  35. file_path = dataset_dir + "/" + file_name
  36. print("Converting " + file_name + " to NumPy Array ...")
  37. with gzip.open(file_path, 'rb') as f:
  38.     data = np.frombuffer(f.read(), np.uint8, offset=16)
  39. data = data.reshape(-1, img_size)
  40. print("Done")
  41. return data
  42. def _convert_numpy():
  43. dataset = {}
  44. dataset['train_img'] =  _load_img(key_file['train_img'])
  45. dataset['train_label'] = _load_label(key_file['train_label'])   
  46. dataset['test_img'] = _load_img(key_file['test_img'])
  47. dataset['test_label'] = _load_label(key_file['test_label'])
  48. return dataset
  49. def init_mnist():
  50. download_mnist()
  51. dataset = _convert_numpy()
  52. print("Creating pickle file ...")
  53. import pickle
  54. with open(save_file, 'wb') as f:
  55.     pickle.dump(dataset, f, -1)
  56. print("Done!")
  57. def load_mnist(normalize=True, flatten=True, one_hot_label=False):
  58. """
  59. 读入 MNIST 数据集
  60. Parameters
  61. normalize : 将图像的像素值正规化为 0.0~1.0
  62. one_hot_label :
  63.     False -> 7, 2, ...
  64.     True -> [0,0,0,0,0,0,0,1,0,0], [0,0,1,0,0,0,0,0,0,0], ...
  65. flatten : 是否将图像展开为一维数组
  66. Returns
  67. (训练图像, 训练标签), (测试图像, 测试标签)
  68. """
  69. if not os.path.exists(save_file):
  70.     init_mnist()
  71.    
  72. import pickle
  73. with open(save_file, 'rb') as f:
  74.     dataset = pickle.load(f)
  75. if normalize:
  76.     for key in ('train_img', 'test_img'):
  77.         dataset[key] = dataset[key].astype(np.float32)
  78.         dataset[key] /= 255.0
  79.         
  80. if one_hot_label:
  81.     dataset['train_label'] = _change_one_hot_label(dataset['train_label'])
  82.     dataset['test_label'] = _change_one_hot_label(dataset['test_label'])
  83. if not flatten:
  84.     for key in ('train_img', 'test_img'):
  85.         dataset[key] = dataset[key].reshape(-1, 1, 28, 28)
  86. return (dataset['train_img'], dataset['train_label']), (dataset['test_img'], dataset['test_label'])
  87. def _change_one_hot_label(X):
  88. T = np.zeros((X.size, 10))
  89. for idx, row in enumerate(T):
  90.     row[X[idx]] = 1
  91.    
  92. return T
复制代码
注:关于MNIST数据集在代码中使用:
  1. import numpy as np
  2. import matplotlib.pyplot as plt
  3. from src.dataset import load_mnist
  4. from src.network import TwoLayerNet
  5. # 1. 读入数据
  6. print("正在加载 MNIST 数据集...")
  7. (x_train, t_train), (x_test, t_test) = load_mnist(normalize=True, one_hot_label=True)
  8. print(f"训练数据形状: {x_train.shape}")
  9. print(f"测试数据形状: {x_test.shape}")
  10. # 2. 超参数设置
  11. iters_num = 10000  # 适当设定循环的次数
  12. train_size = x_train.shape[0]
  13. batch_size = 100
  14. learning_rate = 0.1
  15. train_loss_list = []
  16. train_acc_list = []
  17. test_acc_list = []
  18. # 平均每个 epoch 的重复次数
  19. iter_per_epoch = max(train_size / batch_size, 1)
  20. # 3. 初始化网络
  21. # 输入层 784 (28x28), 隐藏层 50, 输出层 10 (0-9)
  22. network = TwoLayerNet(input_size=784, hidden_size=50, output_size=10)
  23. print("开始训练...")
  24. for i in range(iters_num):
  25.     # 获取 mini-batch
  26.     batch_mask = np.random.choice(train_size, batch_size)
  27.     x_batch = x_train[batch_mask]
  28.     t_batch = t_train[batch_mask]
  29.    
  30. # 计算梯度
  31. # 推荐: 使用反向传播 (Task 4 实现)
  32. grad = network.gradient(x_batch, t_batch)
  33. # 也可以使用数值梯度 (Task 3 实现),但速度非常慢,不建议在实际训练中使用
  34. # grad = network.numerical_gradient(x_batch, t_batch)
  35. # 更新参数 (SGD - Task 5)
  36. for key in ('W1', 'b1', 'W2', 'b2'):
  37.     network.params[key] -= learning_rate * grad[key]
  38. # 记录学习过程
  39. loss = network.loss(x_batch, t_batch)
  40. train_loss_list.append(loss)
  41. # 计算每个 epoch 的识别精度
  42. if i % iter_per_epoch == 0:
  43.     train_acc = network.accuracy(x_train, t_train)
  44.     test_acc = network.accuracy(x_test, t_test)
  45.     train_acc_list.append(train_acc)
  46.     test_acc_list.append(test_acc)
  47.     print(f"epoch: {int(i/iter_per_epoch)}, loss: {loss:.4f}, train acc: {train_acc:.4f}, test acc: {test_acc:.4f}")
  48. print("训练结束!")
  49. # 4. 绘图 (Task 6)
  50. # 绘制损失函数变化
  51. plt.figure(figsize=(12, 5))
  52. plt.subplot(1, 2, 1)
  53. plt.plot(train_loss_list)
  54. plt.title("Loss Function History")
  55. plt.xlabel("Iterations")
  56. plt.ylabel("Loss")
  57. # 绘制精度变化
  58. plt.subplot(1, 2, 2)
  59. markers = {'train': 'o', 'test': 's'}
  60. x = np.arange(len(train_acc_list))
  61. plt.plot(x, train_acc_list, label='train acc')
  62. plt.plot(x, test_acc_list, label='test acc', linestyle='--')
  63. plt.xlabel("Epochs")
  64. plt.ylabel("Accuracy")
  65. plt.ylim(0, 1.0)
  66. plt.legend(loc='lower right')
  67. plt.title("Accuracy History")
  68. plt.tight_layout()
  69. plt.savefig("training_result.png")
  70. print("结果已保存至 training_result.png")
  71. # plt.show()
复制代码
运行:1.安装依赖
  1. from src.dataset import load_mnist
  2.     # 第一次运行时会自动下载并生成 mnist.pkl 缓存文件
  3.     (x_train, t_train), (x_test, t_test) = load_mnist(normalize=True, flatten=True, one_hot_label=True)
  4. print(x_train.shape) # (60000, 784)
  5. print(t_train.shape) # (60000, 10)
复制代码
2.运行训练
  1. pip install -r requirements.txt
复制代码
3. 简单实验记录(loss 曲线或日志)

1.png

以上是该次学习的基本内容。

来源:程序园用户自行投稿发布,如果侵权,请联系站长删除
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!

相关推荐

您需要登录后才可以回帖 登录 | 立即注册