啊哈~花一天快速上手Pytorch(可能是全网最全流程从0到部署)

一起养成写作习惯!这是我参与「掘金日新计划 · 4 月更文挑战」的第3天,点击查看活动详情

前言

刚好有空顺便整理一下以前学习pytorch的笔记,做一个梳理,便于后面的学习~ 在学习pytorch之前,建议可以先掌握numpy,本质上来说,最早的版本来说,pytorch其实是numpy的增强,让矩阵可以在GPU里面运行,之后具备自动求导的特性,也就是咱们的梯度,学过线性回归的朋友应该知道,我们是通过损失函数来计算修正我们的未知参数的,那么在这里手算的话我们就需要求导,那么在pytorch里面肯定不是直接求导,而是通过记录变化值,也就是通过当前参数的变化值来进行反向修改。那么这个东西就叫做梯度,可以正向传播,也可以反向传播。 之后就是pytorch官方给咱们提供的一些API,例如自制数据集,常用神经网络,CNN 卷积核等。

环境

这里注意一下,我的环境是Windows10,N卡,装的pytorch版本是GPU版本,CUDA驱动版本是10.1(10.2也是一样的)

image.png

可以看到,我这边的显卡是GTX1650 没有显卡的朋友不要慌,我们可以使用谷歌实验室,当然你得会那啥。免费,一个礼拜30个小时的使用时长,代码你可以在本地写好,然后改成GPU版本,放到实验平台先跑一下,能过,然后在完整地训练一下。

image.png

Tensor初识

tensor 与 numpy

在我们的pytorch包括TensorFlow里面经常会出现Tensor这个词,这个其实就是它的基本数据结构,这个玩意其实和我们的numpy的那个结构是一样的,其实就是那个结构,只是他现在叫Tensor,区别就是在我们的深度学习框架里面这个Tensor可以做GPU加速!你们可以先感受一下

image.png

所以其实tensor的一些操作(除了CURD)基本上和numpy其实差不多,而numpy又具备了很多list的特性,所以tensor也一样

image.png

所以python就是老流氓嘛,用python写算法直接开挂。

在flink里面我想用元组,我还得调用scale封装的API Tuple,python直接一个小括号搞定,直接耍流氓。

当然回到主题,我们说tensor还能够在GPU里面运行,那我们来看看例子。

image.png

现在我们的代码就已经在我们的GPU里面了。那么除此之外还有什么差别嘛,有的。梯度

这个梯度 就是我们高数里面的内容。Pytorch可以自动求导,主要是在神经网络里面需要反向传播嘛。(好吧不太理解神经网络的话,这个确实不好理解,我前面也有博文提到了这个,应该是算是说的比较明白了,也是在本专栏)

tensor 使用

基本使用

这部分内容不多,也不复杂,很多和numpy类似,具体的可以查看这篇文章

Tensor 常用操作

我这里主要说说,不一样的细节。

numpy与tensor转换

我们的numpy和tensor是可以相互转换的。

image.png

但是注意两点,第一 只有都在CPU上的才能相互转换 第二点 直接使用from_numpy的是指向了同一个地址

如果想创建新的

b = torch.tensor(a)

image.png

有“_”尾巴的函数

在pytorch当中你会见到很多这样的类型的函数add_() 最后面有个“_“

这个函数的意思是说,把处理后的值进行覆盖。例如

image.png

梯度

梯度使用

这个是tensor里面很重要的东西之一。 顾名思义就是求梯度,先来一个例子你们就明白了。


import torch

a = torch.tensor([[1,1,4,4],[4,4,4,4]],requires_grad=True,dtype=torch.float) #初始化的tensor a

b = a**2 # 等价与 x ^2

c = b.sum() # 求和

print(c)

c.backward() # 反向传递(相当于求导)
print(a.grad) #结果,指的是有 a 到 c 的变化的梯度
复制代码

image.png

这个我其实可以解释一下,看好了

首先是 a 我们把a想象成一个函数

A(x) = {[X1,X2,4*(X3),4*(X4)],[...]}
复制代码

现在我们乘以^2 于是就变成了

A^2(x) = {[X1^2,X2^2,4*(X3^2)...]}

复制代码

后面我们求和 于是又变成了

Sum(A^2) = {[X1^2+X2^2+4*(X3^2) ...])
复制代码

对这个函数求偏导,然后再还原成矩阵的样子,不就是

image.png

这里开始注意一个细节,那就是,我们是a -- >c的过程的反向传播,也就是a --> c的梯度,a 是我们的根节点,只有a才会有我们的梯度。其实你可以这样理解,b其实是a的引用,由于a的变化才会产生梯度。 不然你可以看看b 的梯度。

之所以要说这个是因为一方面是我们的后面要用,还有一方面是我们关于 tensor的复制

取消梯度

有时候我们不想要那个梯度,因为这个Tensor可能只是一个普通变量。 于是你就可以直接使用 detach()来取消梯度。很多别的博客也说这个是个复制的函数,但是这个我认为是不准确的,因为这个玩意 其实只是取消了梯度,两者的内存指向还是一样的。不过比较特殊的是,例如 b = a.detach() 此时b是a的地址引用,在使用b的时候是没有梯度的,但是使用a的时候本身还是有梯度的,所以严格意义上来说并不是复制。

image.png

当你声明detach()的时候就不会有了,此外

import torch
import numpy as np

a = torch.tensor([[1,1,4,4],[4,4,4,4]],requires_grad=True,dtype=torch.float)
a.requires_grad_(False) # 等价 torch.tensor(数据,requires_grad=True)
a.detach()
b = a**2
f = a.detach()
a.detach()
c = b.sum()
复制代码

image.png

直接不让,也行,或者

image.png

复制tensor

