深度神经网络基础理解(pytorch)

深度神经网络基础理解(pytorch)


前言

随着社会的发展基于pytorch结构的深度神经网络越来越流行(分类问题,目标检测,人脸识别,目标追踪等等),现对CNN(卷积神经网络)以及基本定义与理解进行简单的论述以及针对Mnist数据分类问题代码实现与讲解,注意本文章使用pytorch框架。


提示:以下是本篇文章正文内容,

一、CNN是什么?

CNN(Convolution Neural Network)卷积神经网络,一种针对于图像处理的网络结构·,带有部分正则化性质因为相对于全连接来说可以减少大量的权重参数从而一定程度上避免过拟合。

1.1 图像是什么?
现在我们在网络下载的图片大多数是栅格图像,除此之外还有矢量图像一种不会随着对其进行放大操作而造成失真,然而我们现在的图片成像大多数是RGB格式(即三通道),而所谓的栅格图像就是说图像是由一个个像素堆积起来的,像素值的大小与拍摄图像时相机的光敏电阻有关。
注意:在python中进行读取时格式为(W(宽),H(高),C(深度)),但是在pytorch中使用的格式为(C(深度),W(宽),H(高))记得转换以便高效使用】,
深度神经网络基础理解(pytorch)

图一.RGB图像

1.2 预备知识
卷积也叫卷积操作,利用卷积核在通道上进行遍历:
深度神经网络基础理解(pytorch)

无填充(padding=0)
这个是一个通道的时候的卷积,若是多个通道只需将不同通道进行卷积后相加即可: 深度神经网络基础理解(pytorch)
无填充(padding=0)
很显然进行卷积操作后改变了原来矩阵的大小,然而有时我们想保持大小不变这时就需要padding即填充操作在图像外围添加(padding的值)行,且规定数值为0,例如: 深度神经网络基础理解(pytorch)
(padding=1)
卷积核滑动时还可以规定其步长(stride)这也可以减小原size的大小: 深度神经网络基础理解(pytorch)
(padding=0,stride=2)
在神经网络中还有一个常见的操作就是池化(pooling)包括平均池化,最大池化等等, 深度神经网络基础理解(pytorch)
(MaxPooling)
如果说不规定其步长默认为你的池化大小

二、CNN过程

2.1 A Simple Convolutional Neural Network

深度神经网络基础理解(pytorch)
(CNN)
这是MNIST数据集是新手入门深度学习计算机视觉的必经之路,该数据集为多张图片,其中为手写数字。

2 代码实现
在代码中主要分为四大模块:

Created with Raphaël 2.3.0 Prepare dataset(准备数据集) Design model using class(建立模型) Construct loss and optimizer(损失函数以及优化器) Training cycle(epoch循环)

代码如下:

#导入需要使用的相应包
import torch
from torchvision import transforms #数据预处理
from torchvision import datasets
from torch.utils.data import DataLoader #导入加载器
import torch.nn.functional as F
import torch.optim as optim
'''在pytorch中自带数据集,我们只需使用pytorch内置函数在网上下载即可。
细致步骤包括定义转换器将图片转化成tensor形式,并做归一化处理。
这里做归一化处理是由于网络对输入为0-1之间的数字训练得到的结果较好。'''

#加载数据集

#定义batch_size
batch_size = 64
#定义转换器对数据集转换成tensor形式,并用Normalize函数将数值进行归一化
transform = transforms.Compose([
    transforms.ToTensor(),
    #这里的两个数字分别为给定数据集的数据大小平均值和标准差,为大众计算结果,这样使得训练效果较好
    transforms.Normalize((0.137,), (0.3081,))
])
#加载训练集:train参数(true表示数据为训练集,false表示为测试集)
train_dataset = datasets.MNIST(root='dataset/mnist/',
                               train=True,
                               download=True,
                               transform=transform)
#生成DataLoader对象
train_loader = DataLoader(train_dataset,
                          shuffle=True,
                          batch_size=batch_size)

test_dataset = datasets.MNIST(root='dataset/mnist/',
                               train=False,
                               download=True,
                               transform=transform)
test_loader = DataLoader(test_dataset,
                          shuffle=False,
                          batch_size=batch_size)

