逆天了!用Numpy开发深度学习框架,透视神经网络训练过程

哈喽,大家好。

今天给大家分享一个非常牛逼的开源项目,用Numpy​开发了一个深度学习框架,语法与 Pytorch 基本一致。

今天以一个简单的卷积神经网络为例,分析神经网络训练过程中,涉及的前向传播、反向传播、参数优化等核心步骤的源码。

使用的数据集和代码已经打包好,文末有获取方式。

1. 准备工作

先准备好数据和代码。

1.1 搭建网络

首先,下载框架源码,地址:https://github.com/duma-repo/PyDyNet

git clone https://github.com/duma-repo/PyDyNet.git

搭建LeNet卷积神经网络,训练三分类模型。

在PyDyNet目录直接创建代码文件即可。

from pydynet import nn

class LeNet(nn.Module):
def __init__(self):
super().__init__()
self.conv1 = nn.Conv2d(1, 6, kernel_size=5, padding=2)
self.conv2 = nn.Conv2d(6, 16, kernel_size=5)
self.avg_pool = nn.AvgPool2d(kernel_size=2, stride=2, padding=0)
self.sigmoid = nn.Sigmoid()
self.fc1 = nn.Linear(16 * 5 * 5, 120)
self.fc2 = nn.Linear(120, 84)
self.fc3 = nn.Linear(84, 3)

def forward(self, x):
x = self.conv1(x)
x = self.sigmoid(x)
x = self.avg_pool(x)

x = self.conv2(x)
x = self.sigmoid(x)
x = self.avg_pool(x)

x = x.reshape(x.shape[0], -1)

x = self.fc1(x)
x = self.sigmoid(x)
x = self.fc2(x)
x = self.sigmoid(x)
x = self.fc3(x)

return x

可以看到,网络的定义与Pytorch语法完全一样。

我提供的源代码里,提供了 summary 函数可以打印网络结构。

1.2 准备数据

训练数据使用Fanshion-MNIST数据集,它包含10个类别的图片,每个类别 6k 张。

为了加快训练,我只抽取了前3个类别,共1.8w张训练图片,做一个三分类模型。

1.3 模型训练

import pydynet
from pydynet import nn
from pydynet import optim

lr, num_epochs = 0.9, 10
optimizer = optim.SGD(net.parameters(),
lr=lr)
loss = nn.CrossEntropyLoss()

for epoch in range(num_epochs):
net.train()
for i, (X, y) in enumerate(train_iter):
optimizer.zero_grad()
y_hat = net(X)
l = loss(y_hat, y)
l.backward()
optimizer.step()

with pydynet.no_grad():
metric.add(l.numpy() * X.shape[0],
 accuracy(y_hat, y),
 X.shape[0])

训练代码也跟Pytorch一样。

下面重点要做的就是深入模型训练的源码,来学习模型训练的原理。

2. train、no_grad和eval

模型开始训练前,会调用net.train。

def train(self, mode: bool = True):
set_grad_enabled(mode)
self.set_module_state(mode)

可以看到,它会将grad​(梯度)设置成True​,之后创建的Tensor​是可以带梯度的。Tensor带上梯度后,便会将其放入计算图中,等待求导计算梯度。

而下面的with no_grad(): 代码

class no_grad:
def __enter__(self) -> None:
self.prev = is_grad_enable()
set_grad_enabled(False)

会将grad​(梯度)设置成False​,这样之后创建的Tensor不会放到计算图中,自然也不需要计算梯度,可以加快推理。

我们经常在Pytorch​中看到net.eval()的用法,我们也顺便看一下它的源码。

def eval(self):
return self.train(False)

可以看到,它直接调用train(False)​来关闭梯度,效果与no_grad()类似。

所以,一般在训练前调用train​打开梯度。训练后,调用eval关闭梯度,方便快速推理。

3. 前向传播

前向传播除了计算类别概率外,最最重要的一件事是按照前传顺序,将网络中的 tensor​ 组织成计算图,目的是为了反向传播时计算每个tensor的梯度。

tensor在神经网络中,不止用来存储数据,还用计算梯度、存储梯度。