之所以要先说梯度,原因是一方面要用,还有一方面是复制的时候要注意,还记得深度拷贝嘛,假设我们要用的话.

首先我们想要克隆或者深度拷贝,一个玩意。我们注意,前面我们不是说只要叶子节点最后才能拿到梯度吗。我们想要拷贝使用clone() 或者 copy_() 都类似(前者常用)

image.png

所以有时候为了拿到梯度,让两个变量彻底无关,所以我们要

>>> a = torch.tensor(1.0, requires_grad=True)
>>> b = a.clone().detach()
复制代码

数据加载与转换

通过前面的一些介绍的话,我们大概知道了我们的pytorch的tensor的一些基本概念,还有咱们梯度和tensor复制时的一些细节,tensor和numpy在很大程度上很像,在某些场合我们甚至可以直接使用tensor来进行运算。那么现在我们来说说pytorch的一些基本使用。

毕竟我们使用pytorch是用来搭建我们的神经网络,进行深度学习的。那么在机器学习小概述里面说过,深度学习其实也是我们机器学习的一种分支,也就是特殊一点的机器学习。那么先前sklearn的机器学习和aruze的云平台的机器学习步骤大致分为了五部曲,那么在pytorch里面其实也是类似,只是算法部分换成了比较抽象的神经网络。

所以我们可以把pytorch大致分为这几块

在这里插入图片描述 那么在这里主要将的是数据的加载,与转换。

类型转换

一开始我们说了,tensor可以将numpy的数据进行转换,但是有时候我们需要处理的可能是文本,或者图片,声音。所以我们需要一个转换器(当然你也可以转成numpy然后再转换为tensor但是那闲的慌才那么干)

这里使用工具包

tensorvision

例如我们对图片进行转换。

在这里插入图片描述 我们发现这工具包下面还有很多内容,Totensor()可以直接进行转换(看见源码还有说明)在这里插入图片描述

在这里插入图片描述 在这里我们就轻松完成了转化。

Compose “链式转化”

有时候我们可能需要进行多次转化,例如我们需要对一个图片先改变尺寸,然后进行转化。那么这个时候为了避免重复代码,所以此时我们还能这样做。


from torchvision import transforms

tensor_to = transforms.ToTensor()
compose = transforms.Compose([tensor_to,])
image = Image.open("train/1/0BGHNV6P.jpg")

img = compose(image)
print(img)


复制代码

那么这里还有其他的方式,我就不说了,你pycharm一点都出来了,还有注释。 类型转换的话其实很简单,而且对应的情况也比较多,我这边真不好说明。

数据处理

我们都知道,机器学习其实都离不开数据,数据集。那么对于一些比较出名的网络模型,或者是数据集,在pytorch里面都提供了自动下载的工具。

自带数据集

这个是指pytorch会自动通过爬虫来下载数据集合,然后给我们封装好。 这个也是使用tenssorvision 例如下载CIFAR10数据集

train_set = torchvision.datasets.CIFAR10(root="./dataset",train=True,download=True)
tese_set = torchvision.datasets.CIFAR10(root="./dataset",train=False,download=True)

复制代码

直接搞的,但是注意的是,这里得到的数据集不是tensor类型的,我们还要进行类型转换

from torchvision import transforms

trans = transforms.Compose([transforms.ToTensor()])
dataset = torchvision.datasets.CIFAR10(root="./dataset",train=False,transform=trans,download=True)
复制代码

数据加载

之后就是我们的数据加载 这里的话使用的就是utils下面的工具了

from torch.utils.data import DataLoader
from torchvision import transforms

trans = transforms.Compose([transforms.ToTensor()])
dataset = torchvision.datasets.CIFAR10(root="./dataset",train=False,transform=trans,download=True)

dataloader = DataLoader(dataset,batch_size=64)
复制代码

这里主要介绍一些DataLoader的参数。

在这里插入图片描述

自定义获取数据

这个的话就比较原始,就是有时候我们需要自己加载数据集,举个例子。 在这里插入图片描述 这个就是从网上下载的数据集,现在要把这个导入到我们的pytorch里面。 这个文件夹是标签名,在这个数据集里面。1 是 1块钱的图片 100是一百块钱的图片。

我这里先直接给出代码

from torch.utils.data import Dataset,DataLoader
from torchvision import transforms
import os
from PIL import Image

# 通过Dataset来获取数据
class MyDataset(Dataset):

    def __init__(self,RootDir,LabelDir):
        self.RootDir = RootDir

        self.LabelDir = LabelDir
        self.transform = transforms.ToTensor()
        self.ImagePathDir = os.path.join(self.RootDir,self.LabelDir)
        self.ImageNameItems = os.listdir(self.ImagePathDir)

    def __getitem__(self, item):
        # item 是获取某一个数据元素,懒汉模式,你要用我才给你
        ItemName = self.ImageNameItems[item]
        ImagePathItem = os.path.join(self.RootDir,self.LabelDir,ItemName)
        ItemGet = self.transform(Image.open(ImagePathItem).resize((500,500)))
        ItemLabel = self.LabelDir
        return ItemGet,ItemLabel

    def __len__(self):
        return len(self.ImageNameItems)




if __name__ =="__main__":
    RootDir = "train"
    OneYuanLabel = "1"
    HandoneYuanLabel = "100"
    OneYuanData = MyDataset(RootDir,OneYuanLabel)
    HandoneData = MyDataset(RootDir,HandoneYuanLabel)

    DataGet  = OneYuanData+HandoneData


    train_data = DataLoader(dataset=DataGet,batch_size=18,shuffle=True,num_workers=0,drop_last=True)

    for data in train_data:
        imgs,tags = data
        print(imgs.shape)



复制代码