#定义模型
'''这里我们使用五层线性全连接模型,将784(28*28)个列转化为10个属性,分别表示0-9的可能性'''
class Net(torch.nn.Module):
    def __init__(self):
        super(Net, self).__init__()
        self.l1 = torch.nn.Linear(784, 512)
        self.l2 = torch.nn.Linear(512, 256)
        self.l3 = torch.nn.Linear(256, 128)
        self.l4 = torch.nn.Linear(128, 64)
        self.l5 = torch.nn.Linear(64, 10)
    def forward(self, x):
        x = x.view(-1, 784)
        x = F.relu(self.l1(x))
        x = F.relu(self.l2(x))
        x = F.relu(self.l3(x))
        x = F.relu(self.l4(x))
        return self.l5(x)

 #定义损失函数以及优化器
'''由于是分类问题,我们采用交叉熵作为损失函数,并使用pytorch内置的SGD作为优化器。'''
#损失函数
criterion = torch.nn.CrossEntropyLoss()
#优化器选择SGD
optimizer = optim.SGD(model.parameters(), lr=0.1, momentum=0.5)

#定义训练和测试函数
def train(epoch):
	#运行损失
    running_loss = 0.0
    #这里按行进行遍历去训练,包括前向、反向以及优化
    for batch_idx, data in enumerate(train_loader, 0):
        inputs, targets = data
        #这里注意需要先将grad梯度置为0
        optimizer.zero_grad()

        outputs = model(inputs)
        loss = criterion(outputs, targets)
        loss.backward()
        optimizer.step()
        running_loss += loss.item()
        #每三百次输出训练情况
        if batch_idx % 300 == 299:
            print('[%d, %5d] loss: %.3f' % (epoch + 1, batch_idx + 1, running_loss / 300))

def test():
    correct = 0
    total = 0
    with torch.no_grad():
        for data in test_loader:
            images, labels = data
            outputs = model(images)
            _, predicted = torch.max(outputs.data, dim=1)
            total += labels.size(0)
            correct += (predicted == labels).sum().item()
        print('Accuracy on test set: %d %%' % (100 * correct / total))
#调用训练以及测试函数
if __name__ == '__main__':
    for epoch in range(10):
        train(epoch)
        test()
     

以上代码为全连接网络,将图像展开为一维的向量进行全连接,所需权重参数比较多。以下代码为直接进行卷积。

import torch
import torch.nn as nn
import torch.nn.functional as F
import torch.optim as optim
from torchvision import datasets, transforms   
# torchvision是独立于pytorch的关于图像操作的一些方便工具库。
# vision.datasets : 几个常用视觉数据集,可以下载和加载
# vision.models : 流行的模型,例如 AlexNet, VGG, ResNet 和 Densenet 以及训练好的参数。
# vision.transforms : 常用的图像操作,例如:数据类型转换,图像到tensor ,numpy 数组到tensor , tensor 到 图像等。
# vision.utils : 用于把形似 (3 x H x W) 的张量保存到硬盘中,给一个mini-batch的图像可以产生一个图像格网

print(torch.__version__)                        # 检查 pytorch 的版本

# 定义一些超参数
BATCH_SIZE=512                                  # batch_size即每批训练的样本数量
EPOCHS=20                                       # 循环次数
DEVICE=torch.device("cuda" if torch.cuda.is_available() else "cpu")     # 让torch判断是否使用GPU,即device定义为CUDA或CPU

# 下载 MNIST的数据集
# 训练集
train_loader = torch.utils.data.DataLoader(                 # vision.utils : 用于把形似 (3 x H x W) 的张量保存到硬盘中,给一个mini-batch的图像可以产生一个图像格网。
        datasets.MNIST('data', train=True, download=True,
                       transform=transforms.Compose([
                           transforms.ToTensor(),       # 图像转化为Tensor
                           transforms.Normalize((0.1307,), (0.3081,))       # 标准化(参数不明)
                       ])),
        batch_size=BATCH_SIZE, shuffle=True)            # shuffle() 方法将序列的所有元素随机排序

# 测试集
test_loader = torch.utils.data.DataLoader(
        datasets.MNIST('data', train=False, transform=transforms.Compose([
                           transforms.ToTensor(),
                           transforms.Normalize((0.1307,), (0.3081,))
                       ])),
        batch_size=BATCH_SIZE, shuffle=True)            # shuffle() 方法将序列的所有元素随机排序


