Python 官方文档:入门教程 => 点击学习
目录 1. Pytorch环境的配置及安装 如何管理项目环境? 如何看自己电脑cuda版本? 安装Pytorch 2. Python编辑器的选择、安装及配置 PyCharm PyCharm神器 Jupyter(可交互) 3. Pyth
目录
三种运行方式(PyCharm、PyCharm的Python控制台、Jupyter Notebook)的适用场景:
PyTorch 读取数据涉及两个类:Dataset & Dataloader
8. 图像变换,torchvision中transforms的使用
如何把数据集(多张图片)和 transforms 结合在一起
Transformer Layers(特定网络中使用,自学)
18. 神经网络 - 搭建小实战和 Sequential 的使用
用 Sequential 搭建,实现上图 CIFAR10 model 的代码
如何在之前写的神经网络中用到 Loss Function(损失函数)
首先需要下载Anaconda(包含了大量的package/工具包)
深度学习离不开显卡(Tensorflow/Pytorch支持英伟达的显卡),起到训练加速的作用
一个package相当于一个工具包,把环境理解成一个房子,初始环境base,不同环境可以有不同版本的工具包,切换环境即可
conda指令创建pytorch环境(如果开了代理服务器,务必关闭!!!)
以下命令在 Anaconda Prompt 中输入:
conda create -n pytorch python=3.8 # -n是name的意思
conda activate pytorch # 激活环境
左边括号里的环境由base变成了pytorch(环境名称)
看一下环境中的工具包:
pip list
并没有pytorch,接下来:安装pytorch
网址:PyTorch
在cmd里输入:
nvidia-smi
找到自己适合的版本,复制命令:
在刚刚激活了pytorch环境的窗口里输入:
conda install pytorch torchvision torchaudio cudatoolkit=11.3 -c pytorch
等待安装成功之后,再
pip list
可以看到已经安装成功
检验安装是否成功:先输入
Python
再输入
import torch
若没有报错则安装成功,如图
检验pytorch是否可以用GPU:
torch.cuda.is_available()
返回True即可
PyCharm: the Python IDE for Professional Developers by JetBrains
在Python工作台/控制台验证:
先输入
import torch
再输入
torch.cuda.is_available()
返回True即可,如上图
在左边输入,右边有变量的属性,很直观
---------------------------------------------------------------------------------------------------------------------------------
建议直接安装Anaconda,Jupyter会随着Anaconda一起安装
Jupyter 默认安装在 base 环境中,但是base环境中没有安装 Pytorch,导致Jupyter无法使用Pytorch,两种解决方式:
打开Anaconda Prompt是base环境,从base环境启用Jupyter需要一个package:ipykernel,如图:
输入
conda list
可以看到要用到的 package是 ipykernel
进入Pytorch环境中:
conda activate pytorch
再输入
conda list
可以发现没有 ipykernel ,安装这个 package(同样需要关闭代理服务器!!!)
conda install nb_conda
等到done之后,再输入
jupyter notebook
等到页面跳转到浏览器,New — Python[conda env:pytorch]*
同样,先输入
import torch
再输入
torch.cuda.is_available()
返回True即可,如下图
意外插曲:
网页可以正常跳转,但是会有这个窗口弹出:
解决方式:按照地址删去 pythoncom38.dll 文件即可(可能需要删多个 pythoncom38.dll 文件)
package(名称为 pytorch)就像一个工具箱,有不同的分隔区,分隔区里有不同的工具
探索工具箱,两个道具:
例:
dir(pytorch),就能看到输出1、2、3、4等分隔区;想继续探索第3个分隔区里有什么的话,dir(pytorch.3),输出会是a,b,c(3号分隔区里有a,b,c等工具),如何使用?
help(pytorch.3.a) 输出:将此扳手放在特定地方,然后拧动
---------------------------------------------------------------------------------------------------------------------------------
打开PyCharm,输入
dir(torch)
可以看到输出了大量的分隔区(或理解为更小的工具箱),ctrl+F找到cuda
看cuda分隔区里有什么,输入
dir(torch.cuda)
可以看到又输出了大量的分隔区
继续输入:
dir(torch.cuda.is_available)
可以看到输出的前后都带有双下划线__,这是一种规范,说明这个变量不容许篡改,即它不再是一个分隔区,而是一个确确实实的函数(相当于工具,可以用help()函数)
对工具使用 help():
help(torch.cuda.is_available)
注意 is_available 不带括号
返回一个布尔值(True或False),表明cuda是否可用
(1)新建文件夹 Learn_torch(右键:New Folder)
File —> Settings
(2)新建 Python文件,命名 first_demo
(3)运行程序:为该Python文件设置相应的Python解释器后,点击绿色的运行符号即可
或直接右键:
Python控制台(Python Console)是以每一行作为一个块进行执行
---------------------------------------------------------------------------------------------------------------------------------
首先在开始菜单打开anaconda的命令行,进入pytorch的conda环境,再打开Jupyter
以每一个块为一个运行整体
---------------------------------------------------------------------------------------------------------------------------------
代码是以块为一个整体运行的话,Python文件的块是所有行的代码,即在PyCharm运行时出现错误时,修改后要从头开始运行
PyCharm的Python控制台是一行一行代码运行的,即以每一行为一个块来运行的(也可以以任意行为块运行,按Shift+回车,输入下一行代码,再按回车运行)
Jupyter以任意行为块运行
Python控制台和Jupyter的好处是:某一块发生错误时,修改这一块时不会影响前面已经运行的块
PyCharm | 从头开始运行 | 通用,传播方便,适用于大型项目 | 需要从头运行 |
PyCharm的Python控制台 | 一行一行运行(也可以以任意行) | 可以显示每个变量的属性,一般调试时使用 | 出错时会破坏整体阅读性,不利于代码阅读及修改 |
Jupyter Notebook | 以任意行为块运行 | 代码阅读性较高,利于代码的阅读及修改 | 环境需要配置 |
对于神经网络训练,要从数据海洋里找到有用的数据
Dataset:提供一种方式,获取其中需要的数据及其对应的真实的 label 值,并完成编号。主要实现以下两个功能:
Dataloader:打包(batch_size),为后面的神经网络提供不同的数据形式
---------------------------------------------------------------------------------------------------------------------------------
数据集 hymenoptera_data(识别蚂蚁和蜜蜂的数据集,二分类)
---------------------------------------------------------------------------------------------------------------------------------
from torch.utils.data import Dataset
查看Dataset类的介绍:
help(Dataset)
或者:
Dataset??
Dataset 是一个抽象类,所有数据集都需要继承这个类,所有子类都需要重写 __getitem__ 的方法,这个方法主要是获取每个数据集及其对应 label,还可以重写长度类 __len__
def __getitem__(self, index): raise NotImplementedErrordef __add__(self, other): return ConcatDataset([self, other])
# 法1from PIL import Imageimg_path = "xxx"img = Image.open(img_path)img.show()# 法2:利用OpenCV读取图片,获得numpy型图片数据import cv2cv_img=cv2.imread(img_path)
安装opencv:
在 PyCharm的 Terminal 里输入(关闭代理服务器!!!)
pip install opencv-python
再
import cv2
---------------------------------------------------------------------------------------------------------------------------------
注意,路径引号前加 r 可以防止转义,或使用双斜杠
在Python控制台读取数据,可以看到图片的属性:
from PIL import Imageimg_path = r"E:\学完了学完了\PyTorch\土堆-Pytorch\Learn_torch\dataset\train\ants\0013035.jpg"img = Image.open(img_path)
输入 img.size 就可以返回属性的数值
可视化图片输入:img.show()
---------------------------------------------------------------------------------------------------------------------------------
想要获取图片地址(通过索引),需要os库
import osdir_path = "dataset/train/ants"img_path_list = os.listdir(dir_path) # 将文件夹下的东西变成一个列表
import os root_dir = "dataset/train"label_dir = "ants"path = os.path.join(root_dir, label_dir) # 把两个路径拼接在一起
完整代码:
from torch.utils.data import Datasetfrom PIL import Image #读取图片import os #想要获得所有图片的地址,需要导入os(系统库)#创建一个class,继承Dataset类class MyData(Dataset): def __init__(self,root_dir,label_dir): #创建初始化类,即根据这个类去创建一个实例时需要运行的函数 #通过索引获取图片的地址,需要先创建图片地址的list #self可以把其指定的变量给后面的函数使用,相当于为整个class提供全局变量 self.root_dir=root_dir self.label_dir=label_dir self.path=os.path.join(self.root_dir,self.label_dir) self.img_path=os.listdir(self.path) #获得图片下所有的地址 def __getitem__(self, idx): #idx为编号 #获取每一个图片 img_name=self.img_path[idx] #名称 img_item_path=os.path.join(self.root_dir,self.label_dir,img_name) # 每张图片的相对路径 img=Image.open(img_item_path) #读取图片 label=self.label_dir return img,label def __len__(self): #数据集的长度 return len(self.img_path)#用类创建实例root_dir="dataset/train"ants_label_dir="ants"bees_label_dir="bees"ants_dataset=MyData(root_dir,ants_label_dir)bees_dataset=MyData(root_dir,bees_label_dir)img, label = ants_dataset[0]img.show() # 可视化第一张图片#将ants(124张)和bees(121张)两个数据集进行拼接train_dataset=ants_dataset+bees_dataset
当label比较复杂,存储数据比较多时,不可能以文件夹命名的方式,而是以每张图片对应一个txt文件,txt里存储label信息的方式
rename_dataset.py代码如下:
import osroot_dir=r"E:\学完了学完了\PyTorch\土堆-Pytorch\Learn_torch\dataset\train"# 把原来的ants重命名为ants_imagetarget_dir="ants_image"img_path=os.listdir(os.path.join(root_dir,target_dir))label=target_dir.split('_')[0] # antsout_dir="ants_label"for i in img_path: file_name=i.split('.jpg')[0] with open(os.path.join(root_dir,out_dir,"{}.txt".format(file_name)),'w') as f: f.write(label)
运行效果如图:
transforms 在 Dataset 类中很常用,对图像进行变换,如图像要统一到某一个尺寸,或图像中的每一个数据进行类的转换
学 TensorBoard 探究:
---------------------------------------------------------------------------------------------------------------------------------
from torch.utils.tensorboard import SummaryWriter
查看一个类如何使用:在PyCharm中,按住Ctrl键,把鼠标移到类上
是一个直接向 log_dir 文件夹写入的事件文件,可以被 TensorBoard 进行解析
初始化函数:
def __init__(self, log_dir=None):
# 实例化SummaryWriter类writer = SummaryWriter("logs") # 把对应的事件文件存储到logs文件夹下
主要用到两个方法:
writer.add_image()writer.add_scalar()writer.close()
---------------------------------------------------------------------------------------------------------------------------------
在 Anaconda 命令行中激活 pytorch 环境,或直接在 PyCharm 的 Terminal 的 pytorch 环境中安装
输入:
pip install tensorboard
即可安装成功
---------------------------------------------------------------------------------------------------------------------------------
Ctrl + / 注释
def add_scalar( self, tag, scalar_value, global_step=None, walltime=None, new_style=False, double_precision=False, ):
添加一个标量数据到 Summary 当中,需要参数
例:y=x
from torch.utils.tensorboard import SummaryWriter #导入SummaryWriter类#创建实例writer=SummaryWriter("logs") #把对应的事件文件存储到logs文件夹下#两个方法# writer.add_image()# y=xfor i in range(100): writer.add_scalar("y=x",i,i)writer.close()
运行后多了一个logs文件夹,下面是TensorBoard的一些事件文件,如图:
如何打开事件文件?
在 Terminal 里输入:
tensorboard --logdir=logs # logdir=事件文件所在文件夹名
结果如图:
为了防止和别人冲突(一台服务器上有好几个人训练,默认打开的都是6006端口),也可以指定端口,命令如下:
tensorboard --logdir=logs --port=6007
例:y=2x
from torch.utils.tensorboard import SummaryWriter #导入SummaryWriter类#创建实例writer=SummaryWriter("logs") #把对应的事件文件存储到logs文件夹下#两个方法# writer.add_image()# y=2xfor i in range(100): writer.add_scalar("y=2x",2*i,i) # 标题、y轴、x轴writer.close()
title 和 y 不一致的情况:
每向 writer 中写入新的事件,也记录了上一个事件
如何解决?
把logs文件夹下的所有文件删掉,程序删掉,重新开始
或:重新写一个子文件,即创建新的 SummaryWriter("新文件夹")
删掉logs下的文件,重新运行代码,在 Terminal 里按 Ctrl+C ,再输入命令:
tensorboard --logdir=logs --port=6007
就可以出现名字为y=2x,但实际纵坐标是y=3x数值的图像
以上即是显示 train_loss 的一个方式
---------------------------------------------------------------------------------------------------------------------------------
def add_image(self, tag, img_tensor, global_step=None):
例子:
在Python控制台输入:
# 打开控制台,其位置就是项目文件夹所在的位置# 故只需复制相对地址image_path = "data/train/ants_image/0013035.jpg"from PIL import Imageimg = Image.open(image_path)print(type(img))
---------------------------------------------------------------------------------------------------------------------------------
在Python控制台,把PIL类型的img变量转换为numpy类型(add_image() 函数所需要的图像的数据类型),重新赋值给img_array:
import numpy as npimg_array=np.array(img)print(type(img_array)) # numpy.ndarray
从PIL到numpy,需要在add_image()中指定shape中每一个数字/维表示的含义
step1:蚂蚁为例
from torch.utils.tensorboard import SummaryWriter #导入SummaryWriter类import numpy as npfrom PIL import Image#创建实例writer=SummaryWriter("logs") #把对应的事件文件存储到logs文件夹下image_path="data/train/ants_image/0013035.jpg"img_PIL=Image.open(image_path)img_array=np.array(img_PIL)print(type(img_array))print(img_array.shape) #(512,768,3) 即(H,W,C)(高度,宽度,通道)writer.add_image("test",img_array,1, dataformats='HWC') # 第1步writer.close()
结果:
step2:蜜蜂为例
from torch.utils.tensorboard import SummaryWriter #导入SummaryWriter类import numpy as npfrom PIL import Image#创建实例writer=SummaryWriter("logs") #把对应的事件文件存储到logs文件夹下image_path="data/train/bees_image/16838648_415acd9e3f.jpg"img_PIL=Image.open(image_path)img_array=np.array(img_PIL)print(type(img_array))print(img_array.shape) #(512,768,3) 即(H,W,C)(高度,宽度,通道)writer.add_image("test",img_array,2, dataformats='HWC') # 第2步writer.close()
结果:
在一个title下,通过滑块显示每一步的图形,可以直观地观察训练中给model提供了哪些数据,或者想对model进行测试时,可以看到每个阶段的输出结果
如果想要单独显示,重命名一下title即可,即 writer.add_image() 的第一个字符串类型的参数
对图片进行一些变换
from torchvision import transforms
按住Ctrl,看 transforms.py文件(工具箱),它定义了很多 class文件(工具)
搜索快捷键:
File—> Settings—> Keymap—> 搜索 structure(快捷键 Alt+7)
拿一些特定格式的图片,经过工具(class文件)后,就会输出我们想要的图片变换的结果
---------------------------------------------------------------------------------------------------------------------------------
python的用法 ——> tensor数据类型
通过 transforms.ToTensor去解决两个问题
from PIL import Imagefrom torchvision import transforms# 绝对路径 C:\Users\11842\Desktop\Learn_torch\data\train\ants_image\0013035.jpg# 相对路径 data/train/ants_image/0013035.jpgimg_path="data/train/ants_image/0013035.jpg" #用相对路径,绝对路径里的\在Windows系统下会被当做转义符# img_path_abs="C:\Users\11842\Desktop\Learn_torch\data\train\ants_image\0013035.jpg",双引号前加r表示转义img = Image.open(img_path) #Image是Python中内置的图片的库print(img) # PIL类型
从transforms中选择一个class,对它进行创建,对创建的对象传入图片,即可返回出结果
ToTensor将一个 PIL Image 或 numpy.ndarray 转换为 tensor的数据类型
# 1、Transforms该如何使用tensor_trans = transforms.ToTensor() #从工具箱transforms里取出ToTensor类,返回tensor_trans对象tensor_img=tensor_trans(img) #创建出tensor_trans后,传入其需要的参数,即可返回结果print(tensor_img)
Ctrl+P可以提示函数里需要填什么参数
在Python Console输入:
from PIL import Imagefrom torchvision import transformsimg_path= "data/train/ants_image/0013035.jpg" img = Image.open(img_path) tensor_trans = transforms.ToTensor() tensor_img = tensor_trans(img)
打开img,即用Python内置的函数读取的图片,具有的参数有:
再打开tensor_img,看一下它有哪些参数:
Tensor 数据类型包装了反向神经网络所需要的一些理论基础的参数,如:_backward_hooks、_grad等(先转换成Tensor数据类型,再训练)
---------------------------------------------------------------------------------------------------------------------------------
from PIL import Imageimg_path = "xxx"img = Image.open(img_path)img.show()
import cv2cv_img=cv2.imread(img_path)
上节课以 numpy.array 类型为例,这节课使用 torch.Tensor 类型:
from PIL import Imagefrom torch.utils.tensorboard import SummaryWriterfrom torchvision import transforms# python的用法 ——> tensor数据类型# 通过 transforms.ToTensor去解决两个问题# 1、Transforms该如何使用# 2、Tensor数据类型与其他图片数据类型有什么区别?为什么需要Tensor数据类型# 绝对路径 C:\Users\11842\Desktop\Learn_torch\data\train\ants_image\0013035.jpg# 相对路径 data/train/ants_image/0013035.jpgimg_path="data/train/ants_image/0013035.jpg" #用相对路径,绝对路径里的\在Windows系统下会被当做转义符# img_path_abs="C:\Users\11842\Desktop\Learn_torch\data\train\ants_image\0013035.jpg",双引号前加r表示转义img = Image.open(img_path) #Image是Python中内置的图片的库#print(img)writer = SummaryWriter("logs")# 1、Transforms该如何使用tensor_trans = transforms.ToTensor() #从工具箱transforms里取出ToTensor类,返回tensor_trans对象tensor_img = tensor_trans(img) #创建出tensor_trans后,传入其需要的参数,即可返回结果#print(tensor_img)writer.add_image("Tensor_img",tensor_img) # .add_image(tag, img_tensor, global_step)# tag即名称# img_tensor的类型为torch.Tensor/numpy.array/string/blobname# global_step为int类型writer.close()
运行后,在 Terminal 里输入:
tensorboard --logdir=logs
进入网址后可以看到图片:
图片有不同的格式,打开方式也不同
图片格式 | 打开方式 |
PIL | Image.open() ——Python自带的图片打开方式 |
tensor | ToTensor() |
narrays | cv.imread() ——Opencv |
---------------------------------------------------------------------------------------------------------------------------------
把不同的 transforms 结合在一起,后面接一个数组,里面是不同的transforms
Example:图片首先要经过中心裁剪,再转换成Tensor数据类型 >>> transforms.Compose([ >>> transforms.CenterCrop(10), >>> transforms.PILToTensor(), >>> transforms.ConvertImageDtype(torch.float), >>> ])
---------------------------------------------------------------------------------------------------------------------------------
内置函数 __call__ ,不用.的方式调用方法,可以直接拿对象名,加上需要的参数,即可调用方法
按 Ctrl+p,会提示需要什么参数
class Person: def __call__(self, name): #下划线__表示为内置函数 print("__call__"+"Hello "+name) def hello(self,name): print("hello"+name)person = Person()person("zhangsan")person.hello("lisi")
输出结果如下:
__call__Hello zhangsanhellolisi
---------------------------------------------------------------------------------------------------------------------------------
把 PIL Image 或 numpy.ndarray 类型转换为 tensor 类型(TensorBoard 必须是 tensor 的数据类型)
from PIL import Imagefrom torch.utils.tensorboard import SummaryWriterfrom torchvision import transformswriter = SummaryWriter("logs")img=Image.open("images/people.jpg")print(img) #可以看到类型是PIL#ToTensor的使用trans_totensor = transforms.ToTensor() #将类型转换为tensorimg_tensor = trans_totensor(img) #img变为tensor类型后,就可以放入TensorBoard当中writer.add_image("ToTensor",img_tensor)writer.close()
(运行前要先把之前的logs进行删除)运行后,在 Terminal 里输入:
tensorboard --logdir=logs
进入网址后可以看到图片:
---------------------------------------------------------------------------------------------------------------------------------
把 tensor 数据类型或 ndarray 类型转换成 PIL Image
---------------------------------------------------------------------------------------------------------------------------------
用平均值/标准差归一化 tensor 类型的 image(输入)
图片RGB三个信道,将每个信道中的输入进行归一化
output[channel] = (input[channel] - mean[channel]) / std[channel]
设置 mean 和 std 都为0.5,则 output= 2*input -1。如果 input 图片像素值为0~1范围内,那么结果就是 -1~1之间
#Normalize的使用print(img_tensor[0][0][0]) # 第0层第0行第0列trans_norm = transforms.Normalize([0.5,0.5,0.5],[0.5,0.5,0.5]) # mean,std,因为图片是RGB三信道,故传入三个数img_norm = trans_norm(img_tensor) # 输入的类型要是tensorprint(img_norm[0][0][0])writer.add_image("Normalize",img_norm)
输出结果:
tensor(0.6549)tensor(0.3098)
刷新网页:
加入step值:
#Normalize的使用print(img_tensor[0][0][0])trans_norm = transforms.Normalize([6,3,2],[9,3,5])img_norm = trans_norm(img_tensor)print(img_norm[0][0][0])writer.add_image("Normalize",img_norm,2) # 第二步writer.close()
---------------------------------------------------------------------------------------------------------------------------------
输入:PIL Image 将输入转变到给定尺寸
PyCharm小技巧设置:忽略大小写,进行提示匹配
- 一般情况下,你需要输入R,才能提示出Resize
- 我们想设置,即便你输入的是r,也能提示出Resize,也就是忽略了大小写进行匹配提示
File—> Settings—> 搜索case—> Editor-General-Code Completion-去掉Match case前的√
—>Apply—>OK
返回值还是 PIL Image
#Resize的使用print(img.size) # 输入是PIL.Imagetrans_resize = transforms.Resize((512,512))#img:PIL --> resize --> img_resize:PILimg_resize = trans_resize(img) #输出还是PIL Image#img_resize:PIL --> totensor --> img_resize:tensor(同名,覆盖)img_resize = trans_totensor(img_resize)writer.add_image("Resize",img_resize,0)print(img_resize)
---------------------------------------------------------------------------------------------------------------------------------
Compose() 中的参数需要是一个列表,Python中列表的表示形式为[数据1,数据2,...]
在Compose中,数据需要是transforms类型,所以得到Compose([transforms参数1,transforms参数2,...])
#Compose的使用(将输出类型从PIL变为tensor类型,第二种方法)trans_resize_2 = transforms.Resize(512) # 将图片短边缩放至512,长宽比保持不变# PIL --> resize --> PIL --> totensor --> tensor#compose()就是把两个参数功能整合,第一个参数是改变图像大小,第二个参数是转换类型,前者的输出类型与后者的输入类型必须匹配trans_compose = transforms.Compose([trans_resize_2,trans_totensor])img_resize_2 = trans_compose(img) # 输入需要是PIL Imagewriter.add_image("Resize",img_resize_2,1)
---------------------------------------------------------------------------------------------------------------------------------
随机裁剪,输入PIL Image
参数size:
(1)以 int 为例:
#RandomCrop()的使用trans_random = transforms.RandomCrop(512)trans_compose_2 = transforms.Compose([trans_random,trans_totensor])for i in range(10): #裁剪10个 img_crop = trans_compose_2(img) # 输入需要是PIL Image writer.add_image("RandomCrop",img_crop,i)
(2)以 sequence 为例:
#RandomCrop()的使用trans_random = transforms.RandomCrop((500,1000))trans_compose_2 = transforms.Compose([trans_random,trans_totensor])for i in range(10): #裁剪10个 img_crop = trans_compose_2(img) writer.add_image("RandomCropHW",img_crop,i)
---------------------------------------------------------------------------------------------------------------------------------
torchvision — Torchvision 0.11.0 documentation
如:COCO 目标检测、语义分割;MNIST 手写文字;CIFAR 物体识别
输入输出模块,不常用
提供一些比较常见的神经网络,有的已经预训练好,如分类、语义分割、目标检测、视频分类
torchvision提供的一些比较少见的特殊的操作,不常用
提供一些常用的小工具,如TensorBoard
---------------------------------------------------------------------------------------------------------------------------------
本节主要讲解torchvision.datasets,以及它如何跟transforms联合使用
CIFAR10 数据集包含了6万张32×32像素的彩色图片,图片有10个类别,每个类别有6千张图像,其中有5万张图像为训练图片,1万张为测试图片
#如何使用torchvision提供的标准数据集import torchvisiontrain_set=torchvision.datasets.CIFAR10(root="./dataset",train=True,download=True) #root使用相对路径,会在该.py所在位置创建一个叫dataset的文件夹,同时把数据保存进去test_set=torchvision.datasets.CIFAR10(root="./dataset",train=False,download=True)print(test_set[0]) # 查看测试集中的第一个数据,是一个元组:(img, target)print(test_set.classes) # 列表img,target = test_set[0]print(img)print(target) # 3print(test_set.classes[target]) # catimg.show()
数据集下载过慢时:
获得下载链接后,把下载链接放到迅雷中,会首先下载压缩文件tar.gz,之后会对该压缩文件进行解压,里面会有相应的数据集
采用迅雷下载完毕后,在PyCharm里新建directory,名字也叫dataset,再将下载好的压缩包复制进去,download依然为True,运行后,会自动解压该数据
没有显示下载地址时:
按住Ctrl键,查看数据集的源代码,若其中有 url地址,可直接复制到迅雷中进行下载
--------------------------------------------------------------------------------------------------------------------------------
CIFAR10数据集原始图片是PIL Image,如果要给pytorch使用,需要转为tensor数据类型(转成tensor后,就可以用tensorboard了)
transforms 更多地是用在 datasets 里 transform 的选项中
import torchvisionfrom torch.utils.tensorboard import SummaryWriter#把dataset_transform运用到数据集中的每一张图片,都转为tensor数据类型dataset_transform = torchvision.transforms.Compose([ torchvision.transforms.ToTensor()])train_set=torchvision.datasets.CIFAR10(root="./dataset",train=True,transform=dataset_transform,download=True) #root使用相对路径,会在该.py所在位置创建一个叫dataset的文件夹,同时把数据保存进去test_set=torchvision.datasets.CIFAR10(root="./dataset",train=False,transform=dataset_transform,download=True)# print(test_set[0])writer = SummaryWriter("p10")#显示测试数据集中的前10张图片for i in range(10): img,target = test_set[i] writer.add_image("test_set",img,i) # img已经转成了tensor类型writer.close()
运行后在 terminal 里输入
tensorboard --logdir="p10"
torch.utils.data — PyTorch 1.10 documentation
参数如下(大部分有默认值,实际中只需要设置少量的参数即可):
---------------------------------------------------------------------------------------------------------------------------------
# 测试数据集中第一张图片及targetimg,target = test_data[0]print(img.shape)print(target)
输出结果:
torch.Size([3, 32, 32]) #三通道,32×32大小3 #类别为3
dataset
dataloader(batch_size=4):从dataset中取4个数据
把 img 0-3 进行打包,记为imgs;target 0-3 进行打包,记为targets;作为dataloader中的返回
for data in test_loader: imgs,targets = data print(imgs.shape) print(targets)
输出结果:
torch.Size([4, 3, 32, 32]) #4张图片,三通道,32×32tensor([0, 4, 4, 8]) #4个target进行一个打包
数据是随机取的(断点debug一下,可以看到采样器sampler是随机采样的),所以两次的 target 0 并不一样
---------------------------------------------------------------------------------------------------------------------------------
# 用上节课torchvision提供的自定义的数据集# CIFAR10原本是PIL Image,需要转换成tensorimport torchvision.datasetsfrom torch.utils.data import DataLoaderfrom torch.utils.tensorboard import SummaryWriter# 准备的测试数据集test_data = torchvision.datasets.CIFAR10("./dataset",train=False,transform=torchvision.transforms.ToTensor())# 加载测试集test_loader = DataLoader(dataset=test_data,batch_size=64,shuffle=True,num_workers=0,drop_last=False)#batch_size=4,意味着每次从test_data中取4个数据进行打包writer = SummaryWriter("dataloader")step=0for data in test_loader: imgs,targets = data #imgs是tensor数据类型 writer.add_images("test_data",imgs,step) step=step+1writer.close()
运行后在 terminal 里输入:
tensorboard --logdir="dataloader"
运行结果如图,滑动滑块即是每一次取数据时的batch_size张图片:
由于 drop_last 设置为 False,所以最后16张图片(没有凑齐64张)显示如下:
---------------------------------------------------------------------------------------------------------------------------------
若将 drop_last 设置为 True,最后16张图片(step 156)会被舍去,结果如图:
---------------------------------------------------------------------------------------------------------------------------------
一个 for data in test_loader 循环,就意味着打完一轮牌(抓完一轮数据),在下一轮再进行抓取时,第二次数据是否与第一次数据一样。值为True的话,会重新洗牌(一般都设置为True)
shuffle为False的话两轮取的图片是一样的
在外面再套一层 for epoch in range(2) 的循环
# shuffle为Truefor epoch in range(2): step=0 for data in test_loader: imgs,targets = data #imgs是tensor数据类型 writer.add_images("Epoch:{}".format(epoch),imgs,step) step=step+1
结果如下:
可以看出两次 step 155 的图片不一样
Pytorch官网左侧:Python api(相当于package,提供了一些不同的工具)
关于神经网络的工具主要在torch.nn里
torch.nn — PyTorch 1.10 documentation
---------------------------------------------------------------------------------------------------------------------------------
包含6个模块:
其中最常用的是 Module 模块(为所有神经网络提供基本骨架)
CLASS torch.nn.Module #搭建的 Model都必须继承该类
模板:
import torch.nn as nnimport torch.nn.functional as Fclass Model(nn.Module): #搭建的神经网络 Model继承了 Module类(父类) def __init__(self): #初始化函数 super(Model, self).__init__() #必须要这一步,调用父类的初始化函数 self.conv1 = nn.Conv2d(1, 20, 5) self.conv2 = nn.Conv2d(20, 20, 5) def forward(self, x): #前向传播(为输入和输出中间的处理过程),x为输入 x = F.relu(self.conv1(x)) #conv为卷积,relu为非线性处理 return F.relu(self.conv2(x))
---------------------------------------------------------------------------------------------------------------------------------
Code —> Generate —> Override Methods 可以自动补全代码
例子:
import torchfrom torch import nnclass Tudui(nn.Module): def __init__(self): super().__init__() # def __init__(self): # super(Tudui, self).__init__() def forward(self,input): output = input + 1 return outputtudui = Tudui() #拿Tudui模板创建出的神经网络x = torch.tensor(1.0) #将1.0这个数转换成tensor类型output = tudui(x)print(output)
结果如下:
tensor(2.)
在下列语句前打断点:
tudui = Tudui() #整个程序的开始
然后点击蜘蛛,点击 Step into My Code,可以看到代码每一步的执行过程
torch.nn — PyTorch 1.10 documentation
Convolution Layers
Conv2d — PyTorch 1.10 documentation
torch.nn 和 torch.nn.functional 的区别:前者是后者的封装,更利于使用
点击 torch.nn.functional - Convolution functions - conv2d
可以是单个数,或元组(sH,sW) — 控制横向步进和纵向步进
当 stride = 2 时,横向和纵向都是2,输出是一个2×2的矩阵
--------------------------------------------------------------------------------------------------------------------------------
使用 torch.reshape 函数,将输入改变为要求输入的维度
实现上图的代码:
import torchimport torch.nn.functional as Finput =torch.tensor([[1,2,0,3,1], [0,1,2,3,1], [1,2,1,0,0], [5,2,3,1,1], [2,1,0,1,1]]) #将二维矩阵转为tensor数据类型# 卷积核kernelkernel = torch.tensor([[1,2,1], [0,1,0], [2,1,0]])# 尺寸只有高和宽,不符合要求print(input.shape) #5×5print(kernel.shape) #3×3# 尺寸变换为四个数字input = torch.reshape(input,(1,1,5,5)) #通道数为1,batch大小为1kernel = torch.reshape(kernel,(1,1,3,3))print(input.shape)print(kernel.shape)output = F.conv2d(input,kernel,stride=1) # .conv2d(input:Tensor, weight:Tensor, stride)print(output)
结果为:
当将步进 stride 改为 2 时:
output2 = F.conv2d(input,kernel,stride=2)print(output2)
---------------------------------------------------------------------------------------------------------------------------------
在输入图像左右两边进行填充,决定填充有多大。可以为一个数或一个元组(分别指定高和宽,即纵向和横向每次填充的大小)。默认情况下不进行填充
padding=1:将输入图像左右上下两边都拓展一个像素,空的地方默认为0
代码实现:
output3 = F.conv2d(input,kernel,stride=1,padding=1)print(output3)
输出结果如下:可以看出输出尺寸变大
torch.nn — PyTorch 1.10 documentation
一维卷积 | Applies a 1D convolution over an input signal composed of several input planes. |
二维卷积 | Applies a 2D convolution over an input signal composed of several input planes. |
三维卷积 | Applies a 3D convolution over an input signal composed of several input planes. |
图像为二维矩阵,所以讲解 nn.Conv2d
Conv2d — PyTorch 1.10 documentation
CLASS torch.nn.Conv2d(in_channels, out_channels, kernel_size, stride=1, padding=0, dilation=1, groups=1, bias=True, padding_mode='zeros', device=None, dtype=None)# in_channels 输入通道数# out_channels 输出通道数# kernel_size 卷积核大小 #以上参数需要设置#以下参数提供了默认值# stride=1 卷积过程中的步进大小# padding=0 卷积过程中对原始图像进行padding的选项# dilation=1 每一个卷积核对应位的距离# groups=1 一般设置为1,很少改动,改动的话为分组卷积# bias=True 通常为True,对卷积后的结果是否加减一个常数的偏置# padding_mode='zeros' 选择padding填充的模式
动图:
conv_arithmetic/README.md at master · vdumoulin/conv_arithmetic · GitHub
定义了一个卷积核的大小,若为3则生成一个3×3的卷积核
in_channels 和 out_channels 都为 1 时,拿一个卷积核在输入图像中进行卷积
out_channels 为 2 时,卷积层会生成两个卷积核(不一定一样),得到两个输出,叠加后作为最后输出
---------------------------------------------------------------------------------------------------------------------------------
# CIFAR10数据集import torchimport torchvisionfrom torch import nnfrom torch.nn import Conv2dfrom torch.utils.data import DataLoaderfrom torch.utils.tensorboard import SummaryWriterdataset = torchvision.datasets.CIFAR10("../data",train=False,transform=torchvision.transforms.ToTensor(),download=True) # 这里用测试数据集,因为训练数据集太大了dataloader = DataLoader(dataset,batch_size=64)# 搭建神经网络Tuduiclass Tudui(nn.Module): def __init__(self): super(Tudui, self).__init__() # 因为是彩色图片,所以in_channels=3 self.conv1 = Conv2d(in_channels=3,out_channels=6,kernel_size=3,stride=1,padding=0) #卷积层conv1 def forward(self,x): #输出为x x = self.conv1(x) return xtudui = Tudui() # 初始化网络# 打印一下网络结构print(tudui) #Tudui((conv1): Conv2d(3, 6, kernel_size=(3, 3), stride=(1, 1)))writer = SummaryWriter("../logs")step = 0for data in dataloader: imgs,targets = data #经过ToTensor转换,成为tensor数据类型,可以直接送到网络中 output = tudui(imgs) print(imgs.shape) #输入大小 torch.Size([64, 3, 32, 32]) batch_size=64,in_channels=3(彩色图像),每张图片是32×32的 print(output.shape) #经过卷积后的输出大小 torch.Size([64, 6, 30, 30]) 卷积后变成6个channels,但原始图像减小,所以是30×30的 writer.add_images("input",imgs,step) # 6个channel无法显示。torch.Size([64, 6, 30, 30]) ——> [xxx,3,30,30] 第一个值不知道为多少时写-1,会根据后面值的大小进行计算 output = torch.reshape(output,(-1,3,30,30)) writer.add_images("output",output,step) step = step + 1
运行后,在 Terminal 里启动 pytorch 环境:
conda activate pytorch
打开 tensorboard:
tensorboard --logdir=logs
打开网址 Http://localhost:6006/:
---------------------------------------------------------------------------------------------------------------------------------
---------------------------------------------------------------------------------------------------------------------------------
torch.nn — PyTorch 1.10 documentation
最常用:MaxPool2d — PyTorch 1.10 documentation
CLASS torch.nn.MaxPool2d(kernel_size, stride=None, padding=0, dilation=1, return_indices=False, ceil_mode=False)# kernel_size 池化核
注意,卷积中stride默认为1,而池化中stride默认为kernel_size
--------------------------------------------------------------------------------------------------------------------------------
Ceil_mode 默认情况下为 False,对于最大池化一般只需设置 kernel_size 即可
--------------------------------------------------------------------------------------------------------------------------------
--------------------------------------------------------------------------------------------------------------------------------
要求的 input 必须是四维的,参数依次是:batch_size、channel、高、宽
上述图用代码实现:(以 Ceil_mode = True 为例)
import torchfrom torch import nnfrom torch.nn import MaxPool2dinput = torch.tensor([[1,2,0,3,1], [0,1,2,3,1], [1,2,1,0,0], [5,2,3,1,1], [2,1,0,1,1]],dtype=torch.float32) #最大池化无法对long数据类型进行实现,将input变成浮点数的tensor数据类型input = torch.reshape(input,(-1,1,5,5)) #-1表示torch计算batch_sizeprint(input.shape)# 搭建神经网络class Tudui(nn.Module): def __init__(self): super(Tudui, self).__init__() self.maxpool1 = MaxPool2d(kernel_size=3,ceil_mode=True) def forward(self,input): output = self.maxpool1(input) return output# 创建神经网络tudui = Tudui()output = tudui(input)print(output)
运行结果如下:
--------------------------------------------------------------------------------------------------------------------------------
最大池化的目的是保留输入的特征,同时把数据量减小(数据维度变小),对于整个网络来说,进行计算的参数变少,会训练地更快
池化一般跟在卷积后,卷积层是用来提取特征的,一般有相应特征的位置是比较大的数字,最大池化可以提取出这一部分有相应特征的信息
池化不影响通道数
池化后一般再进行非线性激活
--------------------------------------------------------------------------------------------------------------------------------
import torchimport torchvisionfrom torch import nnfrom torch.nn import MaxPool2dfrom torch.utils.data import DataLoaderfrom torch.utils.tensorboard import SummaryWriterdataset = torchvision.datasets.CIFAR10("../data",train=False,download=True,transform=torchvision.transforms.ToTensor())dataloader = DataLoader(dataset,batch_size=64)# 搭建神经网络class Tudui(nn.Module): def __init__(self): super(Tudui, self).__init__() self.maxpool1 = MaxPool2d(kernel_size=3,ceil_mode=True) def forward(self,input): output = self.maxpool1(input) return output# 创建神经网络tudui = Tudui()writer = SummaryWriter("../logs_maxpool")step = 0for data in dataloader: imgs,targets = data writer.add_images("input",imgs,step) output = tudui(imgs) #output尺寸池化后不会有多个channel,原来是3维的图片,经过最大池化后还是3维的,不需要像卷积一样还要reshape操作(影响通道数的是卷积核个数) writer.add_images("output",output,step) step = step + 1writer.close()
运行后在 terminal 里输入(注意是在pytorch环境下):
tensorboard --logdir=logs_maxpool
打开网址:
Padding Layers(对输入图像进行填充的各种方式)
几乎用不到,nn.ZeroPad2d(在输入tensor数据类型周围用0填充)
nn.ConstantPad2d(用常数填充)
在 Conv2d 中可以实现,故不常用
非线性激活:给神经网络引入一些非线性的特征
非线性越多,才能训练出符合各种曲线或特征的模型(提高泛化能力)
--------------------------------------------------------------------------------------------------------------------------------
ReLU — PyTorch 1.10 documentation
输入:(N,*) N 为 batch_size,*不限制
import torchfrom torch import nnfrom torch.nn import ReLUinput = torch.tensor([[1,-0.5], [-1,3]])input = torch.reshape(input,(-1,1,2,2)) #input必须要指定batch_size,-1表示batch_size自己算,1表示是1维的print(input.shape) #torch.Size([1, 1, 2, 2])# 搭建神经网络class Tudui(nn.Module): def __init__(self): super(Tudui, self).__init__() self.relu1 = ReLU() #inplace默认为False def forward(self,input): output = self.relu1(input) return output# 创建网络tudui = Tudui()output = tudui(input)print(output)
运行结果:
跟输入对比可以看到:小于0的值被0截断,大于0的值仍然保留
--------------------------------------------------------------------------------------------------------------------------------
Sigmoid — PyTorch 1.10 documentation
输入:(N,*) N 为 batch_size,*不限制
import torchimport torchvision.datasetsfrom torch import nnfrom torch.nn import Sigmoidfrom torch.utils.data import DataLoaderfrom torch.utils.tensorboard import SummaryWriterdataset = torchvision.datasets.CIFAR10("../data",train=False,download=True,transform=torchvision.transforms.ToTensor())dataloader = DataLoader(dataset,batch_size=64)# 搭建神经网络class Tudui(nn.Module): def __init__(self): super(Tudui, self).__init__() self.sigmoid1 = Sigmoid() #inplace默认为False def forward(self,input): output = self.sigmoid1(input) return output# 创建网络tudui = Tudui()writer = SummaryWriter("../logs_sigmoid")step = 0for data in dataloader: imgs,targets = data writer.add_images("input",imgs,global_step=step) output = tudui(imgs) writer.add_images("output",output,step) step = step + 1writer.close()
运行后在 terminal 里输入:
tensorboard --logdir=logs_sigmoid
打开网址:
--------------------------------------------------------------------------------------------------------------------------------
torch.nn — PyTorch 1.10 documentation
BatchNorm2d — PyTorch 1.10 documentation
对输入采用Batch Normalization,可以加快神经网络的训练速度
CLASS torch.nn.BatchNorm2d(num_features, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True, device=None, dtype=None)# num_features C-输入的channel
# With Learnable Parametersm = nn.BatchNorm2d(100)# Without Learnable Parametersm = nn.BatchNorm2d(100, affine=False) # 正则化层num_feature等于channel,即100input = torch.randn(20, 100, 35, 45) #batch_size=20,100个channel,35x45的输入output = m(input)
---------------------------------------------------------------------------------------------------------------------------------
RNN、LSTM等,用于文字识别中,特定的网络结构
torch.nn — PyTorch 1.13 documentation
---------------------------------------------------------------------------------------------------------------------------------
特定网络结构
torch.nn — PyTorch 1.13 documentation
---------------------------------------------------------------------------------------------------------------------------------
Linear — PyTorch 1.10 documentation
--------------------------------------------------------------------------------------------------------------------------------
vgg16 model
---------------------------------------------------------------------------------------------------------------------------------
torch.flatten — PyTorch 1.10 documentation
# Example>>> t = torch.tensor([[[1, 2], [3, 4]], [[5, 6], [7, 8]]]) #3个中括号,所以是3维的>>> torch.flatten(t) #摊平tensor([1, 2, 3, 4, 5, 6, 7, 8])>>> torch.flatten(t, start_dim=1) #变为1行tensor([[1, 2, 3, 4], [5, 6, 7, 8]])
output = torch.flatten(imgs)# 等价于output = torch.reshape(imgs,(1,1,1,-1))for data in dataloader: imgs,targets = data print(imgs.shape) #torch.Size([64, 3, 32, 32]) output = torch.reshape(imgs,(1,1,1,-1)) # 想把图片展平 print(output.shape) # torch.Size([1, 1, 1, 196608]) output = tudui(output) print(output.shape) # torch.Size([1, 1, 1, 10])for data in dataloader: imgs,targets = data print(imgs.shape) #torch.Size([64, 3, 32, 32]) output = torch.flatten(imgs) #摊平 print(output.shape) #torch.Size([196608]) output = tudui(output) print(output.shape) #torch.Size([10])
--------------------------------------------------------------------------------------------------------------------------------
import torchimport torchvision.datasetsfrom torch import nnfrom torch.nn import Linearfrom torch.utils.data import DataLoaderdataset = torchvision.datasets.CIFAR10("../data",train=False,transform=torchvision.transforms.ToTensor(),download=True)dataloader = DataLoader(dataset,batch_size=64,drop_last=True)class Tudui(nn.Module): def __init__(self): super(Tudui, self).__init__() self.linear1 = Linear(196608,10) def forward(self,input): output = self.linear1(input) return outputtudui = Tudui()for data in dataloader: imgs,targets = data print(imgs.shape) #torch.Size([64, 3, 32, 32]) # output = torch.reshape(imgs,(1,1,1,-1)) # 想把图片展平 # print(output.shape) # torch.Size([1, 1, 1, 196608]) # output = tudui(output) # print(output.shape) # torch.Size([1, 1, 1, 10]) output = torch.flatten(imgs) #摊平 print(output.shape) #torch.Size([196608]) output = tudui(output) print(output.shape) #torch.Size([10])
运行结果如下:
---------------------------------------------------------------------------------------------------------------------------------
Dropout — PyTorch 1.10 documentation
在训练过程中,随机把一些 input(输入的tensor数据类型)中的一些元素变为0,变为0的概率为p
目的:防止过拟合
---------------------------------------------------------------------------------------------------------------------------------
Embedding — PyTorch 1.10 documentation
用于自然语言处理
--------------------------------------------------------------------------------------------------------------------------------
计算两个值之间的误差
torch.nn — PyTorch 1.13 documentation
--------------------------------------------------------------------------------------------------------------------------------
loss 误差大小
torch.nn — PyTorch 1.13 documentation
---------------------------------------------------------------------------------------------------------------------------------
下一节:Container ——> Sequential(序列)
Containers中有Module、Sequential等
Sequential — PyTorch 1.10 documentation
Example:
# Using Sequential to create a small model. When `model` is run,# input will first be passed to `Conv2d(1,20,5)`. The output of# `Conv2d(1,20,5)` will be used as the input to the first# `ReLU`; the output of the first `ReLU` will become the input# for `Conv2d(20,64,5)`. Finally, the output of# `Conv2d(20,64,5)` will be used as input to the second `ReLU`model = nn.Sequential( nn.Conv2d(1,20,5), nn.ReLU(), nn.Conv2d(20,64,5), nn.ReLU() )# Using Sequential with OrderedDict. This is functionally the# same as the above codemodel = nn.Sequential(OrderedDict([ ('conv1', nn.Conv2d(1,20,5)), ('relu1', nn.ReLU()), ('conv2', nn.Conv2d(20,64,5)), ('relu2', nn.ReLU()) ]))
好处:代码简洁易懂
---------------------------------------------------------------------------------------------------------------------------------
CIFAR 10:根据图片内容,识别其究竟属于哪一类(10代表有10个类别)
CIFAR-10 and CIFAR-100 datasets
第一次卷积:首先加了几圈 padding(图像大小不变,还是32x32),然后卷积了32次
几个卷积核就是几通道的,一个卷积核作用于RGB三个通道后会把得到的三个矩阵的对应值相加,也就是说会合并,所以一个卷积核会产生一个通道
任何卷积核在设置padding的时候为保持输出尺寸不变都是卷积核大小的一半
通道变化时通过调整卷积核的个数(即输出通道)来实现的,在 nn.conv2d 的参数中有 out_channel 这个参数,就是对应输出通道
kernel 的内容是不一样的,可以理解为不同的特征抓取,因此一个核会产生一个channel
from torch import nnfrom torch.nn import Conv2d, MaxPool2d, Flatten, Linearclass Tudui(nn.Module): def __init__(self): super(Tudui, self).__init__() self.conv1 = Conv2d(in_channels=3, out_channels=32, kernel_size=5, padding=2) #第一个卷积 self.maxpool1 = MaxPool2d(kernel_size=2) #池化 self.conv2 = Conv2d(32,32,5,padding=2) #维持尺寸不变,所以padding仍为2 self.maxpool2 = MaxPool2d(2) self.conv3 = Conv2d(32,64,5,padding=2) self.maxpool3 = MaxPool2d(2) self.flatten = Flatten() #展平为64x4x4=1024个数据 # 经过两个线性层:第一个线性层(1024为in_features,64为out_features)、第二个线性层(64为in_features,10为out_features) self.linear1 = Linear(1024,64) self.linear2 = Linear(64,10) #10为10个类别,若预测的是概率,则取最大概率对应的类别,为该图片网络预测到的类别 def forward(self,x): #x为input x = self.conv1(x) x = self.maxpool1(x) x = self.conv2(x) x = self.maxpool2(x) x = self.conv3(x) x = self.maxpool3(x) x = self.flatten(x) x = self.linear1(x) x = self.linear2(x) return xtudui = Tudui()print(tudui)
可以看到网络结构:
---------------------------------------------------------------------------------------------------------------------------------
核心:一定尺寸的数据经过网络后,能够得到我们想要的输出
对网络结构进行检验的代码:
input = torch.ones((64,3,32,32)) #全是1,batch_size=64,3通道,32x32output = tudui(input)print(output.shape)
运行结果:
torch.Size([64, 10])
--------------------------------------------------------------------------------------------------------------------------------
from torch import nnfrom torch.nn import Conv2d, MaxPool2d, Flatten, Linearclass Tudui(nn.Module): def __init__(self): super(Tudui, self).__init__() self.conv1 = Conv2d(in_channels=3, out_channels=32, kernel_size=5, padding=2) #第一个卷积 self.maxpool1 = MaxPool2d(kernel_size=2) #池化 self.conv2 = Conv2d(32,32,5,padding=2) #维持尺寸不变,所以padding仍为2 self.maxpool2 = MaxPool2d(2) self.conv3 = Conv2d(32,64,5,padding=2) self.maxpool3 = MaxPool2d(2) self.flatten = Flatten() #展平为64x4x4=1024个数据 # 经过两个线性层:第一个线性层(1024为in_features,64为out_features)、第二个线性层(64为in_features,10为out_features) self.linear1 = Linear(1024,64) self.linear2 = Linear(64,10) #10为10个类别,若预测的是概率,则取最大概率对应的类别,为该图片网络预测到的类别 def forward(self,x): #x为input x = self.conv1(x) x = self.maxpool1(x) x = self.conv2(x) x = self.maxpool2(x) x = self.conv3(x) x = self.maxpool3(x) x = self.flatten(x) return xtudui = Tudui()print(tudui)input = torch.ones((64,3,32,32)) #全是1,batch_size=64(64张图片),3通道,32x32output = tudui(input)print(output.shape) # torch.Size([64,1024])
看到输出的维度是(64,1024),64可以理解为64张图片,1024就是flatten之后的维度了
---------------------------------------------------------------------------------------------------------------------------------
作用:代码更加简洁
import torchfrom torch import nnfrom torch.nn import Conv2d, MaxPool2d, Flatten, Linear, Sequentialclass Tudui(nn.Module): def __init__(self): super(Tudui, self).__init__() self.model1 = Sequential( Conv2d(3,32,5,padding=2), MaxPool2d(2), Conv2d(32,32,5,padding=2), MaxPool2d(2), Conv2d(32,64,5,padding=2), MaxPool2d(2), Flatten(), Linear(1024,64), Linear(64,10) ) def forward(self,x): #x为input x = self.model1(x) return xtudui = Tudui()print(tudui)input = torch.ones((64,3,32,32)) #全是1,batch_size=64,3通道,32x32output = tudui(input)print(output.shape)
运行结果:
--------------------------------------------------------------------------------------------------------------------------------
在上述代码后面加上以下代码:
from torch.utils.tensorboard import SummaryWriterwriter = SummaryWriter("../logs_seq")writer.add_graph(tudui,input) # add_graph 计算图writer.close()
运行后在 terminal 里输入:
tensorboard --logdir=logs_seq
打开网址,双击图片中的矩形,可以放大每个部分:
torch.nn 里的 loss function 衡量误差,在使用过程中根据需求使用,注意输入形状和输出形状即可
loss 衡量实际神经网络输出 output 与真实想要结果 target 的差距,越小越好
作用:
梯度下降法:
---------------------------------------------------------------------------------------------------------------------------------
input:(N,*) N是batch_size,即有多少个数据;*可以是任意维度
CLASS torch.nn.L1Loss(size_average=None, reduce=None, reduction='mean')
import torchfrom torch.nn import L1Loss# 实际数据或网络默认情况下就是float类型,不写测试案例的话一般不需要加dtypeinputs = torch.tensor([1,2,3],dtype=torch.float32) # 计算时要求数据类型为浮点数,不能是整型的longtargets = torch.tensor([1,2,5],dtype=torch.float32)inputs = torch.reshape(inputs,(1,1,1,3)) # 1 batch_size, 1 channel, 1行3列targets = torch.reshape(targets,(1,1,1,3))loss = L1Loss()result = loss(inputs,targets)print(result)
运行结果:
tensor(0.6667)
修改上述代码中的一句即可
loss = L1Loss(reduction='sum')
运行结果:
tensor(2.)
---------------------------------------------------------------------------------------------------------------------------------
input:(N,*) N是batch_size,即有多少个数据;*可以是任意维度
CLASS torch.nn.MSELoss(size_average=None, reduce=None, reduction='mean')
import torchfrom torch import nn# 实际数据或网络默认情况下就是float类型,不写测试案例的话一般不需要加dtypeinputs = torch.tensor([1,2,3],dtype=torch.float32) # 计算时要求数据类型为浮点数,不能是整型的longtargets = torch.tensor([1,2,5],dtype=torch.float32)inputs = torch.reshape(inputs,(1,1,1,3)) # 1 batch_size, 1 channel, 1行3列targets = torch.reshape(targets,(1,1,1,3))loss_mse = nn.MSELoss()result_mse = loss_mse(inputs,targets)print(result_mse)
结果:
tensor(1.3333)
---------------------------------------------------------------------------------------------------------------------------------
适用于训练分类问题,有C个类别
例:三分类问题,person,dog,cat
这里的output不是概率,是评估分数
input 为没有进行处理过的对每一类的得分
代码:
x = torch.tensor([0.1,0.2,0.3])y = torch.tensor([1])x = torch.reshape(x,(1,3))loss_cross = nn.CrossEntropyLoss()result_cross = loss_cross(x,y)print(result_cross)
结果:
tensor(1.1019)
---------------------------------------------------------------------------------------------------------------------------------
import torchvision.datasetsfrom torch import nnfrom torch.nn import Conv2d, MaxPool2d, Flatten, Linear, Sequentialfrom torch.utils.data import DataLoaderdataset = torchvision.datasets.CIFAR10("../data",train=False,transform=torchvision.transforms.ToTensor(),download=True)dataloader = DataLoader(dataset,batch_size=1)class Tudui(nn.Module): def __init__(self): super(Tudui, self).__init__() self.model1 = Sequential( Conv2d(3,32,5,padding=2), MaxPool2d(2), Conv2d(32,32,5,padding=2), MaxPool2d(2), Conv2d(32,64,5,padding=2), MaxPool2d(2), Flatten(), Linear(1024,64), Linear(64,10) ) def forward(self,x): # x为input x = self.model1(x) return xloss = nn.CrossEntropyLoss()tudui = Tudui()for data in dataloader: imgs,targets = data # imgs为输入,放入神经网络中 outputs = tudui(imgs) # outputs为输入通过神经网络得到的输出,targets为实际输出 result_loss = loss(outputs,targets) print(result_loss) # 神经网络输出与真实输出的误差
结果:
---------------------------------------------------------------------------------------------------------------------------------
计算出每一个节点参数的梯度
在上述代码后加一行:
result_loss.backward() # backward反向传播,是对result_loss,而不是对loss
在这一句代码前打上断点(运行到该行代码的前一行,该行不运行),debug 后:
tudui ——> model 1 ——> Protected Attributes ——> _modules ——> '0' ——> bias / weight——> grad(是None)
点击Step into My Code,运行完该行后,可以发现刚刚的None有值了(损失函数一定要经过 .backward() 后才能反向传播,才能有每个需要调节的参数的grad的值)
下一节:选择合适的优化器,利用梯度对网络中的参数进行优化更新,以达到整个 loss最低的目的
当使用损失函数时,可以调用损失函数的 backward,得到反向传播,反向传播可以求出每个需要调节的参数对应的梯度,有了梯度就可以利用优化器,优化器根据梯度对参数进行调整,以达到整体误差降低的目的
torch.optim — PyTorch 1.10 documentation
---------------------------------------------------------------------------------------------------------------------------------
(1)构造
# Example:# SGD为构造优化器的算法,Stochastic Gradient Descent 随机梯度下降optimizer = optim.SGD(model.parameters(), lr=0.01, momentum=0.9) #模型参数、学习速率、特定优化器算法中需要设定的参数optimizer = optim.Adam([var1, var2], lr=0.0001)
(2)调用优化器的step方法
利用之前得到的梯度对参数进行更新
for input, target in dataset: optimizer.zero_grad() #把上一步训练的每个参数的梯度清零 output = model(input) loss = loss_fn(output, target) # 输出跟真实的target计算loss loss.backward() #调用反向传播得到每个要更新参数的梯度 optimizer.step() #每个参数根据上一步得到的梯度进行优化
---------------------------------------------------------------------------------------------------------------------------------
如Adadelta、Adagrad、Adam、RMSProp、SGD等等,不同算法前两个参数:params、lr 都是一致的,后面的参数不同
CLASS torch.optim.Adadelta(params, lr=1.0, rho=0.9, eps=1e-06, weight_decay=0)# params为模型的参数、lr为学习速率(learning rate)# 后续参数都是特定算法中需要设置的
学习速率不能太大(太大模型训练不稳定)也不能太小(太小模型训练慢),一般建议先采用较大学习速率,后采用较小学习速率
--------------------------------------------------------------------------------------------------------------------------------
以 SGD(随机梯度下降法)为例进行说明:
import torchimport torchvision.datasetsfrom torch import nnfrom torch.nn import Conv2d, MaxPool2d, Flatten, Linear, Sequentialfrom torch.utils.data import DataLoader# 加载数据集并转为tensor数据类型dataset = torchvision.datasets.CIFAR10("../data",train=False,transform=torchvision.transforms.ToTensor(),download=True)# 加载数据集dataloader = DataLoader(dataset,batch_size=1)# 创建网络名叫Tuduiclass Tudui(nn.Module): def __init__(self): super(Tudui, self).__init__() self.model1 = Sequential( Conv2d(3,32,5,padding=2), MaxPool2d(2), Conv2d(32,32,5,padding=2), MaxPool2d(2), Conv2d(32,64,5,padding=2), MaxPool2d(2), Flatten(), Linear(1024,64), Linear(64,10) ) def forward(self,x): # x为input,forward前向传播 x = self.model1(x) return x# 计算lossloss = nn.CrossEntropyLoss()# 搭建网络tudui = Tudui()# 设置优化器optim = torch.optim.SGD(tudui.parameters(),lr=0.01) # SGD随机梯度下降法for data in dataloader: imgs,targets = data # imgs为输入,放入神经网络中 outputs = tudui(imgs) # outputs为输入通过神经网络得到的输出,targets为实际输出 result_loss = loss(outputs,targets) optim.zero_grad() # 把网络模型中每一个可以调节的参数对应梯度设置为0 result_loss.backward() # backward反向传播求出每一个节点的梯度,是对result_loss,而不是对loss optim.step() # 对每个参数进行调优
可以在以下地方打断点,debug:
tudui ——> Protected Attributes ——> _modules ——> 'model1' ——> Protected Attributes ——> _modules ——> '0' ——> weight ——> data 或 grad
通过每次按箭头所指的按钮(点一次运行一行),观察 data 和 grad 值的变化
--------------------------------------------------------------------------------------------------------------------------------
在 data 循环外又套一层 epoch 循环,一次 data 循环相当于对数据训练一次,加了 epoch 循环相当于对数据训练 20 次
import torchimport torchvision.datasetsfrom torch import nnfrom torch.nn import Conv2d, MaxPool2d, Flatten, Linear, Sequentialfrom torch.utils.data import DataLoader# 加载数据集并转为tensor数据类型dataset = torchvision.datasets.CIFAR10("../data",train=False,transform=torchvision.transforms.ToTensor(),download=True)dataloader = DataLoader(dataset,batch_size=1)# 创建网络名叫Tuduiclass Tudui(nn.Module): def __init__(self): super(Tudui, self).__init__() self.model1 = Sequential( Conv2d(3,32,5,padding=2), MaxPool2d(2), Conv2d(32,32,5,padding=2), MaxPool2d(2), Conv2d(32,64,5,padding=2), MaxPool2d(2), Flatten(), Linear(1024,64), Linear(64,10) ) def forward(self,x): # x为input,forward前向传播 x = self.model1(x) return x# 计算lossloss = nn.CrossEntropyLoss()# 搭建网络tudui = Tudui()# 设置优化器optim = torch.optim.SGD(tudui.parameters(),lr=0.01) # SGD随机梯度下降法for epoch in range(20): running_loss = 0.0 # 在每一轮开始前将loss设置为0 for data in dataloader: # 该循环相当于只对数据进行了一轮学习 imgs,targets = data # imgs为输入,放入神经网络中 outputs = tudui(imgs) # outputs为输入通过神经网络得到的输出,targets为实际输出 result_loss = loss(outputs,targets) optim.zero_grad() # 把网络模型中每一个可以调节的参数对应梯度设置为0 result_loss.backward() # backward反向传播求出每一个节点的梯度,是对result_loss,而不是对loss optim.step() # 对每个参数进行调优 running_loss = running_loss + result_loss # 每一轮所有loss的和 print(running_loss)
部分运行结果:
优化器对模型参数不断进行优化,每一轮的 loss 在不断减小
实际过程中模型在整个数据集上的训练次数(即最外层的循环)都是成百上千/万的,本例仅以 20 次为例
本节主要讲解 torchvision
本节主要讲解 Classification 里的 VGG 模型,数据集仍为 CIFAR10 数据集(主要用于分类)
torchvision.models — Torchvision 0.11.0 documentation
---------------------------------------------------------------------------------------------------------------------------------
注意:必须要先有 package scipy
在 Terminal 里输入
pip list
寻找是否有 scipy,若没有的话输入
pip install scipy
(注意关闭代理!!!)
---------------------------------------------------------------------------------------------------------------------------------
下载 ImageNet 数据集:
import torchvision.datasetstrain_data = torchvision.datasets.ImageNet("../data_image_net",split='train',download=True, transform=torchvision.transforms.ToTensor())
./xxx表示当前路径下,../xxx表示返回上一级目录
多行注释快捷键:ctrl+/
运行后会报错:
RuntimeError: The dataset is no longer publicly accessible. You need to download the arcHives externally and place them in the root directory.
下载地址:
Imagenet 完整数据集下载_wendell 的博客-CSDN博客_imagenet下载
100 多个G,太大... 放弃
按住 Ctrl 键,点击 ImageNet,查看其源码:
--------------------------------------------------------------------------------------------------------------------------------
VGG 11/13/16/19 常用16和19
import torchvision.modelsvgg16_false = torchvision.models.vgg16(pretrained=False) # 另一个参数progress显示进度条,默认为Truevgg16_true = torchvision.models.vgg16(pretrained=True)print('ok')
断点打在 print('ok') 前,debug 一下,结果如图:
vgg16_true ——> classifier ——> Protected Attributes ——> modules ——> '0'(线性层) ——> weight
为 false 的情况,同理找到 weight 值:
总结:
---------------------------------------------------------------------------------------------------------------------------------
import torchvision.modelsvgg16_false = torchvision.models.vgg16(pretrained=False) # 另一个参数progress显示进度条,默认为Truevgg16_true = torchvision.models.vgg16(pretrained=True)print(vgg16_true)
输出:
VGG( (features): Sequential(# 输入图片先经过卷积,输入是3通道的、输出是64通道的,卷积核大小是3×3的 (0): Conv2d(3, 64, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))# 非线性 (1): ReLU(inplace=True)# 卷积、非线性、池化... (2): Conv2d(64, 64, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1)) (3): ReLU(inplace=True) (4): MaxPool2d(kernel_size=2, stride=2, padding=0, dilation=1, ceil_mode=False) (5): Conv2d(64, 128, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1)) (6): ReLU(inplace=True) (7): Conv2d(128, 128, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1)) (8): ReLU(inplace=True) (9): MaxPool2d(kernel_size=2, stride=2, padding=0, dilation=1, ceil_mode=False) (10): Conv2d(128, 256, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1)) (11): ReLU(inplace=True) (12): Conv2d(256, 256, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1)) (13): ReLU(inplace=True) (14): Conv2d(256, 256, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1)) (15): ReLU(inplace=True) (16): MaxPool2d(kernel_size=2, stride=2, padding=0, dilation=1, ceil_mode=False) (17): Conv2d(256, 512, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1)) (18): ReLU(inplace=True) (19): Conv2d(512, 512, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1)) (20): ReLU(inplace=True) (21): Conv2d(512, 512, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1)) (22): ReLU(inplace=True) (23): MaxPool2d(kernel_size=2, stride=2, padding=0, dilation=1, ceil_mode=False) (24): Conv2d(512, 512, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1)) (25): ReLU(inplace=True) (26): Conv2d(512, 512, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1)) (27): ReLU(inplace=True) (28): Conv2d(512, 512, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1)) (29): ReLU(inplace=True) (30): MaxPool2d(kernel_size=2, stride=2, padding=0, dilation=1, ceil_mode=False) ) (avgpool): AdaptiveAvgPool2d(output_size=(7, 7)) (classifier): Sequential( (0): Linear(in_features=25088, out_features=4096, bias=True) (1): ReLU(inplace=True) (2): Dropout(p=0.5, inplace=False) (3): Linear(in_features=4096, out_features=4096, bias=True) (4): ReLU(inplace=True) (5): Dropout(p=0.5, inplace=False)# 最后线性层输出为1000(vgg16也是一个分类模型,能分出1000个类别) (6): Linear(in_features=4096, out_features=1000, bias=True) ))
所以 out_features = 1000
---------------------------------------------------------------------------------------------------------------------------------
train_data = torchvision.datasets.CIFAR10(root="./dataset",train=True,transform=torchvision.transforms.ToTensor(),download=True)
CIFAR10 把数据分成了10类,而 vgg16 模型把数据分成了 1000 类,如何应用这个网络模型呢?
利用现有网络去改动它的结构,避免写 vgg16
很多框架会把 vgg16 当做前置的网络结构,提取一些特殊的特征,再在后面加一些网络结构,实现功能
--------------------------------------------------------------------------------------------------------------------------------
以 vgg16_true 为例讲解,实现上面的第二种思路:
# 给 vgg16 添加一个线性层,输入1000个类别,输出10个类别vgg16_true.add_module('add_linear',nn.Linear(in_features=1000,out_features=10))print(vgg16_true)
结果如图:
如果想将 module 添加至 classifier 里:
# 给 vgg16 添加一个线性层,输入1000个类别,输出10个类别vgg16_true.classifier.add_module('add_linear',nn.Linear(in_features=1000,out_features=10))print(vgg16_true)
结果如图:
---------------------------------------------------------------------------------------------------------------------------------
以上为添加,那么如何修改呢?
以 vgg16_false 为例:
vgg16_false = torchvision.models.vgg16(pretrained=False) # 另一个参数progress显示进度条,默认为Trueprint(vgg16_false)
结果如下:
(classifier): Sequential( (0): Linear(in_features=25088, out_features=4096, bias=True) (1): ReLU(inplace=True) (2): Dropout(p=0.5, inplace=False) (3): Linear(in_features=4096, out_features=4096, bias=True) (4): ReLU(inplace=True) (5): Dropout(p=0.5, inplace=False) (6): Linear(in_features=4096, out_features=1000, bias=True) ))
想将最后一层 Linear 的 out_features 改为10:
vgg16_false.classifier[6] = nn.Linear(4096,10)print(vgg16_false)
结果如下:
本节:
后续内容:
---------------------------------------------------------------------------------------------------------------------------------
import torchimport torchvision.modelsvgg16 = torchvision.models.vgg16(pretrained=False) # 网络中模型的参数是没有经过训练的、初始化的参数
方式1:不仅保存了网络模型的结构,也保存了网络模型的参数
# 保存方式1:模型结构+模型参数torch.save(vgg16,"vgg16_method1.pth")
方式2:网络模型的参数保存为字典,不保存网络模型的结构(官方推荐的保存方式,用的空间小)
# 保存方式2:模型参数(官方推荐)torch.save(vgg16.state_dict(),"vgg16_method2.pth") # 把vgg16的状态保存为字典形式(Python中的一种数据格式)
运行后 src 文件夹底下会多出以下两个文件:
---------------------------------------------------------------------------------------------------------------------------------
方式1:对应保存方式1,打印出的是网络模型的结构
# 方式1 对应 保存方式1,加载模型model = torch.load("vgg16_method1.pth",)print(model) # 打印出的只是模型的结构,其实它的参数也被保存下来了
在print这句打上断点,debug一下,可以看一下模型参数
方式2:对应保存方式2,打印出的是参数的字典形式
# 方式2 加载模型model = torch.load("vgg16_method2.pth")print(model)
如何恢复网络模型结构?
import torchvision.modelsvgg16 = torchvision.models.vgg16(pretrained=False) # 预训练设置为Falsevgg16.load_state_dict(torch.load("vgg16_method2.pth")) # vgg16通过字典形式,加载状态即参数print(vgg16)
---------------------------------------------------------------------------------------------------------------------------------
用方式1保存的话,加载时要让程序能够访问到其定义模型的一种方式
首先在 model_save.py 中写以下代码:
# 陷阱from torch import nnclass Tudui(nn.Module): def __init__(self): super(Tudui, self).__init__() self.conv = nn.Conv2d(3,64,kernel_size=3) def forward(self,x): # x为输入 x = self.conv(x) return xtudui = Tudui() # 有一个卷积层和一些初始化的参数torch.save(tudui,"tudui_method1.pth")
运行后 src 文件夹底下多出一个文件:
再在 model_load.py 中写以下代码:
# 陷阱import torchmodel = torch.load("tudui_method1.pth")print(model)
运行后发现报错:
AttributeError: Can't get attribute 'Tudui' on # 不能得到'Tudui'这个属性,因为没有这个类
--------------------------------------------------------------------------------------------------------------------------------
还是需要将 model_save.py 中的网络结构复制到 model_load.py 中,即下列代码需要复制到 model_load.py 中(为了确保加载的网络模型是想要的网络模型):
class Tudui(nn.Module): def __init__(self): super(Tudui, self).__init__() self.conv = nn.Conv2d(3,64,kernel_size=3) def forward(self,x): # x为输入 x = self.conv(x) return x
但是不需要创建了,即在 model_load.py 中不需要写:
tudui = Tudui()
此时 model_load.py 完整代码为:
# 陷阱class Tudui(nn.Module): def __init__(self): super(Tudui, self).__init__() self.conv = nn.Conv2d(3,64,kernel_size=3) def forward(self,x): # x为输入 x = self.conv(x) return x model = torch.load("tudui_method1.pth")print(model)
运行结果如下:
实际写项目过程中,直接定义在一个单独的文件中(如model_save.py),再在 model_load.py 中:
from model_save import *
以 CIFAR10 数据集为例,分类问题(10分类)
在语句后面按 Ctrl + d 可以复制这条语句
---------------------------------------------------------------------------------------------------------------------------------
import torchfrom torch import nn# 搭建神经网络(10分类网络)class Tudui(nn.Module): def __init__(self): super(Tudui, self).__init__() # 把网络放到序列中 self.model = nn.Sequential( nn.Conv2d(in_channels=3,out_channels=32,kernel_size=5,stride=1,padding=2), #输入是32x32的,输出还是32x32的(padding经计算为2) nn.MaxPool2d(kernel_size=2), nn.Conv2d(in_channels=32,out_channels=32,kernel_size=5,stride=1,padding=2), #输入输出都是16x16的(同理padding经计算为2) nn.MaxPool2d(kernel_size=2), nn.Conv2d(in_channels=32,out_channels=64,kernel_size=5,stride=1,padding=2), nn.MaxPool2d(kernel_size=2), nn.Flatten(), # 展平 nn.Linear(in_features=64*4*4,out_features=64), nn.Linear(in_features=64,out_features=10) ) def forward(self,x): x = self.model(x) return xif __name__ == '__main__': # 测试网络的验证正确性 tudui = Tudui() input = torch.ones((64,3,32,32)) # batch_size=64(代表64张图片),3通道,32x32 output = tudui(input) print(output.shape)
运行结果如下:
返回64行数据,每一行数据有10个数据,代表每一张图片在10个类别中的概率
---------------------------------------------------------------------------------------------------------------------------------
(与 model.py 文件必须在同一个文件夹底下)
import torchvision.datasetsfrom model import *from torch import nnfrom torch.utils.data import DataLoader# 准备数据集,CIFAR10 数据集是PIL Image,要转换为tensor数据类型train_data = torchvision.datasets.CIFAR10(root="../data",train=True,transform=torchvision.transforms.ToTensor(),download=True)test_data = torchvision.datasets.CIFAR10(root="../data",train=False,transform=torchvision.transforms.ToTensor(),download=True)# 看一下训练数据集和测试数据集都有多少张(如何获得数据集的长度)train_data_size = len(train_data) # length 长度test_data_size = len(test_data)# 如果train_data_size=10,那么打印出的字符串为:训练数据集的长度为:10print("训练数据集的长度为:{}".format(train_data_size)) # 字符串格式化,把format中的变量替换{}print("测试数据集的长度为:{}".format(test_data_size))# 利用 DataLoader 来加载数据集train_dataloader = DataLoader(train_data,batch_size=64)test_dataloader = DataLoader(test_data,batch_size=64)# 创建网络模型tudui = Tudui()# 创建损失函数loss_fn = nn.CrossEntropyLoss() # 分类问题可以用交叉熵# 定义优化器learning_rate = 0.01 # 另一写法:1e-2,即1x 10^(-2)=0.01optimizer = torch.optim.SGD(tudui.parameters(),lr=learning_rate) # SGD 随机梯度下降# 设置训练网络的一些参数total_train_step = 0 # 记录训练次数total_test_step = 0 # 记录测试次数epoch = 10 # 训练轮数for i in range(epoch): print("----------第{}轮训练开始-----------".format(i+1)) # i从0-9 # 训练步骤开始 for data in train_dataloader: # 从训练的dataloader中取数据 imgs,targets = data outputs = tudui(imgs) loss = loss_fn(outputs,targets) # 优化器优化模型 optimizer.zero_grad() # 首先要梯度清零 loss.backward() # 反向传播得到每一个参数节点的梯度 optimizer.step() # 对参数进行优化 total_train_step += 1 print("训练次数:{},loss:{}".format(total_train_step,loss.item()))
运行结果如下:
print(a) 和 print(a.item()) 的区别
import torcha = torch.tensor(5)print(a)print(a.item())
---------------------------------------------------------------------------------------------------------------------------------
每次训练完一轮就进行测试,以测试数据集上的损失或正确率来评估模型有没有训练好
测试过程中不需要对模型进行调优,利用现有模型进行测试,所以有以下命令:
with torch.no_grad():
在上述 train.py 代码后继续写:
# 测试步骤开始 total_test_loss = 0 with torch.no_grad(): # 无梯度,不进行调优 for data in test_dataloader: imgs,targets = data outputs = tudui(imgs) loss = loss_fn(outputs,targets) # 该loss为部分数据在网络模型上的损失,为tensor数据类型 # 求整体测试数据集上的误差或正确率 total_test_loss = total_test_loss + loss.item() # loss为tensor数据类型,而total_test_loss为普通数字,所以要.item()一下 print("整体测试集上的Loss:{}".format(total_test_loss))
结果如下:
此处为了使测试的 loss 结果易找,在 train.py 中添加了一句 if 的代码,使train每训练100轮才打印1次:
if total_train_step % 100 ==0: # 逢百才打印记录 print("训练次数:{},loss:{}".format(total_train_step,loss.item()))
import torchvision.datasetsfrom model import *from torch import nnfrom torch.utils.data import DataLoader# 准备数据集,CIFAR10 数据集是PIL Image,要转换为tensor数据类型train_data = torchvision.datasets.CIFAR10(root="../data",train=True,transform=torchvision.transforms.ToTensor(),download=True)test_data = torchvision.datasets.CIFAR10(root="../data",train=False,transform=torchvision.transforms.ToTensor(),download=True)# 看一下训练数据集和测试数据集都有多少张(如何获得数据集的长度)train_data_size = len(train_data) # length 长度test_data_size = len(test_data)# 如果train_data_size=10,那么打印出的字符串为:训练数据集的长度为:10print("训练数据集的长度为:{}".format(train_data_size)) # 字符串格式化,把format中的变量替换{}print("测试数据集的长度为:{}".format(test_data_size))# 利用 DataLoader 来加载数据集train_dataloader = DataLoader(train_data,batch_size=64)test_dataloader = DataLoader(test_data,batch_size=64)# 创建网络模型tudui = Tudui()# 创建损失函数loss_fn = nn.CrossEntropyLoss() # 分类问题可以用交叉熵# 定义优化器learning_rate = 0.01 # 另一写法:1e-2,即1x 10^(-2)=0.01optimizer = torch.optim.SGD(tudui.parameters(),lr=learning_rate) # SGD 随机梯度下降# 设置训练网络的一些参数total_train_step = 0 # 记录训练次数total_test_step = 0 # 记录测试次数epoch = 10 # 训练轮数for i in range(epoch): print("----------第{}轮训练开始-----------".format(i+1)) # i从0-9 # 训练步骤开始 for data in train_dataloader: imgs,targets = data outputs = tudui(imgs) loss = loss_fn(outputs,targets) # 优化器优化模型 optimizer.zero_grad() # 首先要梯度清零 loss.backward() # 反向传播得到每一个参数节点的梯度 optimizer.step() # 对参数进行优化 total_train_step += 1 if total_train_step % 100 ==0: # 逢百才打印记录 print("训练次数:{},loss:{}".format(total_train_step,loss.item())) # 测试步骤开始 total_test_loss = 0 with torch.no_grad(): # 无梯度,不进行调优 for data in test_dataloader: imgs,targets = data outputs = tudui(imgs) loss = loss_fn(outputs,targets) # 该loss为部分数据在网络模型上的损失,为tensor数据类型 # 求整体测试数据集上的误差或正确率 total_test_loss = total_test_loss + loss.item() # loss为tensor数据类型,而total_test_loss为普通数字 print("整体测试集上的Loss:{}".format(total_test_loss))
---------------------------------------------------------------------------------------------------------------------------------
import torchvision.datasetsfrom torch.utils.tensorboard import SummaryWriterfrom model import *from torch import nnfrom torch.utils.data import DataLoader# 准备数据集,CIFAR10 数据集是PIL Image,要转换为tensor数据类型train_data = torchvision.datasets.CIFAR10(root="../data",train=True,transform=torchvision.transforms.ToTensor(),download=True)test_data = torchvision.datasets.CIFAR10(root="../data",train=False,transform=torchvision.transforms.ToTensor(),download=True)# 看一下训练数据集和测试数据集都有多少张(如何获得数据集的长度)train_data_size = len(train_data) # length 长度test_data_size = len(test_data)# 如果train_data_size=10,那么打印出的字符串为:训练数据集的长度为:10print("训练数据集的长度为:{}".format(train_data_size)) # 字符串格式化,把format中的变量替换{}print("测试数据集的长度为:{}".format(test_data_size))# 利用 DataLoader 来加载数据集train_dataloader = DataLoader(train_data,batch_size=64)test_dataloader = DataLoader(test_data,batch_size=64)# 创建网络模型tudui = Tudui()# 创建损失函数loss_fn = nn.CrossEntropyLoss() # 分类问题可以用交叉熵# 定义优化器learning_rate = 0.01 # 另一写法:1e-2,即1x 10^(-2)=0.01optimizer = torch.optim.SGD(tudui.parameters(),lr=learning_rate) # SGD 随机梯度下降# 设置训练网络的一些参数total_train_step = 0 # 记录训练次数total_test_step = 0 # 记录测试次数epoch = 10 # 训练轮数# 添加tensorboardwriter = SummaryWriter("../logs_train")for i in range(epoch): print("----------第{}轮训练开始-----------".format(i+1)) # i从0-9 # 训练步骤开始 for data in train_dataloader: imgs,targets = data outputs = tudui(imgs) loss = loss_fn(outputs,targets) # 优化器优化模型 optimizer.zero_grad() # 首先要梯度清零 loss.backward() # 反向传播得到每一个参数节点的梯度 optimizer.step() # 对参数进行优化 total_train_step += 1 if total_train_step % 100 ==0: # 逢百才打印记录 print("训练次数:{},loss:{}".format(total_train_step,loss.item())) writer.add_scalar("train_loss",loss.item(),total_train_step) # 测试步骤开始 total_test_loss = 0 with torch.no_grad(): # 无梯度,不进行调优 for data in test_dataloader: imgs,targets = data outputs = tudui(imgs) loss = loss_fn(outputs,targets) # 该loss为部分数据在网络模型上的损失,为tensor数据类型 # 求整体测试数据集上的误差或正确率 total_test_loss = total_test_loss + loss.item() # loss为tensor数据类型,而total_test_loss为普通数字 print("整体测试集上的Loss:{}".format(total_test_loss)) writer.add_scalar("test_loss",total_test_loss,total_test_step) total_test_step += 1writer.close()
运行结果:
在 Terminal 里输入:
tensorboard --logdir=logs_train
---------------------------------------------------------------------------------------------------------------------------------
添加两句代码:
torch.save(tudui,"tudui_{}.pth".format(i)) # 每一轮保存一个结果 print("模型已保存")writer.close()
运行后:
---------------------------------------------------------------------------------------------------------------------------------
即便得到整体测试集上的 loss,也不能很好说明在测试集上的表现效果
例子:
第一步:
import torchoutputs = torch.tensor([[0.1,0.2], [0.3,0.4]])print(outputs.argmax(1)) # 0或1表示方向,1为横向比较大小. 运行结果:tensor([1, 1])
第二步:
preds = outputs.argmax(1)targets = torch.tensor([0,1])print(preds == targets) # tensor([False, True])print(sum(preds == targets).sum()) # tensor(1),对应位置相等的个数
上例说明了基本用法,现对原问题的代码再进一步优化,计算整体正确率:
# 测试步骤开始 total_test_loss = 0 total_accuracy = 0 with torch.no_grad(): # 无梯度,不进行调优 for data in test_dataloader: imgs,targets = data outputs = tudui(imgs) loss = loss_fn(outputs,targets) # 该loss为部分数据在网络模型上的损失,为tensor数据类型 # 求整体测试数据集上的误差或正确率 total_test_loss = total_test_loss + loss.item() # loss为tensor数据类型,而total_test_loss为普通数字 # 求整体测试数据集上的误差或正确率 accuracy = (outputs.argmax(1) == targets).sum() # 1:横向比较,==:True或False,sum:计算True或False个数 total_accuracy = total_accuracy + accuracy print("整体测试集上的Loss:{}".format(total_test_loss)) print("整体测试集上的正确率:{}".format(total_accuracy/imgs.size(0))) writer.add_scalar("test_loss",total_test_loss,total_test_step) writer.add_scalar("test_accuracy",total_accuracy/imgs.size(0),total_test_step) total_test_step += 1 torch.save(tudui,"tudui_{}.pth".format(i)) # 每一轮保存一个结果 print("模型已保存")writer.close()
运行结果:
在 Terminal 里输入:
tensorboard --logdir=logs_train
打开网址 http://localhost:6006/ :
---------------------------------------------------------------------------------------------------------------------------------
import torchvision.datasetsfrom torch.utils.tensorboard import SummaryWriterfrom model import *from torch import nnfrom torch.utils.data import DataLoader# 准备数据集,CIFAR10 数据集是PIL Image,要转换为tensor数据类型train_data = torchvision.datasets.CIFAR10(root="../data",train=True,transform=torchvision.transforms.ToTensor(),download=True)test_data = torchvision.datasets.CIFAR10(root="../data",train=False,transform=torchvision.transforms.ToTensor(),download=True)# 看一下训练数据集和测试数据集都有多少张(如何获得数据集的长度)train_data_size = len(train_data) # length 长度test_data_size = len(test_data)# 如果train_data_size=10,那么打印出的字符串为:训练数据集的长度为:10print("训练数据集的长度为:{}".format(train_data_size)) # 字符串格式化,把format中的变量替换{}print("测试数据集的长度为:{}".format(test_data_size))# 利用 DataLoader 来加载数据集train_dataloader = DataLoader(train_data,batch_size=64)test_dataloader = DataLoader(test_data,batch_size=64)# 创建网络模型tudui = Tudui()# 创建损失函数loss_fn = nn.CrossEntropyLoss() # 分类问题可以用交叉熵# 定义优化器learning_rate = 0.01 # 另一写法:1e-2,即1x 10^(-2)=0.01optimizer = torch.optim.SGD(tudui.parameters(),lr=learning_rate) # SGD 随机梯度下降# 设置训练网络的一些参数total_train_step = 0 # 记录训练次数total_test_step = 0 # 记录测试次数epoch = 10 # 训练轮数# 添加tensorboardwriter = SummaryWriter("../logs_train")for i in range(epoch): print("----------第{}轮训练开始-----------".format(i+1)) # i从0-9 # 训练步骤开始 for data in train_dataloader: imgs,targets = data outputs = tudui(imgs) loss = loss_fn(outputs,targets) # 优化器优化模型 optimizer.zero_grad() # 首先要梯度清零 loss.backward() # 反向传播得到每一个参数节点的梯度 optimizer.step() # 对参数进行优化 total_train_step += 1 if total_train_step % 100 ==0: # 逢百才打印记录 print("训练次数:{},loss:{}".format(total_train_step,loss.item())) writer.add_scalar("train_loss",loss.item(),total_train_step) # 测试步骤开始 total_test_loss = 0 total_accuracy = 0 with torch.no_grad(): # 无梯度,不进行调优 for data in test_dataloader: imgs,targets = data outputs = tudui(imgs) loss = loss_fn(outputs,targets) # 该loss为部分数据在网络模型上的损失,为tensor数据类型 # 求整体测试数据集上的误差或正确率 total_test_loss = total_test_loss + loss.item() # loss为tensor数据类型,而total_test_loss为普通数字 # 求整体测试数据集上的误差或正确率 accuracy = (outputs.argmax(1) == targets).sum() # 1:横向比较,==:True或False,sum:计算True或False个数 total_accuracy = total_accuracy + accuracy print("整体测试集上的Loss:{}".format(total_test_loss)) print("整体测试集上的正确率:{}".format(total_accuracy/test_data_size)) writer.add_scalar("test_loss",total_test_loss,total_test_step) writer.add_scalar("test_accuracy",total_accuracy/test_data_size,total_test_step) total_test_step += 1 torch.save(tudui,"tudui_{}.pth".format(i)) # 每一轮保存一个结果 print("模型已保存")writer.close()
---------------------------------------------------------------------------------------------------------------------------------
训练步骤开始之前会把网络模型(我们这里的网络模型叫 tudui)设置为train,并不是说把网络设置为训练模型它才能够开始训练
测试网络前写 网络.eval(),并不是说需要这一行才能把网络设置成 eval 状态,才能进行网络测试
--------------------------------------------------------------------------------------------------------------------------------
这两句不写网络依然可以运行,它们的作用是:
本节写的案例没有 Dropout 层或 BatchNorm 层,所以有没有这两行无所谓
如果有这些特殊层,一定要调用
---------------------------------------------------------------------------------------------------------------------------------
首先,要准备数据集,准备对应的 dataloader
import torchvision.datasetsfrom torch.utils.tensorboard import SummaryWriterfrom model import *from torch import nnfrom torch.utils.data import DataLoader# 准备数据集,CIFAR10 数据集是PIL Image,要转换为tensor数据类型train_data = torchvision.datasets.CIFAR10(root="../data",train=True,transform=torchvision.transforms.ToTensor(),download=True)test_data = torchvision.datasets.CIFAR10(root="../data",train=False,transform=torchvision.transforms.ToTensor(),download=True)# 看一下训练数据集和测试数据集都有多少张(如何获得数据集的长度)train_data_size = len(train_data) # length 长度test_data_size = len(test_data)# 如果train_data_size=10,那么打印出的字符串为:训练数据集的长度为:10print("训练数据集的长度为:{}".format(train_data_size)) # 字符串格式化,把format中的变量替换{}print("测试数据集的长度为:{}".format(test_data_size))# 利用 DataLoader 来加载数据集train_dataloader = DataLoader(train_data,batch_size=64)test_dataloader = DataLoader(test_data,batch_size=64)
然后,创建网络模型、损失函数、优化器,设置训练中的一些参数、训练轮数 epoch(为了能够进行多次训练)
# 创建网络模型tudui = Tudui()# 创建损失函数loss_fn = nn.CrossEntropyLoss() # 分类问题可以用交叉熵# 定义优化器learning_rate = 0.01 # 另一写法:1e-2,即1x 10^(-2)=0.01optimizer = torch.optim.SGD(tudui.parameters(),lr=learning_rate) # SGD 随机梯度下降# 设置训练网络的一些参数total_train_step = 0 # 记录训练次数total_test_step = 0 # 记录测试次数epoch = 10 # 训练轮数
调用
tudui.train()
使网络进入训练状态,从训练的 dataloader 中不断取数据,算出误差,放到优化器中进行优化,采用某种特定的方式展示输出,一轮结束后或特定步数后进行测试
# 训练步骤开始 for data in train_dataloader: imgs,targets = data outputs = tudui(imgs) loss = loss_fn(outputs,targets) # 优化器优化模型 optimizer.zero_grad() # 首先要梯度清零 loss.backward() # 反向传播得到每一个参数节点的梯度 optimizer.step() # 对参数进行优化 total_train_step += 1 if total_train_step % 100 ==0: # 逢百才打印记录 print("训练次数:{},loss:{}".format(total_train_step,loss.item())) writer.add_scalar("train_loss",loss.item(),total_train_step)
测试过程中可以设置
tudui.eval()
要设置
with torch.no_grad():
让网络模型中的参数都没有,因为我们只需要进行测试,不需要对网络模型进行调整,更不需要利用梯度来优化
从测试数据集中取数据,计算误差,构建特殊指标显示出来
for data in test_dataloader: imgs,targets = data outputs = tudui(imgs) loss = loss_fn(outputs,targets) # 该loss为部分数据在网络模型上的损失,为tensor数据类型 # 求整体测试数据集上的误差或正确率 total_test_loss = total_test_loss + loss.item() # loss为tensor数据类型,而total_test_loss为普通数字 # 求整体测试数据集上的误差或正确率 accuracy = (outputs.argmax(1) == targets).sum() # 1:横向比较,==:True或False,sum:计算True或False个数 total_accuracy = total_accuracy + accuracy
最后可以通过一些方式来展示一下训练的网络在测试集上的效果
print("整体测试集上的Loss:{}".format(total_test_loss)) print("整体测试集上的正确率:{}".format(total_accuracy/test_data_size)) writer.add_scalar("test_loss",total_test_loss,total_test_step) writer.add_scalar("test_accuracy",total_accuracy/test_data_size,total_test_step) total_test_step += 1
在特定步数或某一轮可以保存模型,保存模型的方式是之前讲的方式1:
torch.save(tudui,"tudui_{}.pth".format(i))
回忆官方推荐的方式2:
torch.save(tudui.state_dict,"tudui_{}.pth".format(i))
(将网络模型的状态转化为字典型,展示它的特定保存位置)
两种方式实现代码在GPU上训练:
网络模型:
数据(包括输入、标注):
损失函数:
更好的写法:
其他地方同理加上
这种写法在CPU和GPU上都可以跑,优先在GPU上跑
---------------------------------------------------------------------------------------------------------------------------------
为了比较时间,引入 time 这个 package
运行 train.py,可以看到它运行了4.53s
时间竟然比CPU长(我不理解orz)
---------------------------------------------------------------------------------------------------------------------------------
在 Terminal 里输入
nvidia-smi
会出现一些GPU的信息
---------------------------------------------------------------------------------------------------------------------------------
Google 为我们提供了一个免费的GPU,默认提供的环境当中就有Pytorch
修改 ——> 笔记本设置 ——> 硬件加速器选择GPU(每周免费使用30h)
使用GPU后会重新启动环境
将 train_gpu_1.py 代码复制进去运行,速度很快,结果如下:
在 Google Colab 上运行 terminal 中运行的东西,在语句前加 !(感叹号)
---------------------------------------------------------------------------------------------------------------------------------
以下两种写法对于单显卡来说等价:
device = torch.device("cuda")device = torch.device("cuda:0")
语法糖(一种语法的简写),程序在 CPU 或 GPU/cuda 环境下都能运行:
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
网络模型、损失函数都不需要另外赋值,直接 .to(device) 就可以
但是数据(图片、标注)需要另外转移之后再重新赋值给变量
核心:利用已经训练好的模型,给它提供输入进行测试(类似之前案例中测试集的测试部分)
---------------------------------------------------------------------------------------------------------------------------------
Resize():
随便在网络上找图片,通过 Resize() 使图片符合模型
# 如何从test.py文件去找到dog文件(相对路径)import torchimport torchvision.transformsfrom PIL import Imagefrom torch import nnimage_path = "../imgs/dog.png" # 或右键-> Copy Path-> Absolute Path(绝对路径)# 读取图片(PIL Image),再用ToTensor进行转换image = Image.open(image_path) # 现在的image是PIL类型print(image) # # image = image.convert('RGB')# 因为png格式是四通道,除了RGB三通道外,还有一个透明度通道,所以要调用上述语句保留其颜色通道# 当然,如果图片本来就是三颜色通道,经过此操作,不变# 加上这一步后,可以适应 png jpg 各种格式的图片# 该image大小为430x247,网络模型的输入只能是32x32,进行一个Resize()# Compose():把transforms几个变换联立在一起transform = torchvision.transforms.Compose([torchvision.transforms.Resize((32,32)), # 32x32大小的PIL Image torchvision.transforms.ToTensor()]) # 转为Tensor数据类型image = transform(image)print(image.shape) # torch.Size([3, 32, 32])# 加载网络模型(之前采用的是第一种方式保存,故需要采用第一种方式加载)# 首先搭建神经网络(10分类网络)class Tudui(nn.Module): def __init__(self): super(Tudui, self).__init__() # 把网络放到序列中 self.model = nn.Sequential( nn.Conv2d(in_channels=3,out_channels=32,kernel_size=5,stride=1,padding=2), #输入是32x32的,输出还是32x32的(padding经计算为2) nn.MaxPool2d(kernel_size=2), nn.Conv2d(in_channels=32,out_channels=32,kernel_size=5,stride=1,padding=2), #输入输出都是16x16的(同理padding经计算为2) nn.MaxPool2d(kernel_size=2), nn.Conv2d(in_channels=32,out_channels=64,kernel_size=5,stride=1,padding=2), nn.MaxPool2d(kernel_size=2), nn.Flatten(), # 展平 nn.Linear(in_features=64*4*4,out_features=64), nn.Linear(in_features=64,out_features=10) ) def forward(self,x): x = self.model(x) return x# 然后加载网络模型model = torch.load("tudui_0.pth") # 因为test.py和tudui_0.pth在同一个层级下,所以地址可以直接写print(model)output = model(image)print(output)
运行会报错,报错提示如下:
RuntimeError: Expected 4-dimensional input for 4-dimensional weight [32, 3, 5, 5], but got 3-dimensional input of size [3, 32, 32] instead
原因:要求是四维的输入 [batch_size,channel,length,width],但是获得的图片是三维的 —— 图片没有指定 batch_size(网络训练过程中是需要 batch_size 的,而图片输入是三维的,需要reshape() 一下)
解决:torch.reshape() 方法
在上述代码后面加上:
image = torch.reshape(image,(1,3,32,32))model.eval() # 模型转化为测试类型with torch.no_grad(): # 节约内存和性能 output = model(image)print(output)
运行结果:
3220 概率最大,预测的是第六类
print(output.argmax(1))
--------------------------------------------------------------------------------------------------------------------------------
怎么找到?
第六类对应的是 frog(青蛙)
---------------------------------------------------------------------------------------------------------------------------------
预测错误的原因可能是训练次数不够多,在 Google Colab 里,将训练轮数 epoch 改为 30 次,完整代码(train.py):
import torchimport torchvision.datasetsfrom torch.utils.tensorboard import SummaryWriter# from model import *from torch import nnfrom torch.utils.data import DataLoaderimport time # time这个package是用来计时的# 准备数据集,CIFAR10 数据集是PIL Image,要转换为tensor数据类型device = torch.device("cuda")print(device)train_data = torchvision.datasets.CIFAR10(root="../data",train=True,transform=torchvision.transforms.ToTensor(),download=True)test_data = torchvision.datasets.CIFAR10(root="../data",train=False,transform=torchvision.transforms.ToTensor(),download=True)# 看一下训练数据集和测试数据集都有多少张(如何获得数据集的长度)train_data_size = len(train_data) # length 长度test_data_size = len(test_data)# 如果train_data_size=10,那么打印出的字符串为:训练数据集的长度为:10print("训练数据集的长度为:{}".format(train_data_size)) # 字符串格式化,把format中的变量替换{}print("测试数据集的长度为:{}".format(test_data_size))# 利用 DataLoader 来加载数据集train_dataloader = DataLoader(train_data,batch_size=64)test_dataloader = DataLoader(test_data,batch_size=64)# 搭建神经网络(10分类网络)class Tudui(nn.Module): def __init__(self): super(Tudui, self).__init__() # 把网络放到序列中 self.model = nn.Sequential( nn.Conv2d(in_channels=3,out_channels=32,kernel_size=5,stride=1,padding=2), #输入是32x32的,输出还是32x32的(padding经计算为2) nn.MaxPool2d(kernel_size=2), nn.Conv2d(in_channels=32,out_channels=32,kernel_size=5,stride=1,padding=2), #输入输出都是16x16的(同理padding经计算为2) nn.MaxPool2d(kernel_size=2), nn.Conv2d(in_channels=32,out_channels=64,kernel_size=5,stride=1,padding=2), nn.MaxPool2d(kernel_size=2), nn.Flatten(), # 展平 nn.Linear(in_features=64*4*4,out_features=64), nn.Linear(in_features=64,out_features=10) ) def forward(self,x): x = self.model(x) return x# 创建网络模型tudui = Tudui()tudui.to(device)# 创建损失函数loss_fn = nn.CrossEntropyLoss() # 分类问题可以用交叉熵loss_fn.to(device)# 定义优化器learning_rate = 0.01 # 另一写法:1e-2,即1x 10^(-2)=0.01optimizer = torch.optim.SGD(tudui.parameters(),lr=learning_rate) # SGD 随机梯度下降# 设置训练网络的一些参数total_train_step = 0 # 记录训练次数total_test_step = 0 # 记录测试次数epoch = 30 # 训练轮数# 添加tensorboardwriter = SummaryWriter("../logs_train")start_time = time.time() # 记录下此时的时间,赋值给开始时间for i in range(epoch): print("----------第{}轮训练开始-----------".format(i+1)) # i从0-9 # 训练步骤开始 tudui.train() for data in train_dataloader: imgs,targets = data imgs = imgs.to(device) targets = targets.to(device) outputs = tudui(imgs) loss = loss_fn(outputs,targets) # 优化器优化模型 optimizer.zero_grad() # 首先要梯度清零 loss.backward() # 反向传播得到每一个参数节点的梯度 optimizer.step() # 对参数进行优化 total_train_step += 1 if total_train_step % 100 ==0: # 逢百才打印记录 end_time = time.time() print(end_time - start_time) # 第一次训练100次所花费的时间 print("训练次数:{},loss:{}".format(total_train_step,loss.item())) writer.add_scalar("train_loss",loss.item(),total_train_step) # 测试步骤开始 tudui.eval() total_test_loss = 0 total_accuracy = 0 with torch.no_grad(): # 无梯度,不进行调优 for data in test_dataloader: imgs,targets = data imgs = imgs.to(device) targets = targets.to(device) outputs = tudui(imgs) loss = loss_fn(outputs,targets) # 该loss为部分数据在网络模型上的损失,为tensor数据类型 # 求整体测试数据集上的误差或正确率 total_test_loss = total_test_loss + loss.item() # loss为tensor数据类型,而total_test_loss为普通数字 accuracy = (outputs.argmax(1) == targets).sum() # 1:横向比较,==:True或False,sum:计算True或False个数 total_accuracy = total_accuracy + accuracy print("整体测试集上的Loss:{}".format(total_test_loss)) print("整体测试集上的正确率:{}".format(total_accuracy/test_data_size)) # 正确率为预测对的个数除以测试集长度 writer.add_scalar("test_loss",total_test_loss,total_test_step) writer.add_scalar("test_accuracy",total_test_loss,total_test_step,total_test_step) total_test_step += 1 torch.save(tudui,"tudui_{}_gpu.pth".format(i)) # 每一轮保存一个结果 print("模型已保存")writer.close()
运行结果:
下载后复制到 PyCharm 中 Learn_Torch 的 src 文件夹下,然后将之前 test.py 中的路径修改为:
model = torch.load("tudui_29_gpu.pth")
运行后报错,报错提示:
RuntimeError: Input type (torch.FloatTensor) and weight type (torch.cuda.FloatTensor) should be the same or input should be a MKLDNN tensor and weight is a dense tensor
原因:采用GPU训练的模型,在CPU上加载,要从GPU上映射到CPU上(在不同环境中加载已经训练好的模型,需要经过映射)
解决:
model = torch.load("tudui_29_gpu.pth",map_location=torch.device('cpu'))
--------------------------------------------------------------------------------------------------------------------------------
# 如何从test.py文件去找到dog文件(相对路径)import torchimport torchvision.transformsfrom PIL import Imagefrom torch import nnimage_path = "../imgs/airplane.png" # 或右键-> Copy Path-> Absolute Path(绝对路径)# 读取图片(PIL Image),再用ToTensor进行转换image = Image.open(image_path) # 现在的image是PIL类型print(image) # # image = image.convert('RGB')# 因为png格式是四通道,除了RGB三通道外,还有一个透明度通道,所以要调用上述语句保留其颜色通道# 当然,如果图片本来就是三颜色通道,经过此操作,不变# 加上这一步后,可以适应 png jpg 各种格式的图片# 该image大小为430x247,网络模型的输入只能是32x32,进行一个Resize()# Compose():把transforms几个变换联立在一起transform = torchvision.transforms.Compose([torchvision.transforms.Resize((32,32)), # 32x32大小的PIL Image torchvision.transforms.ToTensor()]) # 转为Tensor数据类型image = transform(image)print(image.shape) # torch.Size([3, 32, 32])# 加载网络模型(之前采用的是第一种方式保存,故需要采用第一种方式加载)# 首先搭建神经网络(10分类网络)class Tudui(nn.Module): def __init__(self): super(Tudui, self).__init__() # 把网络放到序列中 self.model = nn.Sequential( nn.Conv2d(in_channels=3,out_channels=32,kernel_size=5,stride=1,padding=2), #输入是32x32的,输出还是32x32的(padding经计算为2) nn.MaxPool2d(kernel_size=2), nn.Conv2d(in_channels=32,out_channels=32,kernel_size=5,stride=1,padding=2), #输入输出都是16x16的(同理padding经计算为2) nn.MaxPool2d(kernel_size=2), nn.Conv2d(in_channels=32,out_channels=64,kernel_size=5,stride=1,padding=2), nn.MaxPool2d(kernel_size=2), nn.Flatten(), # 展平 nn.Linear(in_features=64*4*4,out_features=64), nn.Linear(in_features=64,out_features=10) ) def forward(self,x): x = self.model(x) return x# 然后加载网络模型model = torch.load("tudui_29_gpu.pth",map_location=torch.device('cpu')) # 因为test.py和tudui_0.pth在同一个层级下,所以地址可以直接写print(model)image = torch.reshape(image,(1,3,32,32))model.eval() # 模型转化为测试类型with torch.no_grad(): output = model(image)print(output)print(output.argmax(1))
--------------------------------------------------------------------------------------------------------------------------------
再以飞机的图片为例(airplane.png 保存在 imgs 文件夹里)
# 如何从test.py文件去找到dog文件(相对路径)import torchimport torchvision.transformsfrom PIL import Imagefrom torch import nnimage_path = "../imgs/airplane.png" # 或右键-> Copy Path-> Absolute Path(绝对路径)# 读取图片(PIL Image),再用ToTensor进行转换image = Image.open(image_path) # 现在的image是PIL类型print(image) # # image = image.convert('RGB')# 因为png格式是四通道,除了RGB三通道外,还有一个透明度通道,所以要调用上述语句保留其颜色通道# 当然,如果图片本来就是三颜色通道,经过此操作,不变# 加上这一步后,可以适应 png jpg 各种格式的图片# 该image大小为430x247,网络模型的输入只能是32x32,进行一个Resize()# Compose():把transforms几个变换联立在一起transform = torchvision.transforms.Compose([torchvision.transforms.Resize((32,32)), # 32x32大小的PIL Image torchvision.transforms.ToTensor()]) # 转为Tensor数据类型image = transform(image)print(image.shape) # torch.Size([3, 32, 32])# 加载网络模型(之前采用的是第一种方式保存,故需要采用第一种方式加载)# 首先搭建神经网络(10分类网络)class Tudui(nn.Module): def __init__(self): super(Tudui, self).__init__() # 把网络放到序列中 self.model = nn.Sequential( nn.Conv2d(in_channels=3,out_channels=32,kernel_size=5,stride=1,padding=2), #输入是32x32的,输出还是32x32的(padding经计算为2) nn.MaxPool2d(kernel_size=2), nn.Conv2d(in_channels=32,out_channels=32,kernel_size=5,stride=1,padding=2), #输入输出都是16x16的(同理padding经计算为2) nn.MaxPool2d(kernel_size=2), nn.Conv2d(in_channels=32,out_channels=64,kernel_size=5,stride=1,padding=2), nn.MaxPool2d(kernel_size=2), nn.Flatten(), # 展平 nn.Linear(in_features=64*4*4,out_features=64), nn.Linear(in_features=64,out_features=10) ) def forward(self,x): x = self.model(x) return x# 然后加载网络模型model = torch.load("tudui_29_gpu.pth",map_location=torch.device('cpu')) # 因为test.py和tudui_0.pth在同一个层级下,所以地址可以直接写print(model)image = torch.reshape(image,(1,3,32,32))model.eval() # 模型转化为测试类型with torch.no_grad(): output = model(image)print(output)print(output.argmax(1)) # 把输出转换为一种利于解读的方式
运行结果:
第 0 类就是 airplane,预测正确
注意:
model.eval() # 模型转化为测试类型with torch.no_grad():
这两行不写也可以,但为了养成良好的代码习惯,最好写上。如果网络模型中正好有 Dropout 或 BatchNorm 时,不写的话预测就会有问题
GitHub - junyanz/pytorch-CycleGAN-and-pix2pix: Image-to-Image Translation in PyTorch
先读 README.md(怎么安装、注意事项)
--------------------------------------------------------------------------------------------------------------------------------
"""General-purpose training script for image-to-image translation.This script works for various models (with option '--model': e.g., pix2pix, cyclegan, colorization) anddifferent datasets (with option '--dataset_mode': e.g., aligned, unaligned, single, colorization).You need to specify the dataset ('--dataroot'), experiment name ('--name'), and model ('--model').It first creates model, dataset, and visualizer given the option.It then does standard network training. During the training, it also visualize/save the images, print/save the loss plot, and save models.The script supports continue/resume training. Use '--continue_train' to resume your previous training.Example: Train a CycleGAN model: python train.py --dataroot ./datasets/maps --name maps_cyclegan --model cycle_gan Train a pix2pix model: python train.py --dataroot ./datasets/facades --name facades_pix2pix --model pix2pix --direction BtoASee options/base_options.py and options/train_options.py for more training options.See training and test tips at: https://GitHub.com/junyanz/pytorch-CycleGAN-and-pix2pix/blob/master/docs/tips.mdSee frequently asked questions at: https://github.com/junyanz/pytorch-CycleGAN-and-pix2pix/blob/master/docs/qa.md"""import timefrom options.train_options import TrainOptionsfrom data import create_datasetfrom models import create_modelfrom util.visualizer import Visualizerif __name__ == '__main__': opt = TrainOptions().parse() # get training options dataset = create_dataset(opt) # create a dataset given opt.dataset_mode and other options dataset_size = len(dataset) # get the number of images in the dataset. print('The number of training images = %d' % dataset_size) model = create_model(opt) # create a model given opt.model and other options model.setup(opt) # regular setup: load and print networks; create schedulers visualizer = Visualizer(opt) # create a visualizer that display/save images and plots total_iters = 0 # the total number of training iterations for epoch in range(opt.epoch_count, opt.n_epochs + opt.n_epochs_decay + 1): # outer loop for different epochs; we save the model by , + epoch_start_time = time.time() # timer for entire epoch iter_data_time = time.time() # timer for data loading per iteration epoch_iter = 0 # the number of training iterations in current epoch, reset to 0 every epoch visualizer.reset() # reset the visualizer: make sure it saves the results to HTML at least once every epoch model.update_learning_rate() # update learning rates in the beginning of every epoch. for i, data in enumerate(dataset): # inner loop within one epoch iter_start_time = time.time() # timer for computation per iteration if total_iters % opt.print_freq == 0: t_data = iter_start_time - iter_data_time total_iters += opt.batch_size epoch_iter += opt.batch_size model.set_input(data) # unpack data from dataset and apply preprocessing model.optimize_parameters() # calculate loss functions, get gradients, update network weights if total_iters % opt.display_freq == 0: # display images on visdom and save images to a HTML file save_result = total_iters % opt.update_html_freq == 0 model.compute_visuals() visualizer.display_current_results(model.get_current_visuals(), epoch, save_result) if total_iters % opt.print_freq == 0: # print training losses and save logging information to the disk losses = model.get_current_losses() t_comp = (time.time() - iter_start_time) / opt.batch_size visualizer.print_current_losses(epoch, epoch_iter, losses, t_comp, t_data) if opt.display_id > 0: visualizer.plot_current_losses(epoch, float(epoch_iter) / dataset_size, losses) if total_iters % opt.save_latest_freq == 0: # cache our latest model every iterations print('saving the latest model (epoch %d, total_iters %d)' % (epoch, total_iters)) save_suffix = 'iter_%d' % total_iters if opt.save_by_iter else 'latest' model.save_networks(save_suffix) iter_data_time = time.time() if epoch % opt.save_epoch_freq == 0: # cache our model every epochs print('saving the model at the end of epoch %d, iters %d' % (epoch, total_iters)) model.save_networks('latest') model.save_networks(epoch) print('End of epoch %d / %d \t Time Taken: %d sec' % (epoch, opt.n_epochs + opt.n_epochs_decay, time.time() - epoch_start_time))
--------------------------------------------------------------------------------------------------------------------------------
from .base_options import BaseOptionsclass TrainOptions(BaseOptions): """This class includes training options. It also includes shared options defined in BaseOptions. """ def initialize(self, parser): parser = BaseOptions.initialize(self, parser) # visdom and html visualization parameters parser.add_argument('--display_freq', type=int, default=400, help='frequency of showing training results on screen') parser.add_argument('--display_ncols', type=int, default=4, help='if positive, display all images in a single visdom WEB panel with certain number of images per row.') parser.add_argument('--display_id', type=int, default=1, help='window id of the web display') parser.add_argument('--display_server', type=str, default="http://localhost", help='visdom server of the web display') parser.add_argument('--display_env', type=str, default='main', help='visdom display environment name (default is "main")') parser.add_argument('--display_port', type=int, default=8097, help='visdom port of the web display') parser.add_argument('--update_html_freq', type=int, default=1000, help='frequency of saving training results to html') parser.add_argument('--print_freq', type=int, default=100, help='frequency of showing training results on console') parser.add_argument('--no_html', action='store_true', help='do not save intermediate training results to [opt.checkpoints_dir]/[opt.name]/web/') # network saving and loading parameters parser.add_argument('--save_latest_freq', type=int, default=5000, help='frequency of saving the latest results') parser.add_argument('--save_epoch_freq', type=int, default=5, help='frequency of saving checkpoints at the end of epochs') parser.add_argument('--save_by_iter', action='store_true', help='whether saves model by iteration') parser.add_argument('--continue_train', action='store_true', help='continue training: load the latest model') parser.add_argument('--epoch_count', type=int, default=1, help='the starting epoch count, we save the model by , +, ...') parser.add_argument('--phase', type=str, default='train', help='train, val, test, etc') # training parameters parser.add_argument('--n_epochs', type=int, default=100, help='number of epochs with the initial learning rate') parser.add_argument('--n_epochs_decay', type=int, default=100, help='number of epochs to linearly decay learning rate to zero') parser.add_argument('--beta1', type=float, default=0.5, help='momentum term of adam') parser.add_argument('--lr', type=float, default=0.0002, help='initial learning rate for adam') parser.add_argument('--gan_mode', type=str, default='lsgan', help='the type of GAN objective. [vanilla| lsgan | wgangp]. vanilla GAN loss is the cross-entropy objective used in the original GAN paper.') parser.add_argument('--pool_size', type=int, default=50, help='the size of image buffer that stores previously generated images') parser.add_argument('--lr_policy', type=str, default='linear', help='learning rate policy. [linear | step | plateau | cosine]') parser.add_argument('--lr_decay_iters', type=int, default=50, help='multiply by a gamma every lr_decay_iters iterations') self.isTrain = True return parser
没有dataroot
点进其继承的父类查看,可以看到有dataroot
下载代码用 PyCharm 打开后,查看代码里有没有 required=True,若有,删掉 required=True ,加一个默认值 default="./dataset/maps" ,就可以在 PyCharm 里右键运行了
即找到所有 required=True 的参数,将它删去并添加上默认值default
来源地址:https://blog.csdn.net/qq_43629945/article/details/122767670
--结束END--
本文标题: 【我是土堆 - PyTorch教程】学习随手记(已更新 | 已完结 | 10w字超详细版)
本文链接: https://lsjlt.com/news/401706.html(转载时请注明来源链接)
有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341
2024-03-01
2024-03-01
2024-03-01
2024-02-29
2024-02-29
2024-02-29
2024-02-29
2024-02-29
2024-02-29
2024-02-29
回答
回答
回答
回答
回答
回答
回答
回答
回答
回答
0