重点是我们进行那个 继承Dataset,然后实现 __getitem()__这个魔法方法。看代码其实很简单,获取了我们路径的图片名称,然后,再调用魔法方法的时候,我们读取图片然后直接转化为tensor,这个其实和前面获取的数据是类似的,只是我们直接转化了一下,同时这里也是为什么我们要用 DataLoader,用这个可以把数据拿出来,而不是等到训练模型的时候再来,那样是很慢的。

TensorBoard 可视化工具箱

目标

这里主要分两部分,一个是如何直观的显示出我们预测的效果,或者是误差,另一个是如何进行网络的搭建,以及再次理解神经网络。

神经网络

先前我们举过一个例子,就是从线性回归来推导神经网络的工作流程。我们使用损失函数,来进行反向传播,也就是使用损失函数来进行梯度下降,我们假设 y = x2 +2 此时,我们输入的是x ,y 但是我们要猜测的是 2 和 2 也就是 y = ax + b 的 a,b 。之后对于非线性的拟合,我们还有激活函数,那么这个激活函数是什么?就是给我们的线性拟合做非线性变换。(学好神经网络,机器学习,智能优化算法你会发现大部分的数学建模题目都能用这些玩意来做,比如那个2021数学建模的A题我没看懂,C题其实不难但是懒得分析,B直接套,先来个拟合,然后我直接用傅里叶拟合,神经网络推算关系(这个做法可以有,但是过拟合是绝对的,因为数据太少,所以要有大量的误差,灵敏度分析来填坑(然鹅我忘了这个流程)),最后一个优化直接跑优化算法,然后也是有问题也要评估分析,最后一个根据你的分析带上实验步骤,其实就是分析实验不足,也就是你的模型不足,还需要那些数据来修正。虽然做法很暴力,但是比语文建模好多了,而且分析到位的话是能够拿国奖的)。

神经网络在运行的过程中其实分三大部分,第一部分是初始化,这个就是我说的先随便猜测几个参数,第二个部分是通过损失函数,进行反向传递(也就是梯度下降)来优化我们的未知参数,最后一步就是各种优化

OK,我们来看看一个基本的神经网络模型 在这里插入图片描述

还有我们上次的那个网络模型 在这里插入图片描述

其实真正完整的过程是这样的 在这里插入图片描述 但是注意哦,在实际的神经网络当中 我们的 “线性”是有双引号的,例如图像等等。如果只是说拟合的话,我们可以这样说,因为输入的参数,维度就是这样的线性的离散的。

动手搭建线性回归模型

现在,我们是时候使用pytorch搭建一个简单的线性模型了 在此我们先简单地说一下我们的API Linear 这个是我们的线性层,对比上面的图片 在这里插入图片描述

OK,现在我们直接看代码

from torch import nn
from torch.optim import SGD
import torch

class LineBack(nn.Module):
    def __init__(self):
        super().__init__()
        # y=ax+b
        self.line = nn.Linear(1,1)

    def forward(self,x):
        x = self.line(x)
        return x



myline = LineBack()

# 设置学习速度
optim = SGD(myline.parameters(),lr=0.01)

lossfunction = nn.MSELoss()

#数据生成
x = [i for i in range(10)]
y = [(i*2 + 2) for i in range(10)]


#数据转换
x = torch.tensor(x,dtype=torch.float)
x = x.reshape(-1,1) #变成10行一列
y = torch.tensor(y,dtype=torch.float)
y = y.reshape(-1,1)

for epoch in range(1,1001):

    optim.zero_grad()#清空梯度防止影响

    outputs = myline(x)

    loss = lossfunction(outputs,y)

    loss.backward()

    optim.step()

    if(epoch % 50 == 0):
        print("训练:{}次的总体误差为{}".format(epoch,loss.item()))


复制代码

然后,让我们看看效果

在这里插入图片描述

总体上还是非常简单的。

我们的y = 2*x +2

非线性模型搭建

前面我们搭建了线性模型,那么现在我们来看看非线性模型。

这里由于是非线性的,所以我们必须使用激活函数。这里使用的是rule 这个rule是非线性变幻,这个函数大概长这个样子 在这里插入图片描述

我们的神经网络本质上那就是一个拟合的过程,例如咱们使用什么傅里叶拟合,是一个道理,只不过是使用了不同的模型罢了。 不过这里面当然有很多细节需要注意。

from torch import nn
from torch.optim import SGD
import torch
import torch.nn.functional as F

class LineBack(nn.Module):
    def __init__(self):
        super().__init__()
        self.line1 = nn.Linear(1,10)
        self.line2 = nn.Linear(10,1)

    def forward(self,x):
        x = F.relu(self.line1(x)) # 这个和nn.RELU()是一样的,区别是这个可以当中函数在forward里面使用
        x = self.line2(x)
        return x



myline = LineBack()

optim = SGD(myline.parameters(),lr=0.01)

lossfunction = nn.MSELoss()

#数据生成

x = torch.linspace(-1, 1, 100) #生成-1到1的100个数字
y =  x.pow(2) + 0.2*torch.rand(x.size())
#数据转换

x = x.reshape(-1,1) #变成10行一列
y = y.reshape(-1,1)

for epoch in range(1,1001):

    optim.zero_grad()#清空梯度防止影响

    outputs = myline(x)

    loss = lossfunction(outputs,y)

    loss.backward()

    optim.step()

    if(epoch % 50 == 0):
        print("训练:{}次的总体误差为{}".format(epoch,loss.item()))


复制代码

在这里插入图片描述

TensorBoard 直观显示

前面我们在控制台显示可不好看,所以我们得华哥图,我们可以直接使用matplotlib但是在我们的tensorflow或者是pytorch里面有tensorboard这玩意可以画图,而且功能相当强大。

不过在使用之前,你需要下载

pip install tensorboard

