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])
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