我是 雪天鱼,一名FPGA爱好者,研究方向是FPGA架构探索和数字IC设计。
关注公众号【集成电路设计教程】,获取更多学习资料,并拉你进“IC设计交流群
”。
QQIC设计&FPGA&DL交流群
群号:866169462
。
一、数学基础:标量,向量,矩阵与张量
一个标量(Scalar)就是一个单独的数;
一个向量就是一列数,这些数是有序排列的。通过索引,、可以确定对应的每个单独的数;
矩阵是二维数组,其中的每一个元素被两个索引而非一个所确定。
几何代数中定义的张量是基于向量和矩阵的推广,通俗一点理解的话,可以将标量视为零阶张量,向量视为一阶张量,那么矩阵就是二阶张量,任意一张彩色图片表示成一个三阶张量,三个维度分别是图片的高度、宽度和色彩数据。所以Tensor一般指三阶及更高阶的张量。
二、自动求导
x = torch.tensor([2.0], requires_grad = True)
a = torch.tensor([4.0], requires_grad = True)
y = x * a
# 求计算图各节点导数
y.backward()
print(x.grad) # y 对 x 的偏导 -> a
print(a.grad) # y 对 a 的偏导 -> x
结果:
pytorch 自动求导方式很简单,定义一个表达式 y,然后 y 由变量(一般为标量)x1,x2,x3...计算得到,那么此时调用 y.backward()
进行反向传播,对各变量计算梯度(即偏导数),然后这些梯度会保存在变量的 grad
属性中。
再举个例子,如下所示,对 y=x**4 求导
三、线性回归与拟合
在上图中有很多蓝点,也就是数据,我们所要做的是找出一条直线,能尽可能多的经过这些数据点,也就是达到尽可能好的拟合效果,求出斜率W
和截距b
。
可采用迭代法,即先随机初始化一个 W 和 b,然后设置一个 loss函数
(即能衡量拟合效果优劣的指标),然后根据 loss 去修改 W 和 b,目标是使 loss 尽可能的小,即拟合效果尽可能的好。示意图如下所示:
对于线性回归而言,这个衡量指标为:
即预测值与真实值之间差值的均方差,每次调整 w 和 b ,都是为了使此 loss 更小,所以就可以转化为优化问题:
那么具体怎么调整 w,b呢?即在当前位置,w是变大还是变小,b是变大还是变小。
解决方法就是通过梯度(偏导数)来判断增大还是减小所要调整的参数,以 w 为例,当 loss 对 w 的偏导数,即梯度为正表示该处 loss 随 w 增大而增大,故减小 w,f反之梯度为负表示该处 loss 随 w 增大而增大减小,故增大 w。
而具体增大多少,减小多少则由学习率(learning rate)
和梯度
共同决定,学习率越大,收敛越快,这很容易理解,因为移动的步伐增大了嘛,但同时也可以错过最佳拟合点。
代码示例:
x_train = torch.rand(100)
y_train = x_train * 2 + 3 # 目标曲线:w = 2, b = 3, y = 2 * x + 3
# (x_train,y_train)即为要拟合的数据
# 初始化 w 和 b
w = torch.tensor([0.0],requires_grad = True)
b = torch.tensor([0.0],requires_grad = True)
# 拟合
lr = 0.015 # 学习率
loss_func = torch.nn.MSELoss() # loss 函数,衡量拟合效果,越小越好
for i in range(200):
y_pre = x_train * w + b # 预测值
loss = loss_func(y_train, y_pre) # 输入预测值与实际值,计算 loss
if i % 10 == 0: # 每 10 轮输出一次 w, b, loss
print("Iter: %d, w: %.4f, b: %.4f, training loss: %.4f" % (i, w.item(), b.item(), loss.item()))
loss.backward() # 反相传播,计算 loss 对 w 和 b 的偏导数
# 调整 w 和 b
w.data -= w.grad * lr
b.data -= b.grad * lr
# 梯度清零
w.grad.data.zero_()
b.grad.data.zero_()
结果:
从结果上来看,可以发现 loss 越来越小,说明拟合结果越来越好, w 和 b 也是分别越来越靠近 2 和 3,这就是迭代法。
四、Pytorch 写法
写法总结为:
示例代码:
import torch
# 1 定义 model,给训练输入,输出对应预测值
class SimpleLinear:
def __init__(self):
self.w = torch.tensor([0.0], requires_grad=True)
self.b = torch.tensor([0.0], requires_grad=True)
# 前向传播获得预测值
def forward(self, x):
y = self.w * x + self.b
return y
def parameters(self):
return [self.w, self.b]
def __call__(self, x):
return self.forward(x)
# 2 定义优化器
class Optimizer:
def __init__(self, parameters, lr):
self.parameters = parameters
self.lr = lr
# 更新参数
def step(self):
for para in self.parameters:
para.data -= para.grad * self.lr
# 清零参数的梯度
def zero_grad(self):
for para in self.parameters:
para.grad.data.zero_()
# 3 训练
def train():
model = SimpleLinear()
opt = Optimizer(model.parameters(), lr=0.3)
for epoch in range(10):
output = model(x_train)
loss = loss_func(y_train, output)
loss.backward()
opt.step()
opt.zero_grad()
print('Epoch {},w:{:.4f} b:{:.4f},loss is {:.4f}'.format(epoch,model.parameters()[0].item(),
model.parameters()[1].item(), loss.item()))
train()
结果:
五、实战
以 MNIST 数据集
为例,做手写数字识别网络的训练与评估。
示例代码:
import math
import torch
import torch.nn as nn
import torch.nn.functional as F
from torchvision import datasets, transforms
import torch.utils.data
# 定义 model
class Net(nn.Module):
def __init__(self):
super(Net, self).__init__()
# torch.nn.Conv2d(in_channels, out_channels, kernel_size,
# stride=1, padding=0, dilation=1, groups=1, bias=True, padding_mode='zeros')
self.conv1 = nn.Conv2d(1, 32, 3, 1)
self.conv2 = nn.Conv2d(32, 64, 3, 1)
self.dropout1 = nn.Dropout2d(0.25)
self.dropout2 = nn.Dropout2d(0.5)
self.fc1 = nn.Linear(9216, 128)
self.fc2 = nn.Linear(128, 10)
def forward(self, x):
x = self.conv1(x)
x = F.relu(x)
x = self.conv2(x)
x = F.max_pool2d(x, 2)
x = self.dropout1(x)
x = torch.flatten(x, 1)
x = self.fc1(x)
x = F.relu(x)
x = self.dropout2(x)
x = self.fc2(x)
output = F.log_softmax(x, dim=1)
return output
def train(model, device, train_loader, optimizer, epoch):
model.train()
total = 0
for batch_idx, (data, target) in enumerate(train_loader):
# 获取实际值,可以简单理解: data -> x target -> y
data, target = data.to(device), target.to(device)
optimizer.zero_grad() # 梯度清零
output = model(data) # 获得预测值
loss = F.nll_loss(output, target) # 计算 loss
loss.backward() # loss 反向传播
optimizer.step() # 更新参数
# 训练进度统计
total += len(data) # 目前训练图片数
progress = math.ceil(batch_idx / len(train_loader) * 50)
print("\rTrain epoch %d: %d/%d, [%-51s] %d%%" %
(epoch, total, len(train_loader.dataset),
'-' * progress + '>', progress * 2), end='')
def test(model, device, test_loader):
model.eval()
test_loss = 0
correct = 0
with torch.no_grad():
for data, target in test_loader:
# 获取实际值
data, target = data.to(device), target.to(device)
# 获取预测值
output = model(data)
# 计算 loss
test_loss += F.nll_loss(output, target, reduction='sum').item() # sum up batch loss
pred = output.argmax(dim=1, keepdim=True) # get the index of the max log-probability
correct += pred.eq(target.view_as(pred)).sum().item()
test_loss /= len(test_loader.dataset)
print('\nTest: average loss: {:.4f}, accuracy: {}/{} ({:.0f}%)'.format(
test_loss, correct, len(test_loader.dataset),
100. * correct / len(test_loader.dataset)))
def main():
epochs = 2 # 训练总轮数
batch_size = 64
torch.manual_seed(0)
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
train_loader = torch.utils.data.DataLoader(
datasets.MNIST('../data/MNIST', train=True, download=False,
transform=transforms.Compose([
transforms.ToTensor(),
transforms.Normalize((0.1307,), (0.3081,))
])),
batch_size=batch_size, shuffle=True)
test_loader = torch.utils.data.DataLoader(
datasets.MNIST('../data/MNIST', train=False, download=False, transform=transforms.Compose([
transforms.ToTensor(),
transforms.Normalize((0.1307,), (0.3081,))
])),
batch_size=1000, shuffle=True)
model = Net().to(device)
optimizer = torch.optim.SGD(model.parameters(), lr=0.025, momentum=0.9)
for epoch in range(1, epochs + 1):
train(model, device, train_loader, optimizer, epoch)
test(model, device, test_loader)
# torch.save(model.state_dict(), "mnist_cnn.pt")
main()
结果:
至此基于 Pytorch 的网络训练与评估就讲解完毕了,不知道你有没有理解呢?
- 更多技术文章和学习资料,请关注我的公众号:【集成电路设计教程】
- 全平台统一:【雪天鱼】