深度学习:从头构建神经网络

Description

本文旨在使用PyTorch构建并训练一个最简单的神经网络,无需添加任何花哨的层或依赖包。

该模型将足够简单,大家都能使用CPU或GPU来构建和训练。

这个模型虽然简单,但包含了当前诸如LLM和Stable Diffusions等大型模型所拥有的所有基本元素。

准备数据

假设我们要训练一个具有四个权重并能输出一个数字结果的模型,如下所示:

$ y = w_1 * x_1 + w_2 * x_2 + w_3 * x_3 + w_4 * x_4 $

我们先生成以下训练数据,假设权重值为 [2,3,4,7]

import numpy as np
w_list = np.array([2,3,4,7])

我们的模型将会用于预测权重列表,因此再生成一些训练数据后我们假装我们不知道这些权重的值

之后我们创建10组输入样本数据——x_sample,每组x_sample是一个包含4个元素的数组,与权重的长度相同

# Generate 10 random input samples (each with same length as w_list)
import random
x_list = []
for _ in range(10):
    x_sample = np.array([random.randint(1, 100) for _ in range(len(w_list))])
    x_list.append(x_sample)

这里,我们使用numpy,因为我们想利用numpy的点积函数轻松生成输出——y

说到y,我们来生成一个同样包含10个元素的y_list:

y_list = []
for x_sample in x_list:
    y_temp = x_sample @ w_list 
    y_list.append(y_temp)

我们的训练数据已准备就绪,无需下载任何内容,也无需使用DataLoader等,接下来,我们可以开始定义模型

定义模型

我们的模型可能是世界上最简单的模型,即以下代码中定义的一个简单的线性点积:

import torch 
import torch.nn as nn
class MyLinear(nn.Module):
  def __init__(self):
​    super().__init__()
​    self.w = nn.Parameter(torch.randn(len(w_list), dtype=torch.float32))
​    print("Initial weights:", self.w)  # Print initialized weights
  def forward(self, x: torch.Tensor):
​    return self.w @ x

在上述代码中,使用self.w = nn.Parameter(torch.randn(len(w_list)))初始化了权重张量。

无需其他代码,我们的神经网络模型现已准备就绪,命名为——MyLinear

准备训练模型

我们需要像LLM那样初始化模型的随机权重

model = MyLinear()

几乎所有神经网络模型的训练都遵循以下步骤:

  • 前向传播以预测结果
  • 与真实值进行比较以获取损失值
  • 反向传播梯度损失值
  • 更新模型参数

因此,在开始训练之前,我们需要定义一个损失函数和一个优化器

损失函数loss_fn将根据预测结果和真实结果计算损失值,优化器将用于更新权重

loss_fn = nn.MSELoss()
optimizer = torch.optim.SGD(model.parameters(), lr=0.00001)

lr代表学习率,这是最难设置的超参数之一,确定最佳学习率(lr)通常需要根据模型、数据集和问题的特性进行反复试验。然不过有一些策略和技术可以帮助估计一个合理的学习率

  • 从较小的学习率开始:一种常见做法是从较小的学习率(如0.001)开始,并根据观察到的收敛行为逐渐增加或减少它
  • 学习率调度:可以使用学习率调度在训练过程中动态调整学习率。一种常见方法是阶梯衰减,即在固定数量的训练周期后降低学习率。另一种流行方法是指数衰减,即学习率随时间呈指数下降

另外别忘了将输入和输出转换为torch张量对象

x_input = torch.tensor(x_list, dtype=torch.float32)
y_output = torch.tensor(y_list, dtype=torch.float32)

训练模型

我们将训练周期数设置为100,这意味着我们将遍历训练数据100次

# 开始训练模型
num_epochs = 100  # 总训练周期
for epoch in range(num_epochs):
    for i, x in enumerate(x_input):
        # 向前传播
        y_pred = model(x)  # Get prediction from model
        
        # 计算损失值
        loss = loss_fn(y_pred, y_output[i])  # Compare prediction with true value
        
        # 清除之前的缓存的梯度参数
        optimizer.zero_grad()  # Clear previous gradient information
        
        # 反向传播计算梯度
        loss.backward()  # Compute gradients for current parameters
        
        # 更新模型参数
        optimizer.step()  # Update model parameters based on gradients
    
    # 每十个epoch打印一次训练进度
    if (epoch+1) % 10 == 0:
        print(f'Epoch [{epoch+1}/{num_epochs}], Loss: {loss.item():.4f}')

print("Training completed")

在上述代码中,我们可以看到两层循环,外层循环用于训练周期,内层循环用于遍历10组样本数据

我们有10组输入,每组输入有4个元素,即x_1, x_2, x_3, 和 x_4

正如我们在准备阶段所讨论的,第一步是使用模型预测一个结果:y_pred,然后,调用loss_fn来计算损失值

在将损失值反向传播之前,我们需要通过调用optimizer.zero_grad()来清除上一次的梯度值

最后,调用backward和optimizer.step()来更新模型参数

运行代码,我们将看到程序输出类似于以下内容:

Epoch [10/100], Loss: 218.3843
Epoch [20/100], Loss: 283.2002
Epoch [30/100], Loss: 116.5593
Epoch [40/100], Loss: 43.8340
Epoch [50/100], Loss: 16.3244
Epoch [60/100], Loss: 6.0721
Epoch [70/100], Loss: 2.2586
Epoch [80/100], Loss: 0.8400
Epoch [90/100], Loss: 0.3124
Epoch [100/100], Loss: 0.1162
train done

随着时间的推移,损失值在下降,这看起来很不错,我们在代码中添加以下功能:

  • 检查模型当前权重

  • 增加权重数量(上述案例中我们只用了4个权重)

  • 增加训练周期数和更新学习率——lr,

以下是整理出的完整代码(无CUDA版) 同时改动了权重数量和训练周期

# ----------------------------------------------------------
# 生成训练数据
# ----------------------------------------------------------
import numpy as np
import random

# 定义真实的权重向量(训练目标)
w_list = np.array([2, 3, 4, 7, 11, 5, 13])

# 生成10个随机输入样本(每个样本长度与w_list相同)
x_list = []
for _ in range(10):
    x_sample = np.array([random.randint(1, 100) for _ in range(len(w_list))])
    x_list.append(x_sample)

# 计算对应的目标输出(输入与真实权重的点积)
y_list = []
for x_sample in x_list:
    y_temp = x_sample @ w_list  # 矩阵乘法计算预测值
    y_list.append(y_temp)

# -----------------------------------------------------------
# 准备训练环境
# -----------------------------------------------------------
import torch 
import torch.nn as nn

# 定义自定义线性模型
class MyLinear(nn.Module):
    def __init__(self):
        super().__init__()
        # 初始化可学习参数(长度与w_list相同)
        self.w = nn.Parameter(torch.randn(len(w_list), dtype=torch.float32))
        print("初始权重:", self.w)  # 打印初始化的权重
    
    def forward(self, x: torch.Tensor):
        # 前向传播:权重向量与输入向量的点积
        return self.w @ x

# 实例化模型(CPU模式)
model = MyLinear()  # 不使用GPU

# 定义损失函数(均方误差)
loss_fn = nn.MSELoss()

# 定义优化器(随机梯度下降,学习率0.00001)
optimizer = torch.optim.SGD(model.parameters(), lr=0.00001)

# 数据预处理(CPU模式)
x_input = torch.tensor(x_list, dtype=torch.float32)  # 输入数据转为张量
y_output = torch.tensor(y_list, dtype=torch.float32)  # 目标数据转为张量

# ------------------------------------------------------------
# 开始模型训练
# ------------------------------------------------------------
num_epochs = 200  # 总训练轮数

for epoch in range(num_epochs):
    for i, x in enumerate(x_input):
        # 前向传播
        y_pred = model(x)  # 获取模型预测值
        
        # 计算损失
        loss = loss_fn(y_pred, y_output[i])  # 比较预测值与真实值
        
        # 清除梯度缓存
        optimizer.zero_grad()  # 清除之前的梯度信息
        
        # 反向传播
        loss.backward()  # 计算当前参数的梯度
        
        # 参数更新
        optimizer.step()  # 根据梯度更新模型参数
    
    # 每10轮打印一次训练进度
    if (epoch+1) % 10 == 0:
        print(f'轮次 [{epoch+1}/{num_epochs}], 损失: {loss.item():.4f}')

print("训练完成")
print("最终权重:", model.w.detach().numpy())

输出如下:

初始模型权重:

初始权重: Parameter containing:
tensor([-0.3772, -1.7161,  0.2819, -0.4298, -0.8599, -0.2690, -0.5375],

模型计算出的损失值和权重:

轮次 [10/200], 损失: 223.6982
轮次 [20/200], 损失: 3.3349
轮次 [30/200], 损失: 42.0250
轮次 [40/200], 损失: 41.3786
轮次 [50/200], 损失: 26.6852
轮次 [60/200], 损失: 14.8007
轮次 [70/200], 损失: 7.7069
轮次 [80/200], 损失: 3.9039
轮次 [90/200], 损失: 1.9543
轮次 [100/200], 损失: 0.9728
轮次 [110/200], 损失: 0.4838
轮次 [120/200], 损失: 0.2403
轮次 [130/200], 损失: 0.1195
轮次 [140/200], 损失: 0.0594
轮次 [150/200], 损失: 0.0295
轮次 [160/200], 损失: 0.0147
轮次 [170/200], 损失: 0.0073
轮次 [180/200], 损失: 0.0036
轮次 [190/200], 损失: 0.0018
轮次 [200/200], 损失: 0.0009
训练完成
最终权重: [ 2.003787   3.0008602  3.9975853  6.9999294 10.997534   5.0006266
 12.999097 ]

我们代码中所定义的权重:

# 定义真实的权重向量(训练目标)
w_list = np.array([2, 3, 4, 7, 11, 5, 13])

从中我们可以看到我们的模型最终计算出的权重值非常[2,3,4,7,11,5,13],由此得出模型已成功训练并找到了正确的权重值!


深度学习:从头构建神经网络
https://zer0ptr.github.io/2025/07/19/深度学习-从头构建神经网络/
Author
zer0ptr
Posted on
July 19, 2025
Licensed under