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))
posted @ 2023-08-01 22:24  缙云山车神  阅读(82)  评论(0编辑  收藏  举报