或者使用conda,看你的环境,我是直接把pytorch环境当中本机环境了,我双系统,而且先前有 virtualenv 创建开发环境。

from torch import nn
from torch.optim import SGD
import torch
import torch.nn.functional as F
from torch.utils.tensorboard import SummaryWriter
class LineBack(nn.Module):
    def __init__(self):
        super().__init__()
        self.line1 = nn.Linear(1,10)
        self.line2 = nn.Linear(10,1)

    def forward(self,x):
        x = F.relu(self.line1(x)) # 这个和nn.RELU()是一样的,区别是这个可以当中函数在forward里面使用
        x = self.line2(x)
        return x


wirter = SummaryWriter("logs") #图形工具,logs保存路径

myline = LineBack()

optim = SGD(myline.parameters(),lr=0.01)

lossfunction = nn.MSELoss()

#数据生成

x = torch.linspace(-1, 1, 100) #生成-1到1的100个数字
y =  x.pow(2) + 0.2*torch.rand(x.size())
#数据转换

x = x.reshape(-1,1) #变成10行一列
y = y.reshape(-1,1)

for epoch in range(1,1001):

    optim.zero_grad()#清空梯度防止影响

    outputs = myline(x)

    loss = lossfunction(outputs,y)

    loss.backward()

    optim.step()

    if(epoch % 50 == 0):
        percent =(1- loss.item())*100
        wirter.add_scalar("接近度",percent,epoch)
        print("训练:{}次的总体误差为{}".format(epoch,loss.item()))


复制代码

代码如上,然后在我们的控制台输入 在这里插入图片描述 然后打开浏览器 在这里插入图片描述 后面我们再来说是如何使用Pytorch搭建CNN神经网络,已经常见的神经网络。之后我们搭建一个简单的5层神经网络CIFAR10这个最经典的神经网络,也是数据集比较小的。没办法神经网络就是要用数据来喂。

CIRAF10网络搭建

由于咱们的CIRAF10是一个CNN模型,所以在这里我们需要简单地说一下什么是CNN神经网络。

记住一句话,就是前面说的一句话,神经网络本质上就是在做拟合。 我们把这个过程想像成数学建模的过程。

CNN + 神经网络

CNN 就是建模的过程,(CNN 卷积,卷积这个概念我们稍后解释)对于一张图片,我们人类可以轻而易举地识别出来,但是对于计算机,这个它可搞不出来,所以我们必须要想办法让计算机去识别出一张图片,计算机最擅长的就是计算,最喜欢的就是矩阵,所以我们想办法把一张图片和对应的一些信息,例如咱们的这个模型,这是一个分类模型,我们想办法把图片和标签对应起来。

任何的,我们人类的可视化,可理解的内容例如文字,图片,音频都可以在计算机里面使用矩阵表示,于是问题就变成了线性代数,离散数学,微积分,高数的问题。

所以在这个过程当中,对应这个模型CIARF10来说,我们可以这样理解。 在这里插入图片描述

我们的神经网络是一个抽象的黑盒,通过训练之后,我们得到了每个神经网络节点的参数,阈值,这样我们不就可以进行简单地预测了嘛。

然后针对神经网络又有一堆优化问题。

所以 在这个过程当中,我们要解决的有两个问题,一个是神经网络模型,还有一个就是这个图片的处理。那么在这里我们使用的是CNN 卷积的处理方式。

ok,我们先来看看我们的这个CIARF10模型 image.png

卷积

卷积的概念

这里的话其实我们要做一个简单的区分,就是卷积有在数学上的定义,和我们实际上使用的卷积是有区别的,两者之间当然是㕛联系的,不然我们的这个神经网络也不会叫卷积神经网络。不过相对而言我们这里的卷积要好理解,不过不管怎么说,他们其实都是一种对数据的处理方式,不要有过多的纠结。

卷积语文含义

是的,在理解卷积之前,我们还是得看看语文的解释,虽然很多时候有些东西其实是很抽象的。但是关于卷积,它的含义在我们实际的作用当中其实就是类似的含义。

所谓卷积:别名: 褶积。它的意思其实就是将两个或者多个东西变成一个东西输出,其实就是合在一起,的意思, 其实你在这里可以理解为一种运算方式,也就是乘积,我们通过乘积来把这两个玩意合起来。卷积这个名字源于数学,其实是数学的一种运算方式。所以在这里我们先定义一下,卷积是一种特殊的运算关系。

数学表现

前面我们说了这是一种特殊的运算方式 那么问题来了,他有多特殊,可以叫他卷积。 我可以先看一下这个卷积的运算公式。

image.png

然后我们再看一下卷积的运算过程的图片

image.png

光看这个图片和运算的这个状态,我想你应该知道,就是为什么这个运算方式 叫做

那么接下来就来说说它为什么要这样运算,运算场景是什么?

案例

接下来我来举一个小小的例子

来我们现在假设,有这样一场爆破实验,我们有一个设备来观测我们的爆炸产生的冲击波。首先我们知道我们的爆炸波,是源源不断产生的(我们以纳秒为计时单位)(在一个时间内)。同时爆炸产生的波,也会随着时间衰减。

image.png

所以我们现在假设,它们之间的一个关系如下

image.png

(我们假设的图像绘制代码如下).

import matplotlib.pyplot as plt
import matplotlib.pylab as mpl


mpl.rcParams["font.sans-serif"] = ["SimHei"]
mpl.rcParams["axes.unicode_minus"] = False

fig,axes = plt.subplots(nrows=2, ncols=1, figsize=(8,6), dpi=100)

time = [i for i in range(10)]
boomStree = [(i**5) + 10 for i in time ]
boomLess = [(i-20)**2 for i in time]

axes[0].set_xticks(time)
axes[0].set_yticks([0,100,50])
axes[1].set_xticks(time)
axes[1].set_yticks([0,100,50])

