pytorch实现cnn&图像分类器
1 pytorch实现神经网络
1.1 定义网络
从基类 nn.Module
继承过来,必须重载 def __init__()
和 def forward()
class Net(nn.Module):
def __init__(self): #网络结构
super(Net, self).__init__()
# 1 input image channel, 6 output channels, 5x5 square convolution
# kernel
self.conv1 = nn.Conv2d(1, 6, 5)
self.conv2 = nn.Conv2d(6, 16, 5)
# an affine operation: y = Wx + b
self.fc1 = nn.Linear(16 * 5 * 5, 120)
self.fc2 = nn.Linear(120, 84)
self.fc3 = nn.Linear(84, 10)
def forward(self, x): #前向传播
# Max pooling over a (2, 2) window
# 经过relu激活函数,再经过max_pool池化
x = F.max_pool2d(F.relu(self.conv1(x)), (2, 2))
# If the size is a square you can only specify a single number
x = F.max_pool2d(F.relu(self.conv2(x)), 2)
# 铺平
x = x.view(-1, self.num_flat_features(x))
x = F.relu(self.fc1(x))
x = F.relu(self.fc2(x))
x = self.fc3(x)
return x
def num_flat_features(self, x):
size = x.size()[1:] # all dimensions except the batch dimension
num_features = 1
for s in size:
num_features *= s
return num_features
1.2 parameters
Parameter - A kind of Tensor, that is automatically registered as a parameter when assigned as an attribute to a Module.
可以认为是一个 tensor,里面存的是每一层的参数
params = list(net.parameters())
print(len(params))
print(params[0].size()) # conv1's .weight
1.3 调用反向传播
其实就是一个自动求导的过程
input = torch.randn(1, 1, 32, 32)
out = net(input)
net.zero_grad() # 梯度清零
out.backward(torch.randn(1, 10)) #调用反向传播,括号里面的是贡献系数
1.4 损失函数
一个简单常用的损失函数,均方误差,nn.MSELoss
output = net(input)
target = torch.randn(10) # a dummy target, for example
target = target.view(1, -1) # make it the same shape as output
criterion = nn.MSELoss()
loss = criterion(output, target)
print(loss)
1.5 更改权重
反向传播应该从损失函数开始一层一层求导回去,然后更改权重
考虑正向传播
input -> conv2d -> relu -> maxpool2d -> conv2d -> relu -> maxpool2d
-> view -> linear -> relu -> linear -> relu -> linear
-> MSELoss
-> loss
之前用c++实现过反向传播,底层其实很简单,就链式法则求导求回去然后更改权重就好了
而 pytorch 里面自动给你求导了
所以直接调用 loss.backward()
即可
net.zero_grad() # zeroes the gradient buffers of all parameters
print('conv1.bias.grad before backward')
print(net.conv1.bias.grad)
loss.backward()
print('conv1.bias.grad after backward')
print(net.conv1.bias.grad)
输出:
conv1.bias.grad before backward
tensor([0., 0., 0., 0., 0., 0.])
conv1.bias.grad after backward
tensor([-0.0054, 0.0011, 0.0012, 0.0148, -0.0186, 0.0087])
最后就是更新神经网络的参数,更新规则有很多,类似于 SGD, Nesterov-SGD, Adam, RMSProp, 等。torch.optim 中实现了所有的方法。只需要会调库
import torch.optim as optim
#创建并定义一个optimizer
optimizer=optim.SGD(net.parameters(),lr=0.01)
#在训练过程中:
optimizer.zero_grad() # 清空梯度
output = net(input)
loss = criterion(output, target)
loss.backward()
optimizer.step() # 更新权重
2 pytorch 实现图像分类
图像分类器的实现步骤:
- 载入图像与预处理,变成我们愿意看到的 tensor
- 定义卷积神经网络以及损失函数
- 训练
- 测试
2.1 数据加载和预处理 Dataset & DataLoader
这部分东西还挺多的,特别是之后要会加载自己的数据集,细学一下
2.1.1 Dataset & DataLoader
在深度学习中训练模型都是小批量小批量地优化训练的,即每次都会从原数据集中取出一小批量进行训练,完成一次权重更新后,再从原数据集中取下一个小批量数据,然后再训练再更新。比如最常用的小批量随机梯度下降(Mini-Batch Gradient Descent,MBGD)。
毕竟原数据集往往很大,不可能一次性的全部载入模型,只能一小批一小批地载入。训练完了就扔了,再加载下一小批。
在PyTorch的torch.utils.data包中定义了两个类Dataset和DataLoader,这两个类就是用来批量地加载数据的。
看看其用法:
2.1.2 准备数据
在使用Dataset和DataLoader之前需要先准备好数据,这里随即构造了一段数据:
# 自己编造一个数据集
import pandas as pd
import numpy as np
data = np.random.rand(128,3)
data = pd.DataFrame(data, columns=['feature_1', 'feature_2', 'label'])
数据形式如下:
或者,如果有数据,也可以这样读取:
data = pd.read_csv('data/diabetes.csv') # 'data/diabetes.csv' 是我的数据的路径
2.1.3 写一个简单的数据加载器
import numpy as np
import pandas as pd
import torch
# utils是工具包
from torch.utils.data import Dataset # Dataset是个抽象类,只能用于继承
from torch.utils.data import DataLoader # DataLoader需实例化,用于加载数据
class MyDataset(Dataset): # 继承Dataset类
def __init__(self, df):
# 把数据和标签拿出来
# value返回为np的arry, 转化成tensor
self.x_data = torch.tensor(df.iloc[:,:2].values)
#等价于 self.x_data = torch.tensor(df[['feature_1', 'feature_2']].values)
self.y_data = torch.tensor(df.iloc[:,2].values)
# 等价于 self.y_data = torch.tensor(df[['label']].values)
# 数据集的长度
self.length = len(self.y_data)
# 下面两个魔术方法比较好写,直接照着这个格式写就行了
def __getitem__(self, index): # 参数index必写
return self.x_data[index], self.y_data[index]
def __len__(self):
return self.length # 只需返回数据集的长度即可
# 实例化
my_dataset = MyDataset(data)
train_loader = DataLoader(dataset=my_dataset, # 要传递的数据集
batch_size=32, #一个小批量数据的大小是多少
shuffle=True, # 数据集顺序是否要打乱,一般是要的。测试数据集一般没必要
num_workers=0) # 需要几个进程来一次性读取这个小批量数据,默认0,一般用0就够了,多了有时会出一些底层错误。
2.1.4 使用数据加载器来训练模型
for epoch in range(100):
# ---------------主要看这两行代码------------------
for i, data in enumerate(train_loader):
# 1. 数据准备
inputs, labels = data
# ---------------主要看这两行代码------------------
# 2. 前向传播
y_pred = model(inputs)
loss = criterion(y_pred, labels)
# 3. 反向传播
loss.backward()
# 4. 权重/模型更新
optimizer.step()
# 5. 梯度清零
optimizer.zero_grad()
2.1.5 展示一些训练图片
挖个坑,还不太会
2.2 加载并归一化 CIFAR10
使用 torchvision ,用它来加载 CIFAR10 数据非常简单。
import torch
import torchvision
import torchvision.transforms as transforms
torchvision 数据集的输出是范围在[0,1]之间的 PILImage,我们将他们转换成归一化范围为[-1,1]之间的张量 Tensors。
transform = transforms.Compose(
[transforms.ToTensor(),
transforms.Normalize((0.5, 0.5, 0.5), (0.5, 0.5, 0.5))])
trainset = torchvision.datasets.CIFAR10(root='./data', train=True,
download=True, transform=transform)
trainloader = torch.utils.data.DataLoader(trainset, batch_size=4,
shuffle=True, num_workers=2)
testset = torchvision.datasets.CIFAR10(root='./data', train=False,
download=True, transform=transform)
testloader = torch.utils.data.DataLoader(testset, batch_size=4,
shuffle=False, num_workers=2)
classes = ('plane', 'car', 'bird', 'cat',
'deer', 'dog', 'frog', 'horse', 'ship', 'truck')
2.3 定义一个神经网络
现在我们想写一个网络来训练出一个分类器,然后实现 CIFAR10 数据的分类
import torch.nn as nn
import torch.nn.functional as F
class Net(nn.Module):
def __init__(self):
super(Net, self).__init__()
self.conv1 = nn.Conv2d(3, 6, 5) #3通道 6个卷积核 核大小为5*5
self.pool = nn.MaxPool2d(2, 2)
self.conv2 = nn.Conv2d(6, 16, 5)
self.fc1 = nn.Linear(16 * 5 * 5, 120)
self.fc2 = nn.Linear(120, 84)
self.fc3 = nn.Linear(84, 10)
def forward(self, x):
x = self.pool(F.relu(self.conv1(x)))
x = self.pool(F.relu(self.conv2(x)))
x = x.view(-1, 16 * 5 * 5) #这里需要铺平
x = F.relu(self.fc1(x))
x = F.relu(self.fc2(x))
x = self.fc3(x)
return x
net = Net() #实例化
2.4 定义损失函数和优化器
损失函数选择分类交叉熵Cross-Entropy
优化器选择 SGD
import torch.optim as optim
criterion = nn.CrossEntropyLoss()
optimizer = optim.SGD(net.parameters(), lr=0.001, momentum=0.9)
2.5 训练网络
for epoch in range(2): # loop over the dataset multiple times
running_loss = 0.0
for i, data in enumerate(trainloader, 0):
# 取 1 batch的数据来训练
inputs, labels = data
# 优化器中的梯度清零
optimizer.zero_grad()
# forward + backward + optimize
outputs = net(inputs) #前向传播
loss = criterion(outputs, labels) #损失函数
loss.backward() #反向传播
optimizer.step() #更修权重
# print statistics
running_loss += loss.item()
if i % 2000 == 1999: # print every 2000 mini-batches
print('[%d, %5d] loss: %.3f' %
(epoch + 1, i + 1, running_loss / 2000))
running_loss = 0.0
print('Finished Training')
2.6 测试网络
(之前程设课手搓的cnn在测试这一部分做的就挺烂的23333
测试就是在测试集上跑前向传播,然后看看表现
correct = 0
total = 0
with torch.no_grad():
for data in testloader:
images, labels = data
outputs = net(images)
_, predicted = torch.max(outputs.data, 1)
total += labels.size(0)
correct += (predicted == labels).sum().item()
print('Accuracy of the network on the 10000 test images: %d %%' % (
100 * correct / total))