# 下面我们定义一个网络,网络包含两个卷积层,conv1和conv2,
# 然后紧接着两个线性层作为输出,
# 最后输出10个维度,这10个维度我们作为0-9的标识来确定识别出的是那个数字

# 这里建议大家将每一层的输入和输出维度都作为注释标注出来,这样后面阅读代码的会方便很多
class ConvNet(nn.Module):
    def __init__(self):
        super().__init__()
        # 128x28
        self.conv1=nn.Conv2d(1,10,5)         # 10, 24x24
        self.conv2=nn.Conv2d(10, 20,3)       #128, 10x10
        self.fc1=nn.Linear(20*10*10, 500)
        self.fc2=nn.Linear(500, 10)
    def forward(self, x):
        in_size=x.size(0)		# in_size 为 batch_size(一个batch中的Sample数)
        # 卷积层 -> relu -> 最大池化
        out = self.conv1(x)     # 24
        out = F.relu(out)
        out = F.max_pool2d(out, 2, 2)  # 12
        #卷积层 -> relu -> 多行变一行 -> 全连接层 -> relu -> 全连接层 -> sigmoid
        out = self.conv2(out)  # 10
        out = F.relu(out)
        out = out.view(in_size, -1)     # view()函数作用是将一个多行的Tensor,拼接成一行。
        out = self.fc1(out)
        out = F.relu(out)
        out = self.fc2(out)
        # softmax
        out = F.log_softmax(out, dim=1)
        # 返回值 out
        return out
        
# 我们实例化一个网络,实例化后使用“.to”方法将网络移动到GPU
model = ConvNet().to(DEVICE)

# 优化器我们也直接选择简单暴力的Adam
optimizer = optim.Adam(model.parameters())


# 定义 训练函数 ,我们将训练的所有操作都封装到train函数中
def train(model, device, train_loader, optimizer, epoch):
    model.train()
    for batch_idx, (data, target) in enumerate(train_loader):
        data, target = data.to(device), target.to(device)       # CPU转GPU
        optimizer.zero_grad()               # 优化器清零
        output = model(data)                # 由model,计算输出值
        loss = F.nll_loss(output, target)   # 计算损失函数loss
        loss.backward()                     # loss反向传播
        optimizer.step()                    # 优化器优化
        if(batch_idx+1)%30 == 0:            # 输出结果
            print('Train Epoch: {} [{}/{} ({:.0f}%)]\tLoss: {:.6f}'.format(
                epoch, batch_idx * len(data), len(train_loader.dataset),
                100. * batch_idx / len(train_loader), loss.item()))
# -------------------------------------------------------------

# ---------------------测试函数------------------------------
# 测试的操作也一样封装成一个函数
def test(model, device, test_loader):
    test_loss = 0                           # 损失函数初始化为0
    correct = 0                             # correct 计数分类正确的数目
    with torch.no_grad():           # 表示不反向求导(反向求导为训练过程)
        for data, target in test_loader:    # 遍历所有的data和target
            data, target = data.to(device), target.to(device)   # CPU -> GPU
            output = model(data)            # output为预测值,由model计算出
            test_loss += F.nll_loss(output, target, reduction='sum').item()     ### 将一批的损失相加
            pred = output.max(1, keepdim=True)[1]       ### 找到概率最大的下标
            correct += pred.eq(target.view_as(pred)).sum().item()

    test_loss /= len(test_loader.dataset) 	# 总损失除数据集总数
    print('\nTest set: Average loss: {:.4f}, Accuracy: {}/{} ({:.0f}%)\n'.format(
        test_loss, correct, len(test_loader.dataset),
        100. * correct / len(test_loader.dataset)))
# ---------------------------------------------------------------

# 下面开始训练,这里就体现出封装起来的好处了,只要写两行就可以了
# 整个数据集只过一遍
for epoch in range(1, EPOCHS + 1):
    train(model, DEVICE, train_loader, optimizer, epoch)
    test(model, DEVICE, test_loader)

若对其函数功能还有疑义请参考:Harry嗷

总结

以上就是我所理解的内容,本文仅仅简单介绍了CNN的基础。
上一篇:batch batch-size


下一篇:Pytorch中对RNN输入和输出的形状总结