axes[0].plot(time,boomStree,color='r',linestyle="-.",label="爆炸压力")

axes[1].plot(time,boomLess,color='b',linestyle="-.",label="爆炸衰减")

axes[0].legend()
axes[1].legend()
plt.show()

复制代码

现在我想要知道在某一个时刻,我的设备检测到的爆炸数值,是多少。 我们假设我就要知道第四时刻检测到的数值。

状态影响

首先我们必须知道一点,那就是我们在一段时间内,我们爆炸波,其实是不断产生,并且还会影响的,也就是说,第一个时刻产生的爆炸波,在第四时刻造成的影响还存在,只是它递减了,递减的变化是这样的。

image.png

同样的道理,第0时刻产生的爆炸波产生的影响也还存在

image.png

那么同样的我们分析一下我们第一时刻

由于我们求的是第四时刻的压力,对于第一时刻的爆炸波而言,它到第四时刻值经历了三个时刻的衰减!

image.png

我们先假设图像是离散的!

于是为了求取第四个时刻的情况,我们就会这样

image.png

运算

现在我们的实际图像是连续的,所以,我们实际上是要积分然后连乘法。 但是问题来了

image.png

看到公式,为什么是g(x-t) 那么这里其实就是涉及到我们的运算了。

image.png

然后积分即可

那么这个其实就是我们数学上面的对于卷积的定义。

图像处理领域的卷积

那么终于到了我们图像处理领域的卷积了,其实我们通过前面的数学例子知道,卷积操作,其实就是对不同的输入变为一个输出,也就是杂合了我们的关键信息。

那么对于图片处理其实也是类似的,我们的图片处理里面之所以使用卷积,或者为什么有卷积操作,他们这个卷积操作和我们的数学的卷积操作有什么类似的?

相似点

首先相似点就是,数学里面的积分其实相当于,累加没意见吧,同时我们还是用了连乘。那么在我们的图像处理里面,我们也是有连续乘法,和累加的。所以这个操作和数学里面的卷积很像,所以我们在这里也叫卷积,它也是一种特殊的操作。

图像卷积

那么在我们的图像里面是如何卷积的?

这个其实也不难,首先我们要知道,图片的构成

image.png

image.png

所以对于计算机而言,东西有点对多,所以我们必须对矩阵进行压缩,但是由于矩阵是有特殊含义的,所以我们不能随便压缩,必须有一个规则。

卷积核/卷积层

为了能够压缩好矩阵,同时让矩阵的特征不会丢失,或者说突出这个矩阵的某一个特征,我们设计了一个特殊矩阵。 这个特征矩阵是这样使用的。

image.png

我们相乘再相加。这个就和我们的得到新的矩阵,同时操作一次之后我们的矩阵大小会小一圈。

image.png

这个操作过程交叫做卷积。回到我们一开始给的模型

image.png

这个一个层叫做卷积层。 但是前面说我们要简化矩阵,这样操作之后我们只是减少了一圈,这个可不够

于是我们还有池化层

池化层

这个作用就是压缩了(保留最大特征的压缩)

image.png

但是这个还不够,矩阵太抽象了,对计算机而言 于是我们还有最后一层

全连接层

我们会把矩阵变成一维数组 然后带入神经网络去进行运算

image.png

网络搭建

在铺垫了这么多内容之后,我们可以开始来玩玩了。

卷积API

现在我们来到第一部分,就是卷积,我们有一个卷积核,来负责对数据的处理,这个卷积核实际上也是需要被优化的参数,我们可以把这些卷积操作全部看成需要被优化的参数。先来说说这边有的API。

Conv2d卷积函数

这个就是我们的卷积核。

image.png

其中这里有几个参数要注意。 先打开我们官网看看。

image.png

下面这几个是参数

image.png

这几个参数很重要,在我们构建CNN神经网络的时候。 我们来重点介绍几个参数

padding

这个参数,我们前面说过,卷积之后我们的图像会变小,所以有时候,我们可以填充一下原图像,让它变大,这样输出卷积之后的图片尺寸就不会发送一定的改变,也就是说我们的padding可以对原图像进行填充,padding_mode是指,你要填充什么,例如填充0

image.png

计算输出的大小

官方提供了一个公式用于计算输出的图像大小。

image.png

我们可以计算一下。下面构建网络的时候,我会举个例子。

MaxPool2d 池化

这个

image.png

就是这个玩意

Flatten 打平

其实就是把我们的数据进行摊平。

image.png

网络搭建

我们现在直接上代码

import torchvision
from torch import nn
import torch
from torch.utils.data import DataLoader
from torch.nn import Conv2d, MaxPool2d, Flatten, Linear, Sequential, CrossEntropyLoss
from torchvision import transforms

trans = transforms.Compose([transforms.ToTensor()])
dataset = torchvision.datasets.CIFAR10(root="./dataset",train=False,transform=trans,download=True)

dataloader = DataLoader(dataset,batch_size=64)
class MyModule(nn.Module):

    def __init__(self):
        super().__init__()

        self.model = Sequential(
            Conv2d(3, 32, kernel_size=(5, 5), padding=2),
            MaxPool2d(2),
            Conv2d(32, 32, (5, 5), padding=2),
            MaxPool2d(2),
            Conv2d(32, 64, (5, 5), padding=2),
            MaxPool2d(2),
            Flatten(),
            Linear(1024, 64),
            Linear(64, 10)

        )


    def forward(self,x):
        x = self.model(x)

        return x


if __name__ == '__main__':
    mymodule = MyModule()
    loss = torch.nn.CrossEntropyLoss()
    optim = torch.optim.SGD(mymodule.parameters(),lr=0.01)

    for data in dataloader:
        imgs,targets = data
        outputs = mymodule(imgs)
        result_loss = loss(outputs,targets)
        optim.zero_grad()
        result_loss.backward()
        optim.step()