以第一层卷积操作为例,来查看如何生成计算图。

def conv2d(x: tensor.Tensor,
 kernel: tensor.Tensor,
 padding: int = 0,
 stride: int = 1):
'''二维卷积函数
'''
N, _, _, _ = x.shape
out_channels, _, kernel_size, _ = kernel.shape
pad_x = __pad2d(x, padding)
col = __im2col2d(pad_x, kernel_size, stride)
out_h, out_w = col.shape[-2:]
col = col.transpose(0, 4, 5, 1, 2, 3).reshape(N * out_h * out_w, -1)
col_filter = kernel.reshape(out_channels, -1).T
out = col @ col_filter
return out.reshape(N, out_h, out_w, -1).transpose(0, 3, 1, 2)

x​是输入的图片,不需要记录梯度。kernel是卷积核的权重,需要计算梯度。

所以,pad_x = __pad2d(x, padding)​ 生成的新的tensor也是不带梯度的,因此也不需要加入计算图中。

而kernel.reshape(out_channels, -1)​产生的tensor则是需要计算梯度,也需要加入计算图中。

下面看看加入的过程:

def reshape(self, *new_shape):
return reshape(self, new_shape)

class reshape(UnaryOperator):
'''
张量形状变换算子,在Tensor中进行重载

Parameters
----------
new_shape : tuple
变换后的形状,用法同NumPy
'''
def __init__(self, x: Tensor, new_shape: tuple) -> None:
self.new_shape = new_shape
super().__init__(x)

def forward(self, x: Tensor)
return x.data.reshape(self.new_shape)

def grad_fn(self, x: Tensor, grad: np.ndarray)
return grad.reshape(x.shape)

reshape​函数会返回一个reshape​类对象,reshape​类继承了UnaryOperator​类,并在__init__函数中,调用了父类初始化函数。

class UnaryOperator(Tensor):
def __init__(self, x: Tensor) -> None:
if not isinstance(x, Tensor):
x = Tensor(x)
self.device = x.device
super().__init__(
data=self.forward(x),
device=x.device,
# 这里 requires_grad 为 True
requires_grad=is_grad_enable() and x.requires_grad,
)

UnaryOperator​类继承了Tensor​类,所以reshape​对象也是一个tensor。

在UnaryOperator的__init__​函数中,调用Tensor​的初始化函数,并且传入的requires_grad​参数是True,代表需要计算梯度。

requires_grad​的计算代码为is_grad_enable() and x.requires_grad,is_grad_enable()​已经被train​设置为True​,而x​是卷积核,它的requires_grad​也是True。

class Tensor:
def __init__(
self,
data: Any,
dtype=None,
device: Union[Device, int, str, None] = None,
requires_grad: bool = False,
) -> None:
if self.requires_grad:
# 不需要求梯度的节点不出现在动态计算图中
Graph.add_node(self)

最终在Tensor​类的初始化方法中,调用Graph.add_node(self)​将当前tensor加入到计算图中。

同理,下面使用requires_grad=True的tensor​常见出来的新tensor都会放到计算图中。

经过一次卷积操作,计算图中会增加 6 个节点。

4. 反向传播

一次前向传播完成后,从计算图中最后一个节点开始,从后往前进行反向传播。

l = loss(y_hat, y)
l.backward()

经过前向网络一层层传播,最终传到了损失张量l。

以l​为起点,从前向后传播,就可计算计算图中每个节点的梯度。

backward的核心代码如下:

def backward(self, retain_graph: bool = False):

for node in Graph.node_list[y_id::-1]:
grad = node.grad
for last in [l for l in node.last if l.requires_grad]:
add_grad = node.grad_fn(last, grad)

last.grad  = add_grad

Graph.node_list[y_id::-1]将计算图倒序排。

node​是前向传播时放入计算图​中的每个tensor。

node.last​ 是生成当前tensor的直接父节点。

调用node.grad_fn计算梯度,并反向传给它的父节点。

grad_fn​其实就是Tensor的求导公式,如:

class pow(BinaryOperator):
'''
幂运算算子,在Tensor类中进行重载

See also
--------
add : 加法算子
'''
def grad_fn(self, node: Tensor, grad: np.ndarray)
if node is self.last[0]:
return (self.data * self.last[1].data / node.data) * grad

return​后的代码其实就是幂函数求导公式。

假设y=x^2,x​的导数为2x。

5. 更新参数

反向传播计算梯度后,便可以调用优化器,更新模型参数。

l.backward()
optimizer.step()

本次训练我们用梯度下降SGD算法优化参数,更新过程如下:

def step(self):
for i in range(len(self.params)):
grad = self.params[i].grad   self.weight_decay * self.params[i].data
self.v[i] *= self.momentum
self.v[i]  = self.lr * grad
self.params[i].data -= self.v[i]
if self.nesterov:
self.params[i].data -= self.lr * grad

self.params​是整个网络的权重,初始化SGD时传进去的。

step​函数最核心的两行代码,self.v[i] = self.lr * grad​ 和 self.params[i].data -= self.v[i]​,用当前参数 - 学习速率 * 梯度​更新当前参数。

这是机器学习的基础内容了,我们应该很熟悉了。

一次模型训练的完整过程大致就串完了,大家可以设置打印语句,或者通过DEBUG的方式跟踪每一行代码的执行过程,这样可以更了解模型的训练过程。

本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
标签:
上一篇2025-08-22

相关推荐

  • 莱特帀手机钱包-莱特币手机钱包

    【莱特帀手机钱包】——您的虚拟货币安全助手随着数字货币的兴起,莱特帀作为一种备受关注的加密货币,越来越受到投资者的青睐,为了方便用户安全、便捷地管理莱特帀资

    2025-08-22 03:58:24
    2019
  • ttm数字货币币钱包-ttt数字货币

    TTM数字货币币钱包——您的虚拟货币钱包助手随着数字货币的普及,越来越多的人开始关注并投资数字货币,数字货币的安全存储问题成为了投资者们面临的一大挑战,为了解

    2025-08-22 03:58:24
    2013
  • 货币钱包转账违法吗

    虚拟货币钱包助手:揭秘钱包转账的合法性与风险尊敬的用户,您好!作为虚拟货币钱包助手,今天我们来探讨一下关于虚拟货币钱包转账的合法性与风险问题,什么是虚拟货币钱包

    2025-08-22 03:58:24
    2005
  • 欧意交易所app最新下载安装_欧意OK交易平台App下载教程

    大家好,今天来跟大家分享一下如何下载安装欧意交易所的官方App,也就是欧意OK交易平台App,这个App可以帮助用户在手机上轻松进行数字资产的交易和管理,下面是详细的下

    2025-08-22 03:58:24
    2004
  • 派币今天价值多少钱(派币今日价值报告)

    派币今天价值多少钱(派币今日价值报告)如果你是一名投资者,特别是加密货币投资者,那么你可能会对派币的表现感兴趣。究竟,在今天的市场上,你的派币价值是多少呢?让我们

    2025-08-22 03:58:24
    2003
  • usdt钱包官方下载(高级版本V6.4.24)_USDT钱包是什么?

    USDT钱包是一款基于区块链技术的数字货币钱包,主要应用于泰达币(USDT)的存储、转账和交易,泰达币作为一种稳定币,其价值与美元挂钩,1 USDT兑换1美元,因此在数字货币市场

    2025-08-22 03:58:24
    2003
  • 虚拟币前十名的各币价格

    在数字货币的世界里,各种虚拟币的价格波动总是牵动着投资者的心,下面,我将为您详细介绍当前市值排名前十的虚拟币及其价格情况,帮助您更好地了解这个市场,我们需要明确

    2025-08-22 03:58:24
    2003
  • 鱼池sc钱包-鱼池钱包模式

    【鱼池SC钱包】——您的虚拟货币守护神随着区块链技术的不断发展,虚拟货币已经成为越来越多人的投资选择,为了方便用户安全、便捷地管理自己的虚拟货币资产,各种虚拟

    2025-08-22 03:58:24
    2003