复制代码

这个就是我们要搭建的CIFAR10模型代码。 由于数据太大,所以我这边也是先拿了训练集。

计算卷积核大小

现在我们注意一下就是

image.png

所以我们就要使用前面的公式计算一下参数

image.png

输入 (32 + 2padding - 1(5-1)-1)/1) +1= 32-4 + 2padding

所以 padding = 2 其他的参数我们直接按照默认的数值来算,你不按照也可以只要你能够保证你的输出。

所以我们最终得到模型是这样。

class MyModule(nn.Module):

    def __init__(self):
        super().__init__()

        self.model = Sequential(
            Conv2d(3, 32, kernel_size=(5, 5), padding=2),
            MaxPool2d(2),
            Conv2d(32, 32, (5, 5), padding=2),
            MaxPool2d(2),
            Conv2d(32, 64, (5, 5), padding=2),
            MaxPool2d(2),
            Flatten(),
            Linear(1024, 64),
            Linear(64, 10)

        )


    def forward(self,x):
        x = self.model(x)

        return x
复制代码

网络训练与模型加载

终于咱们到了后面的几个关键步骤了~

模型训练完整步骤

我们的训练集一般时分两个部分的,一个是专门拿给你训练的,一个是专门用来做验证的。当然你自己定义的数据集也一样类似。 总之需要两个部分,验证的那个部分的不要进入训练网络就行。

完整代码如下:有注释


import torchvision
from torch import nn
import torch
from torch.utils.data import DataLoader
from torch.nn import Conv2d, MaxPool2d, Flatten, Linear, Sequential, CrossEntropyLoss
from torchvision import transforms
from torch.utils.tensorboard import SummaryWriter

trans = transforms.Compose([transforms.ToTensor()])
#获取训练集
dataset = torchvision.datasets.CIFAR10(root="./dataset",train=True,transform=trans,download=True)
dataset2 = torchvision.datasets.CIFAR10(root="./dataset",train=False,transform=trans,download=True)
train_dataloader = DataLoader(dataset,batch_size=64)
test_dataloader = DataLoader(dataset2,batch_size=64)

test_len = len(dataset2)

class MyModule(nn.Module):

    def __init__(self):
        super().__init__()

        self.model = Sequential(
            Conv2d(3, 32, kernel_size=(5, 5), padding=2),
            MaxPool2d(2),
            Conv2d(32, 32, (5, 5), padding=2),
            MaxPool2d(2),
            Conv2d(32, 64, (5, 5), padding=2),
            MaxPool2d(2),
            Flatten(),
            Linear(1024, 64),
            Linear(64, 10)

        )


    def forward(self,x):
        x = self.model(x)

        return x


if __name__ == '__main__':
    writer = SummaryWriter()
    mymodule = MyModule()
    loss = torch.nn.CrossEntropyLoss()
    learnstep = 0.01
    optim = torch.optim.SGD(mymodule.parameters(),lr=learnstep)
    epoch = 1000

    train_step = 0 #每轮训练的次数
    mymodule.train()#模型在训练状态
    for i in range(epoch):
        print("第{}轮训练".format(i+1))
        train_step = 0
        for data in train_dataloader:
            imgs,targets = data
            outputs = mymodule(imgs)
            result_loss = loss(outputs,targets)
            optim.zero_grad()
            result_loss.backward()
            optim.step()

            train_step+=1
            if(train_step%100==0):

                print("第{}轮的第{}次训练的loss:{}".format((i+1),train_step,result_loss.item()))

        # 在测试集上面的效果
        mymodule.eval() #在验证状态
        test_total_loss = 0
        right_number = 0
        with torch.no_grad(): # 验证的部分,不是训练所以不要带入梯度
            for test_data  in test_dataloader:
                imgs,label = test_data
                outputs_ = mymodule(imgs)

                test_result_loss=loss(outputs_,label)

                right_number += (outputs_.argmax(1)==label).sum()

            # writer.add_scalar("在测试集上的准确率",(right_number/test_len),(i+1))
            print("第{}轮训练在测试集上的准确率为{}".format((i+1),(right_number/test_len)))

        if((i+1)%500==0):
            # 保存模型
            torch.save(mymodule.state_dict(),"mymodule_{}.pth".format((i+1)))
复制代码

模型保存与加载

模型的保存的话很简单,这里主要有两种方法。

一个是直接把模型和是训练好的数据也保存。

torch.save(yourmodlue,path)
modlue = torch.load(path)
复制代码

第二个方法是保存数据,你把数据加载到你的模型里面就行

torch.save(yourmodlue.state_dict(),path)
modlue.load_state_dict(torch.load(path))
复制代码

GPU训练

首先说一下的是哪些东西可以放置GPU上面 模型,数据,损失函数 只要是你一点,后面有cuda()这个提示的就可以~前提是你得先判断一下是不是可以在GPU上面跑,也就是你的本地环境行不行。

torch.cuda.is_available()

于是代码改成了这样



import torchvision
from torch import nn
import torch
from torch.utils.data import DataLoader
from torch.nn import Conv2d, MaxPool2d, Flatten, Linear, Sequential, CrossEntropyLoss
from torchvision import transforms
from torch.utils.tensorboard import SummaryWriter

trans = transforms.Compose([transforms.ToTensor()])
#获取训练集
dataset = torchvision.datasets.CIFAR10(root="./dataset",train=True,transform=trans,download=True)
dataset2 = torchvision.datasets.CIFAR10(root="./dataset",train=False,transform=trans,download=True)
train_dataloader = DataLoader(dataset,batch_size=64)
test_dataloader = DataLoader(dataset2,batch_size=64)

test_len = len(dataset2)
if(torch.cuda.is_available()):
    device = torch.device("cuda")
    print("使用GPU训练中:{}".format(torch.cuda.get_device_name()))
else:
    device = torch.device("cpu")
    print("使用CPU训练")
class MyModule(nn.Module):

    def __init__(self):
        super().__init__()

        self.model = Sequential(
            Conv2d(3, 32, kernel_size=(5, 5), padding=2),
            MaxPool2d(2),
            Conv2d(32, 32, (5, 5), padding=2),
            MaxPool2d(2),
            Conv2d(32, 64, (5, 5), padding=2),
            MaxPool2d(2),
            Flatten(),
            Linear(1024, 64),
            Linear(64, 10)

        )


    def forward(self,x):
        x = self.model(x)

        return x


if __name__ == '__main__':
    writer = SummaryWriter()
    mymodule = MyModule()
    mymodule = mymodule.to(device) #模型转移GPU
    loss = torch.nn.CrossEntropyLoss()
    learnstep = 0.01
    optim = torch.optim.SGD(mymodule.parameters(),lr=learnstep)
    epoch = 1000

    train_step = 0 #每轮训练的次数
    mymodule.train()#模型在训练状态
    for i in range(epoch):
        print("第{}轮训练".format(i+1))
        train_step = 0
        for data in train_dataloader:
            imgs,targets = data

            imgs = imgs.to(device) # 把这个img tensor也放在GPU里面
            targets =targets.to(device)

            outputs = mymodule(imgs)
            result_loss = loss(outputs,targets)
            optim.zero_grad()
            result_loss.backward()
            optim.step()

            train_step+=1
            if(train_step%100==0):

                print("第{}轮的第{}次训练的loss:{}".format((i+1),train_step,result_loss.item()))

        # 在测试集上面的效果
        mymodule.eval() #在验证状态
        test_total_loss = 0
        right_number = 0
        with torch.no_grad(): # 验证的部分,不是训练所以不要带入梯度
            for test_data  in test_dataloader:
                imgs,label = test_data

                imgs = imgs.to(device)
                label = label.to(device)

                outputs_ = mymodule(imgs)

                test_result_loss=loss(outputs_,label)

                right_number += (outputs_.argmax(1)==label).sum()

            # writer.add_scalar("在测试集上的准确率",(right_number/test_len),(i+1))
            print("第{}轮训练在测试集上的准确率为{}".format((i+1),(right_number/test_len)))

        if((i+1)%500==0):
            # 保存模型
            torch.save(mymodule.state_dict(),"mymodule_{}.pth".format((i+1)))
复制代码

然后我们简单地,测试一下,也就是跑一下

image.png

这个速度,不行,得训练到猴年马月。

“借鸡生蛋“使用Free远程平台

自家电脑哪里顶得住这个。

我们还是直接使用谷歌的平台吧,这个得那啥~只要有谷歌账号就是免费的。

而且是国外的,一些数据集下载贼快

image.png

然后睡一觉就好了。

模型使用

这个其实和我们校验的时候使用一样,我们主需要调用我们的模型,然后把你的图片搞进去。

image.png

这个是我们对于的那个下标的动物。


from PIL import Image
import torchvision
import torch
from MyModule import MyModule
path_img = "dog.jpg"

image = Image.open(path_img)

compose = torchvision.transforms.Compose([
    torchvision.transforms.Resize((32,32)),
    torchvision.transforms.ToTensor()
])



#看你是那种保存的模型
module_path = "mymodule_500.pth"
image = compose(image)
module = MyModule()
module.load_state_dict(torch.load("mymodule_500.pth"))



image = torch.reshape(image,(1,3,32,32))

module.eval()
with torch.no_grad():
    out = module(image)

    print(out.argmax(1))
复制代码

然后我们可以看到输出

image.png

然后我这边也是下载了一张图片

image.png

不过值得一提的是,我这里训练了500次的模型只是得到了62%左右的准确率

那么后面我就可以跟换数据集,对神经网络进行稍微调整~实现不同的目的。

此外我们还有很多神经网络模型,例如 VGG,那个层数太多了,而且官方有封装好了的,就在torchvision里面。

部署

最后面就是咱们的模型部署了,这里的话我就直接在Django里面部署了。

环境

我这里使用的是 Django3.2 用的是我经常使用的一个测试项目(直接有现成的嘛) 然后是直接使用html css js 写的。

项目结构

这个我是直接在原来的测试项目里面创建的。也就是这样的。 在这里插入图片描述 在这里插入图片描述 我们的代码都在这APP里面。 当然还有就是,这里使用了一张背景图片 在这里插入图片描述 其他的就没有了,基本上就那样。

前端

现在上我们的前端代码 这个就两个页面

图片上传

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>图片识别Demo</title>
</head>

<style>
    .center {

        margin: 0 auto;
        width: 80%;
        border: 5px solid #18a0ec;
        transition: all 0.9s;
        border-radius: 10px;
        background-image:url("/static/media/torchbackground.jpg") ;
        background-repeat: no-repeat;
        background-size: cover;
        -webkit-background-size: cover;
        -o-background-size: cover;
        background-position: center 0;


    }
    .centerin{
        width: 500px;
        height: 500px;
        margin: 0 auto;
        padding: 10%;

        border-radius: 10px;
    }

        button {
        margin-left: 80%;
        width: 20%;
        height: 35px;
        border-width: 0px;
        border-radius: 3px;
        background: #1E90FF;
        cursor: pointer;
        outline: none;
        font-family: Microsoft YaHei;
        color: white;
        font-size: 17px;
    }
    #info{
        width: 500px;
        /*   这个居中 */
        margin: 0 auto;
        background: #9becff;
        border-radius: 10px;
    }
    #info:hover {
      box-shadow: 0px 15px 30px rgba(0, 0, 0, 0.4);
      margin-top: 5px;
}

    .tips{

      text-align:center;
      color:#fff;
      text-shadow: #1e88e1 5px 5px 5px;
}
</style>

<body>
<div class="center">
    <div class="centerin">

        <div id="info">
              <p class="tips" style="margin: 0 auto;">
                    本Demo是基于CIARF10模型和官方数据集进行构建的10分类模型,能够识别
                    {飞机,自行车,小鸟,小猫,小狗,小鹿,青蛙,小马,船,卡车}
                    @Huterox
              </p>
        </div>
        <br>
        <br>
        <form action="{% url 'torch:hello' %}" enctype="multipart/form-data" method="POST">
            <div style="background-color:#9becff; width: 100%; height: 300px;">
                <img src="" id="showimg">
            </div>
            <br>

            <input id="file" onchange="changepic(this)" type="file" name="pic"
                   style="height: 70px; display:inline-block;width: 50%;">
            <button style="height: 40px;position: relative; margin-left: 29%;">确定</button>
        </form>

    </div>

</div>
</body>
<script>
    function changepic() {
        var reads = new FileReader();
        f = document.getElementById('file').files[0];
        reads.readAsDataURL(f);
        reads.onload = function (e) {
            var showing = document.getElementById('showimg');
            var userpic = document.getElementById('userpic');
            showing.src = this.result;
            showing.style.height = "300px";
            showing.style.width = "100%";
        };
    }
</script>

</html>
复制代码

结果显示

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Answear</title>
</head>
<style>

.show {
  margin: 100px auto;
  width: 80%;

  border: 5px solid #18a0ec;
  transition: all 0.9s;
  border-radius: 10px;


}

.show:hover {
  box-shadow: 0px 15px 30px rgba(0, 0, 0, 0.4);
  margin-top: 90px;
}
.tips{

  text-align:center;
  font: 50px helvetica,arial,sans-serif;
  color:#fff;
  text-shadow: #1e88e1 5px 5px 5px;
}




</style>
<body>
 <div >
    <div class="show">
      <p class="tips">
        {{ answear }}
      </p>
    </div>
  </div>
</body>
</html>
复制代码

后端

这个也没啥好说了,你配置好环境,就能搞了

模型部署

这里就是定义了一下我们的模型,还有模型调用。

# coding=utf-8

from torch import nn

from torch.nn import Conv2d, MaxPool2d, Flatten, Linear, Sequential


class MyModule(nn.Module):

    def __init__(self):
        super().__init__()

        self.model = Sequential(
            Conv2d(3, 32, kernel_size=(5, 5), padding=2),
            MaxPool2d(2),
            Conv2d(32, 32, (5, 5), padding=2),
            MaxPool2d(2),
            Conv2d(32, 64, (5, 5), padding=2),
            MaxPool2d(2),
            Flatten(),
            Linear(1024, 64),
            Linear(64, 10)

        )

    def forward(self,x):
        x = self.model(x)

        return x


复制代码

模型调用 在这里插入图片描述

# coding=utf-8
from PIL import Image
import torchvision
import torch
from PytorchDemo.ModuleTorch.MyModle import MyModule

def recognize_img(Image):

    compose = torchvision.transforms.Compose([
        torchvision.transforms.Resize((32, 32)),
        torchvision.transforms.ToTensor()
    ])


    module_path = "PytorchDemo/ModuleTorch/modulestate/mymodule_500.pth"
    image = compose(Image)
    module = MyModule()
    module.load_state_dict(torch.load(module_path))

    image = torch.reshape(image, (1, 3, 32, 32))

    module.eval()
    with torch.no_grad():
        out = module(image)

        return out.argmax(1).item()

复制代码

路由

主路由 在这里插入图片描述

分路由 在这里插入图片描述

业务代码

这个都有解释

#coding = utf-8
from django.http import HttpResponse
from django.shortcuts import render
import asyncio

# Create your views here.
from django.views.decorators.clickjacking import xframe_options_sameorigin
from django.views.decorators.csrf import csrf_exempt
from PIL import Image
from PytorchDemo.ModuleTorch.CIARF10 import recognize_img

async def UseCIARF10(request,image):
    # {飞机,自行车,小鸟,小猫,小狗,小鹿,青蛙,小马,船,卡车}
    #是的这个view又是我从老demo里面改的,后来才想起来这是个运算密集型任务,异步同步没区别~
    Things = {0:"飞机",1:"自行车",2:"小鸟",3:"小猫",4:"小鹿",
              5:"小狗",6:"青蛙",7:"小马",8:"船",9:"卡车"
              }

    out = recognize_img(image)

    out = Things.get(out,"No Answear")
    print(out)
    Data = {
        "answear": "Answear maybe:{}".format(out)
    }
    return Data



@csrf_exempt
@xframe_options_sameorigin
def Hello(request):
    if(request.method=="GET"):
        return render(request,"Show.html")

    elif(request.method=="POST"):
        image = request.FILES.get('pic')
        if(image):
            image = Image.open(image)


            loop = asyncio.new_event_loop()
            asyncio.set_event_loop(loop) #设置事件循环

            try:
                res = loop.run_until_complete(UseCIARF10(request,image))
            finally:
                loop.close()

            return render(request, "Answear.html",context=res)

        else:
            return render(request, "Show.html")


    return HttpResponse("NO Power")
复制代码

值得一提的是,在Django3.x支持了异步,但是对于一个view而言总体上还是同步的,sync view 对系统进行优化,当前视图遇到IO操作可以切换,为其他用户提供算力。之后操作完后一个return返回数据。

总结

到目前为止,学到这里应该就能去GitHub愉快地去嫖项目了~

猜你喜欢

转载自juejin.im/post/7082216569400459278