pytorch安装
windows安装pytorch环境 并支持CUDA,cuDNN - 知乎
Anaconda | The World’s Most Popular Data Science Platform
先安装虚拟环境anaconda
注意版本
![](https://i-blog.csdnimg.cn/blog_migrate/3e5a039d41987b1d6ff4a2f4c0c420a7.png)
安装成功的标志
![](https://i-blog.csdnimg.cn/blog_migrate/85f1519ec0d305fa77172ce2e55702c8.png)
![](https://i-blog.csdnimg.cn/blog_migrate/a169e90a12962ffe8c6a4505f218be57.png)
可以使用工具包调整pytorch的环境与python版本
![](https://i-blog.csdnimg.cn/blog_migrate/c887c93c1e41adc2f9901a750407b509.png)
conda create -n pytorch python=3.6
![](https://i-blog.csdnimg.cn/blog_migrate/1d65f8616b34e76794227a1d0781bfc4.png)
安装成功之后如果激活可以使用上述两个指令
![](https://i-blog.csdnimg.cn/blog_migrate/5ec6ef0b185aa9ea17581bc565986190.png)
激活成功
![](https://i-blog.csdnimg.cn/blog_migrate/723b06c27765795eebfb9a45cab13093.png)
可用工具包中并没有pytorch,接下来安装pytorch
![](https://i-blog.csdnimg.cn/blog_migrate/8f9d278a6e6bdbf6dbc35571a01d9590.png)
选择好相应版本粘贴命令
然后需要循环进行命令使用,一次安装可能不成功
需要持续进行空格进行安装
![](https://i-blog.csdnimg.cn/blog_migrate/8b4150a761b1c590d45791012bf8d1f3.png)
nvidia-smi
命令行中输入上述指令,进行版本检查,此处需要大于396才能使用,否则需要修改
![](https://i-blog.csdnimg.cn/blog_migrate/5f9302aee35c1b23c361440b84a554a4.png)
-
首先安装Cuda
首先我们要确定本机是否有独立显卡。在计算机-管理-设备管理器-显示适配器中,查看是否有独立显卡。
![](https://i-blog.csdnimg.cn/blog_migrate/5678d7ffd238f371a5f5c557a82d3632.png)
CUDA Toolkit Archive | NVIDIA Developer
![](https://i-blog.csdnimg.cn/blog_migrate/f4ba5e45490a2c82fb388f8c5391aafa.png)
![](https://i-blog.csdnimg.cn/blog_migrate/d534a7d21b94b89346440ae0ee6d5e18.png)
![](https://i-blog.csdnimg.cn/blog_migrate/6dbde938627ad6963e8269185a10ba09.png)
直接打开解压安装
接着就是安装过程,双击打开显示临时解压目录,不需要改变,默认即可。
接下来,进入NVIDIA安装过程,在这安装过程中,我一开始直接选择的精简安装,但由于VS的原因,导致无法正常安装,于是我换成了自定义的安装方式,并将VS勾给去掉,便可以正常安装了,至于CUDA的安装目录,大家默认安装在C盘即可
![](https://i-blog.csdnimg.cn/blog_migrate/1accf0009d44966172c68888694c0fa4.png)
添加环境变量
![](https://i-blog.csdnimg.cn/blog_migrate/d9e628edb9d2542721bb68809004c1ba.png)
![](https://i-blog.csdnimg.cn/blog_migrate/c03b39ec10d5854e094c9105c123c326.png)
-
安装CUDNN
(需要注册账号并且填写调查问卷)
![](https://i-blog.csdnimg.cn/blog_migrate/542d25592ce9dece52d62bb1dceb0c7c.png)
下载之后,解压缩,将CUDNN压缩包里面的bin、clude、lib文件直接复制到CUDA的安装目录下,直接覆盖安装即可。
![](https://i-blog.csdnimg.cn/blog_migrate/74e10dcd5a230f4679421fe791822b80.png)
-
pytorch-gpu的安装
直接来到Pytorch的官网,此处,因为根据自己的配置进行选择,我试过用conda安装,但是conda安装老是不成功,也替换过清华镜像源,但速度实在是龟速,半天没动静。虽然pip下载也蛮慢,但至少可以安装成功。这里我对pip的安装方式,稍作修改,让其直接从清华镜像源下载。然后慢慢等待安装成功即可。
![](https://i-blog.csdnimg.cn/blog_migrate/a2e2cb2cdfb91a60d80c33d3525459d2.png)
![](https://i-blog.csdnimg.cn/blog_migrate/e66626249e6834b722cb59cab6eaad8d.png)
![](https://i-blog.csdnimg.cn/blog_migrate/843f72591e4a3660f9ad3ac145ddc696.png)
然后命令行验证pytorch是否正常安装成功,这里可以正常打印出版本号,安装没问题。
import torch
print(torch.__version__)
![](https://i-blog.csdnimg.cn/blog_migrate/4c42df6baba68ca8d61d3273d60234bc.png)
print(torch.cuda.is_available())
尝试调用CUDA
TRUE表示成功
![](https://i-blog.csdnimg.cn/blog_migrate/f0211c8e71fe4da03f92338705c3a646.png)
在PYcharm中进行运行
![](https://i-blog.csdnimg.cn/blog_migrate/6b529c2396691cb2833c0e5843565917.png)
![](https://i-blog.csdnimg.cn/blog_migrate/5eaa781c1c80ce14ee20127551df7942.png)
然后运行代码片段
def print_hi(name):
# Use a breakpoint in the code line below to debug your script.
print(f'Hi, {name}') # Press Ctrl+F8 to toggle the breakpoint.
print(torch.__version__)
print(torch.cuda.is_available())
# Press the green button in the gutter to run the script.
if __name__ == '__main__':
print_hi('PyCharm')
![](https://i-blog.csdnimg.cn/blog_migrate/a562f5ae4fdf557a672ee52d15d5254d.png)
成功
![](https://i-blog.csdnimg.cn/blog_migrate/302af87b3a2815c3cd49e8ca597bbe49.png)
安装报错
![](https://i-blog.csdnimg.cn/blog_migrate/0acbb7ebbafeeb28a196df457106b26b.png)
解决方式
PackagesNotFoundError: The following packages are not available from current channels的解决办法-CSDN博客
去官网寻找安装包
找到对应版本进行安装
![](https://i-blog.csdnimg.cn/blog_migrate/c145cdaa7a6f57cc343f54cd66278ae9.png)
练习路径
![](https://i-blog.csdnimg.cn/blog_migrate/01d02e2bb66ff58ba27b91aab97b136d.png)
anaconda安装之后jupter默认安装
但是jupyter是安装在base环境中的,需要转换成pytorch环境再重新安装jupyter
这样才能在pytorch中使用jupyter
![](https://i-blog.csdnimg.cn/blog_migrate/e28eb5d4797b0ca80991f68ca31d4b30.png)
pytorch环境中没有jupyter
![](https://i-blog.csdnimg.cn/blog_migrate/2fb787856c6c8ffafc4be153314ec053.png)
安装包
![](https://i-blog.csdnimg.cn/blog_migrate/6a6278d3fddec2fbfa76552f76550689.png)
启动jupyter notebook
![](https://i-blog.csdnimg.cn/blog_migrate/f0289ab9704f84f2cf2647038e455857.png)
选择这个
![](https://i-blog.csdnimg.cn/blog_migrate/feb69ca1af12b3da1b43a46ec8589b4b.png)
![](https://i-blog.csdnimg.cn/blog_migrate/056db522e92c98396e88bdc84ecff4f5.png)
成功使用jupyter,并且jupyter使用pytorch环境
![](https://i-blog.csdnimg.cn/blog_migrate/b638b94d1d4343473fac43cb19562eb0.png)
查看分隔区
查看函数
![](https://i-blog.csdnimg.cn/blog_migrate/4efa73b4b7303839fbc6badd1d527d54.png)
添加python解释器
![](https://i-blog.csdnimg.cn/blog_migrate/c108c7e87c760603cb868828f03926d3.png)
///
使用DateSet类
获取数据及其label
如何获取每一个数据及其label
告诉我们总共有多少个数据
使用DataLoader类
为网络提供不同的数据形式
读取数据集
首先创建read_data.py文件
![](https://i-blog.csdnimg.cn/blog_migrate/4bc4d7a6e0a24bdb9d20a201f2180eee.png)
先在控制台中进行模拟
from PIL import Image
img_path = "E:\\pytorch\\pytorch exercise\\hymenoptera_data\\hymenoptera_data\\train\\ants\\0013035.jpg"
img = Image.open(img_path)
img.show()
其中"E:\\pytorch\\pytorch exercise\\hymenoptera_data\\hymenoptera_data\\train\\ants\\0013035.jpg"
是文件的相对路径
img.show() 打开文件
使用DataSet类进行导入数据集
from torch.utils.data import Dataset
from PIL import Image
import os
import cv2
# root_dir = "hymenoptera_data/hymenoptera_data/train"
# label_dir = "ants"
# path = os.path.join(root_dir,label_dir)
class Mydata(Dataset):
#初始化数据集,self表示当前类,root_dir表示当前的相对根路径,label_dir表示其中的子路径
def __init__(self, root_dir, label_dir):
self.root_dir = root_dir
self.label_dir = label_dir
self.path = os.path.join(root_dir,label_dir)#拼接路径
self.img_path = os.listdir(self.path)
def __getitem__(self, 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)
只要输入好路径,输入Dataset类,就能创建数据集
root_dir = "hymenoptera_data/hymenoptera_data/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)
![](https://i-blog.csdnimg.cn/blog_migrate/24460f64921c3c7675400878a8fe011d.png)
add_scalar的使用
添加函数
from torch.utils.tensorboard import SummaryWriter
writer = SummaryWriter("logs")
#writer.add_image()
#y=x
for i in range(100):
writer.add_scalar("y=x",i,i)#(函数图像,x轴长度,y轴长度)
writer.close()
运行上述代码,会生成一个logs文件
![](https://i-blog.csdnimg.cn/blog_migrate/89826ccc739eec35265b18c6a26d8b4c.png)
在命令行中选择打开文件
tensorboard --logdir=logs
![](https://i-blog.csdnimg.cn/blog_migrate/38e854136d9ca4957c3953d0e500bb72.png)
返回出页面的结果,点进去
![](https://i-blog.csdnimg.cn/blog_migrate/e158eb92b0c593efe42d979ee1355b00.png)
add_image的使用
![](https://i-blog.csdnimg.cn/blog_migrate/5136102c2d2a7baa7cb75fe03379ea46.png)
img_tensor的数据类型
from torch.utils.tensorboard import SummaryWriter
import numpy as np
from PIL import Image
image_path="hymenoptera_data/hymenoptera_data/train/ants/0013035.jpg"
img_PTL = Image.open(image_path)
img_array = np.array(img_PTL)
print(type(img_array))
print(img_array.shape)
writer = SummaryWriter("logs")
writer.add_image("test",img_array,1,dataformats="HWC")
运行结果
![](https://i-blog.csdnimg.cn/blog_migrate/95ca43361532e1e49748224cbe7dc161.png)
此时发现网页中多出了一个IMAGE
![](https://i-blog.csdnimg.cn/blog_migrate/c1069c0ee8b795c58fdc62c75a9f34bc.png)
TransForm的使用
![](https://i-blog.csdnimg.cn/blog_migrate/6ce30c17c9bab9d5f95f68fc35097b42.png)
首先查看TransForm的源码
![](https://i-blog.csdnimg.cn/blog_migrate/5ebc67d66ddfe643e86cd89e6c058c7b.png)
使用python工具中的structure进行查看文件结构
![](https://i-blog.csdnimg.cn/blog_migrate/91a5daef9b6a2e1ada07f2b086b2ba7b.png)
会发现好多类
注意compose类
to tensor
![](https://i-blog.csdnimg.cn/blog_migrate/e1e3e3837e29b5a2334f4db6552a13cb.png)
from PIL import Image
from torchvision import transforms
#python 的用法 tensor数据类型
#通过 transform.ToTensor去看两个问题
#1.transform该如何使用(python)
#2.为什么我们需要 Tensor数据类型
#绝对路径 E:\pytorch\pytorch exercise\hymenoptera_data\hymenoptera_data\train\ants\0013035.jpg
#相对路径 hymenoptera_data/hymenoptera_data/train/ants/0013035.jpg
img_path = "hymenoptera_data/hymenoptera_data/train/ants/0013035.jpg"
img = Image.open(img_path)
print(img)
![](https://i-blog.csdnimg.cn/blog_migrate/c8e97d3e13e7fb18023f1fc4dd2efbd1.png)
图片显示被正确读取
def __call__(self, pic):
"""
Args:
pic (PIL Image or numpy.ndarray): Image to be converted to tensor.
Returns:
Tensor: Converted image.
"""
使用的时候只要传入一个图片就会返回一个tensor类型图片
tensor_trans = transforms.ToTensor()#直接在transform工具箱当中调用ToTensor工具
tensor_img = tensor_trans(img)
print(tensor_img)
将图片转换成tensor类型
![](https://i-blog.csdnimg.cn/blog_migrate/f7816a9d0ebfc24cf62b15e2479c7f67.png)
![](https://i-blog.csdnimg.cn/blog_migrate/ceb206dd7324ada4744fc3cf5cb7889a.png)
查看日志
![](https://i-blog.csdnimg.cn/blog_migrate/78737d50f059d67554eae5c15d7e5f22.png)
tensorboard --logdir=logs
from PIL import Image
from torch.utils.tensorboard import SummaryWriter
from torchvision import transforms
#python 的用法 tensor数据类型
#通过 transform.ToTensor去看两个问题
#1.transform该如何使用(python)
#2.为什么我们需要 Tensor数据类型
#绝对路径 E:\pytorch\pytorch exercise\hymenoptera_data\hymenoptera_data\train\ants\0013035.jpg
#相对路径 hymenoptera_data/hymenoptera_data/train/ants/0013035.jpg
img_path = "hymenoptera_data/hymenoptera_data/train/ants/0013035.jpg"
img = Image.open(img_path)
#print(img)
tensor_trans = transforms.ToTensor()#直接在transform工具箱当中调用ToTensor工具
tensor_img = tensor_trans(img)
#print(tensor_img)
writer = SummaryWriter("logs")
writer.add_image("Tensor_img",tensor_img)#添加tensor类型数据
writer.close()
![](https://i-blog.csdnimg.cn/blog_migrate/d660d0438d1c4d831f1d1cb91e594e11.png)
添加成功
CALL方法的使用
#call方法的使用
class Person:
def __call__(self, name):
print("__call__"+"Hello " + name)
def hello(self,name):
print("hello"+ name)
person = Person()
person("wangxinyu")
person.hello("xinyu")
call方法就是直接使用对象直接传入参数就可以调用
ctrl+p查看方法需要哪些参数
也叫类对象调用
E:\pytorch\ancaonda\python.exe "E:/pytorch/pytorch exercise/p10_UsefulTransform.py"
__call__Hello wangxinyu
helloxinyu
Process finished with exit code 0
from PIL import Image
from torch.utils.tensorboard import SummaryWriter
from torchvision import transforms
writer = SummaryWriter("logs")
#img = Image.open("hymenoptera_data/0013035.jpg")
img_path = "hymenoptera_data/hymenoptera_data/train/ants/0013035.jpg"
img = Image.open(img_path)
print(img)
先输出图片
![](https://i-blog.csdnimg.cn/blog_migrate/19a5ff5498295bebb3921d1d1fb6fb1b.png)
# #ToTensor使用
trans_totensor = transforms.ToTensor()
img_tensor = trans_totensor(img)
writer.add_image("ToTensor",img_tensor)
writer.close()
加入ToTensor
![](https://i-blog.csdnimg.cn/blog_migrate/0319f5bbe3cf5ce5e13bf8ef84b930a1.png)
#Normallize
print(img_tensor[0][0][0])
trans_norm = transforms.Normalize([0.5,0.5,0.5],[0.5,0.5,0.5])
img_norm = trans_norm(img_tensor)
print(img_norm[0][0][0])
writer.add_image("Normalize",img_norm)
writer.close()
增加Normalize模块
![](https://i-blog.csdnimg.cn/blog_migrate/2ccecc6dd73bfd8fce7582d508ce55bd.png)
#Resize
print(img.size)
trans_resize = transforms.Resize((512,512))
# img PIL ->resize -> img_resize PIL
img_resize = trans_resize(img)
#img _resize PIL -> totensor -> img_resize tensor
img_resize = trans_totensor(img_resize)
writer.add_image("Resize",img_resize,0)
print(img_resize)
Resize的使用
![](https://i-blog.csdnimg.cn/blog_migrate/ac94616c5e9a9b2ab85fb63cc485faed.png)
import torchvision
dataset_transform = torchvision.transforms.Compose([
torchvision.transforms.ToTensor()
])
#root表示数据集存放什么位置
#train默认就是true
#download=True表示下载
train_set = torchvision.datasets.CIFAR10(root="./dataset", train=True, transform=dataset_transform, download=True)
test_set = torchvision.datasets.CIFAR10(root="./dataset", train=False, transform=dataset_transform, download=True)
print(test_set[0])
可以看到已经下载数据集
![](https://i-blog.csdnimg.cn/blog_migrate/b6aaa10934bcf4125dc3195df8eb8e2c.png)
如果下载的慢,把链接放在迅雷当中
Downloading https://www.cs.toronto.edu/~kriz/cifar-10-python.tar.gz to ./dataset\cifar-10-python.tar.gz
print(test_set[0])
print(test_set.classes)
img,target = test_set[0]
print(img)
print(target)
![](https://i-blog.csdnimg.cn/blog_migrate/a4fe0b95f8ecbc84231c2fa5ceefab79.png)
使用Tensorboard添加Tensor图片
#使用tensorboard进行显示
writer = SummaryWriter("p10")
for i in range (10):
img,target = test_set[i]
#添加数据,添加图片
writer.add_image("test_set",img,i)
writer.close()
![](https://i-blog.csdnimg.cn/blog_migrate/ab592650cbf6fa715cc4b310c5294f53.png)
dataloader的使用
主要作用是从dataset中读取数据集
import torchvision
from torch.utils.data import DataLoader
test_set = torchvision.datasets.CIFAR10("./dataset", train=False, transform=torchvision.transforms.ToTensor(),
download=True)
test_loader = DataLoader(dataset=test_set, batch_size=4, shuffle=True, num_workers=0, drop_last=False)
for data in test_loader:
imgs, targets = data
print(imgs.shape)
print(targets)
主要对batch_size=4进行讲解
主要为把dataset数据集4个一组4个一组打包读取
![](https://i-blog.csdnimg.cn/blog_migrate/235e842262508965aaca231a534749e0.png)
第一个4表示有4个这样类型的数据
并且tensor也都进行了标号
import torchvision
from torch.utils.data import DataLoader
from torch.utils.tensorboard import SummaryWriter
test_set = torchvision.datasets.CIFAR10("./dataset", train=False, transform=torchvision.transforms.ToTensor(),
download=True)
#drop_last是对batch_size不能整除的数的处理
#shuffle对读取同一个数据集采用不同的方式
test_loader = DataLoader(dataset=test_set, batch_size=64, shuffle=True, num_workers=0, drop_last=False)
img,target = test_set[0]
print(img.shape)
print(target)
writer = SummaryWriter("dataloader")
for epoch in range(2):
step = 0
for data in test_loader:
imgs, targets = data
# print(imgs.shape)
# print(targets)
writer.add_images("Epoch: {}".format(epoch), imgs, step)
step = step+1
writer.close()
正常训练读取数据流程
import torch
from torch import nn
#创建神经网络模型,nn.Moudle表示继承moudle类,#definit表示重写初始化方法
class xinyu(nn.Module):
def __init__(self):
super().__init__()
#前馈方法,表示输入进入的时候做出改变的函数
def forward(self,input):
output = input + 1
return output
#创建一个神经网络
XINYU = xinyu()
x = torch.tensor(1.0)
output = XINYU(x)
print(output)
![](https://i-blog.csdnimg.cn/blog_migrate/5769e5fdfb3d2722362c78f932510522.png)
使用调试器先看一下步骤
![](https://i-blog.csdnimg.cn/blog_migrate/97841c64c0f74fb26f29bc96d55823f5.png)
实例化模型的时候首先要进行init初始化方法
输入参数的时候进入forward方法
![](https://i-blog.csdnimg.cn/blog_migrate/6d95b181cf1e3e460a8794b944087f35.png)
import torch
import torch.nn.functional as F
#首先定义一个网格图像5*5的图像
input = 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]])
#3*3的卷积核
kernel = torch.tensor([[1,2,1],
[0,1,0],
[2,1,0]])
#修改格式,使用神经网络需要4个参数,2参变4参
input = torch.reshape(input,(1,1,5,5))
kernel = torch.reshape(kernel,(1,1,3,3))
#输出形状
print(input.shape)
print(kernel.shape)
#输入到神经网络中进行输出
output = F.conv2d(input,kernel,stride=1)
print(output)
![](https://i-blog.csdnimg.cn/blog_migrate/aa42fbe99140969ffe504858addbc3bb.jpeg)
import torch
import torch.nn.functional as F
#首先定义一个网格图像5*5的图像
input = 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]])
#3*3的卷积核
kernel = torch.tensor([[1,2,1],
[0,1,0],
[2,1,0]])
#修改格式,使用神经网络需要4个参数,2参变4参
input = torch.reshape(input,(1,1,5,5))
kernel = torch.reshape(kernel,(1,1,3,3))
#输出形状
print(input.shape)
print(kernel.shape)
#输入到神经网络中进行输出
#stride=1每次移动一个路径
output = F.conv2d(input,kernel,stride=1)
print(output)
#stride=1每次移动两个路径
output = F.conv2d(input,kernel,stride=2)
print(output)
#使用padding参数,padding表示填充参数,填充图像的边缘部分
output = F.conv2d(input,kernel,stride=1,padding=1)
print(output)
![](https://i-blog.csdnimg.cn/blog_migrate/f88dfef12bf9226baf24eb6fe813f04f.jpeg)
填充的边缘部分
卷积层的通道数
CNN的卷积核通道数 = 卷积输入层的通道数;CNN的卷积输出层通道数 = 卷积核的个数
![](https://i-blog.csdnimg.cn/blog_migrate/f2ce85073509e775c4cd859bf7a4c974.png)
![](https://i-blog.csdnimg.cn/blog_migrate/f31dfc7fb24f389557d4b4df2891420c.png)
![](https://i-blog.csdnimg.cn/blog_migrate/79af29d180af5176cd1cb0e46e309b17.jpeg)
import torch
import torchvision
from torch.utils.data import DataLoader
from torch import nn
from torch.utils.tensorboard import SummaryWriter
dataset = torchvision.datasets.CIFAR10(root="./data", train=False, download=True,
transform=torchvision.transforms.ToTensor())
dataloader = DataLoader(dataset, batch_size=64)
class MyModule(nn.Module):
def __init__(self):
super(MyModule, self).__init__()
# 二维卷积处理
self.conv1 = nn.Conv2d(in_channels=3, out_channels=6, kernel_size=3, stride=1, padding=0)
def forward(self, x):
x = self.conv1(x)
return x
writer = SummaryWriter("conv_logs")
mymodule = MyModule()
step = 0
for data in dataloader:
imgs, targets = data
output = mymodule(imgs)
# torch.Size([64, 3, 32, 32])
print(imgs.shape)
# torch.Size([64, 6, 30, 30]), 如果直接输出为RGB图像会出错,因为channel数不对(为6)
print(output.shape)
writer.add_images("input", imgs, step)
# [64, 6, 30, 30] -> [128, 3, 30, 30] 使用reshape()将输出的 6 通道转化为 3 通道
#因为彩色图片如果显示只能是3通道,6通道会显示不出来无法识别,强制转换成3通道
output = torch.reshape(output, (-1, 3, 30, 30))
writer.add_images("output", output, step)
step = step + 1
writer.close()
![](https://i-blog.csdnimg.cn/blog_migrate/760b69f9c0331559d3479c8c2f695655.png)
池化
最大池化的使用
1.理解池化
![](https://i-blog.csdnimg.cn/blog_migrate/8ef6af0378a6b1806e1ff3f43325e15f.png)
![](https://i-blog.csdnimg.cn/blog_migrate/04b835f730f074200ca75329cf371252.png)
参数:
![](https://i-blog.csdnimg.cn/blog_migrate/138ef55b51ae1ea74ea40d2f6775c0b4.png)
(stride的默认值是核的尺寸)
![](https://i-blog.csdnimg.cn/blog_migrate/44dc4c108d461c5f514c0b6b8fb684d1.png)
dilation 空洞卷积。如图
![](https://i-blog.csdnimg.cn/blog_migrate/a718da90558ae939ee6ab85d3ebf2695.png)
cell_mode可以选择Floor 和 Ceiling模式,默认为False(Floor)模式
计算方式
![](https://i-blog.csdnimg.cn/blog_migrate/8fa663fa38cff509ae419865747f4024.png)
计算公式
![](https://i-blog.csdnimg.cn/blog_migrate/293398f2ffac7ecfdb09c2dee7b86a47.png)
import torch
from torch import nn
from torch.nn import MaxPool2d
#dtype=torch.float32 把数据修改成浮点类型的数据
input = 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)
#注意修改数据类型,否则报错:"max_pool2d" not implemented for 'Long'
input = torch.reshape(input, (-1,1,5,5))
class XINYU(nn.Module):
def __init__(self):
super(XINYU, self).__init__()
self.maxpool1 = MaxPool2d(kernel_size=3, ceil_mode=True)
def forward(self, input):
output = self.maxpool1(input)
return output
xinyu = XINYU()
output = xinyu(input)
print(output)
八、非线性激活
ReLU函数
![](https://i-blog.csdnimg.cn/blog_migrate/4b89cdfaa3e367e5a913a043979e7d90.png)
import torch
from torch import nn
from torch.nn import ReLU
input = torch.tensor([[1, -0.5],
[-1, 3]])
output = torch.reshape(input, (-1, 1, 2, 2))
print(output.shape)
class XINYU(nn.Module):
def __init__(self):
super(XINYU, self).__init__()
self.relu1 = ReLU() #inplace默认为False
# 意思是不在原数据上改动
def forward(self, input):
output = self.relu1(input)
return output
xinyu = XINYU()
output = xinyu(input)
print(output)
![](https://i-blog.csdnimg.cn/blog_migrate/6e5b9168bd80d47bd90ffadc0a2411ea.png)
relu的作用小于1的全部被舍弃
对图像改变,sigmoid
import torch
import torchvision
from torch import nn
from torch.nn import ReLU, Sigmoid
from torch.utils.data import DataLoader
from torch.utils.tensorboard import SummaryWriter
dataset = torchvision.datasets.CIFAR10('dataset', train=False, download=False,
transform=torchvision.transforms.ToTensor())
dataloader = DataLoader(dataset, batch_size=64)
class ZYJ(nn.Module):
def __init__(self):
super(ZYJ, self).__init__()
self.relu1 = ReLU() #inplace默认为False
# 意思是不在原数据上改动
self.sigmoid1 = Sigmoid()
def forward(self, input):
output = self.sigmoid1(input)
return output
zyj = ZYJ()
writer = SummaryWriter("logs_sigmoid")
step = 0
for data in dataloader:
imgs, target = data
writer.add_images("input", imgs, global_step=step)
output = zyj(imgs)
writer.add_images("output", output, global_step=step)
step = step + 1
writer.close()
![](https://i-blog.csdnimg.cn/blog_migrate/5bd545b5f3eeef362c7ee6c3fed3ba0b.png)
九、线性层及其他层的介绍
1.正则化层——BatchNorm2d
2.Recurrent Layers
用得不多
3.Transformer Layers
用得不多
4.Linear Layers(重点)
下图为线性层,对应的in_feature = d, out_feature = L ,bias = True
![](https://i-blog.csdnimg.cn/blog_migrate/d383d8032ce73c8a47770e9b17b22a9c.png)
![](https://i-blog.csdnimg.cn/blog_migrate/586b6e8a457265b27b81a2d9c785c082.png)
import torch
import torchvision
from torch import nn
from torch.nn import Linear
from torch.utils.data import DataLoader
dataset = torchvision.datasets.CIFAR10("dataset", train=False, transform=torchvision.transforms.ToTensor()
, download=False)
dataloader = DataLoader(dataset, batch_size=64, drop_last=True)
class XINYU(nn.Module):
def __init__(self):
super(XINYU, self).__init__()
self.linear1 = Linear(196608,10)
def forward(self,input):
output = self.linear1(input)
return output
xinyu = XINYU()
for data in dataloader:
imgs, targets = data
print(imgs.shape)
output = torch.reshape(imgs, (1, 1, 1, -1))
#上行代码可以用 output = torch.flatten(imgs)替代
#效果一样,最后得到的数据维数不一样
print(output.shape)
output = xinyu(output)
print(output.shape)
![](https://i-blog.csdnimg.cn/blog_migrate/c0d1d82e12996f81bdabf04ff418ea77.png)
5.Dropout Layers
防止过拟合
6.Sparse Layers
自然语言处理使用得多
7.Distance Function
计算两个值的误差
8.Loss Function
…
![](https://i-blog.csdnimg.cn/blog_migrate/e0a396528d2754fff4bfd208880e4866.png)
首先输入3通道32x32,经过卷积变成32通道x32x32,然后池化变成32通道x16x16,然后以此类推
import torch
from torch import nn
from torch.nn import Conv2d, MaxPool2d, Flatten, Linear, Sequential
from torch.utils.tensorboard import SummaryWriter
class XINYU(nn.Module):
def __init__(self):
super(XINYU, self).__init__()
#Sequential把所有的自定义写的网络过程全都封装在Sequential当中
# ,直接形成moudel然后调用model
self.model = Sequential(
#进入3通道,出去32通道,卷积核大小5,计算的padding等于2
Conv2d(3, 32, 5, padding=2),
#池化层的池化核大小为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, input):
output = self.model(input)
return output
xinyu = XINYU()
input = torch.ones(64, 3, 32, 32)
output = XINYU(input)
print(output.shape)
writer = SummaryWriter("logs_seq")
writer.add_graph(xinyu, input)
writer.close()
损失函数和反向传播
损失函数的作用
1.计算实际输出和目标之间的差距
2.为我们更新输出提供一定的依据(反向传播)
L1Loss 和 MSELoss
此版本torch(1.12.0)对输入和目标shape无要求,均为(*)
但input.shape和target.shape要一致
输入和目标的数据要为浮点型
# torch.nn.L1Loss(size_average=None, reduce=None, reduction='mean')
# L1Loss类对输入和目标shape无要求,都是(*)
import torch
from torch.nn import L1Loss
from torch import nn
# L1Loss要求输入数据是浮点形
inputs = torch.tensor([1, 2, 3], dtype=torch.float32)
targets = torch.tensor([1, 2, 5], dtype=torch.float32)
loss = L1Loss() # 默认 reduction = “mean"
result = loss(inputs, targets)
print(result)
# tensor(0.6667)
#算出的差值除以3
# torch.nn.MSELoss(size_average=None, reduce=None, reduction='mean')
# # MSELoss类对输入和目标shape无要求,都是(*)
# 平方差Loss
loss_mse = nn.MSELoss()
result2 = loss_mse(inputs, targets)
print(result2)
# tensor(1.3333)
# 交叉熵Loss
input = torch.tensor([0.1, 0.2, 0.3])
CrossEntropyLoss
交叉熵Loss
适用于分类问题中
torch.nn.CrossEntropyLoss(weight=None, size_average=None, ignore_index=- 100, reduce=None, reduction=‘mean’, label_smoothing=0.0)
按照输入shape为 (N, C),目标shape为 (N)
其中C= number of classes ; N = batch size
举例
# 交叉熵Loss
# CrossEntropyLoss()
# 按照输入shape为(N, C),目标shape为(N)
# 其中N = number of classes N = batch size
import torch
from torch import nn
input = torch.tensor([0.1, 0.2, 0.3])
target = torch.tensor([1])
loss_cross = nn.CrossEntropyLoss()
# 输入shape需要为(N, C)
input = torch.reshape(input, (1, 3))
result3 = loss_cross(input, target)
print(result3)
import torchvision
from torch import nn
from torch.nn import Sequential, Conv2d, MaxPool2d, Flatten, Linear
from torch.utils.data import DataLoader
dataset = torchvision.datasets.CIFAR10("./dataset", train=False, download=True,
transform=torchvision.transforms.ToTensor())
dataloader = DataLoader(dataset, batch_size=1)
class MyModule(nn.Module):
def __init__(self) -> None:
super().__init__()
self.model1 = Sequential(
Conv2d(in_channels=3, out_channels=32, kernel_size=5, stride=1, padding=2),
MaxPool2d(kernel_size=2),
Conv2d(in_channels=32, out_channels=32, kernel_size=5, stride=1, padding=2),
MaxPool2d(kernel_size=2),
Conv2d(32, 64, 5, padding=2),
MaxPool2d(2),
Flatten(),
Linear(1024, 64),
Linear(64, 10),
)
def forward(self, x):
y = self.model1(x)
return y
myModule = MyModule()
# 交叉熵损失
loss = nn.CrossEntropyLoss()
for data in dataloader:
imgs, targets = data
outputs = myModule(imgs)
# outputs --> tensor([[-0.1541, -0.0937, -0.0411, 0.0405, 0.0265, -0.0111, 0.0820, -0.1124,
# 0.0817, -0.0898]], grad_fn=<AddmmBackward0>)
# targets --> tensor([7])
result_loss = loss(outputs, targets) # result_loss --> tensor(2.2170, grad_fn=<NllLossBackward0>)
# 反向传播,计算节点梯度,根据梯度来优化网络参数,
# 把每一个节点的梯度计算出来,这样之后使用优化器可以进行优化
result_loss.backward()
print(result_loss)
优化器
简单使用
优化器根据梯度对参数进行调整,降低损失
*torch.optim.SGD(params, lr=, momentum=0, dampening=0, weight_decay=0, nesterov=False, , maximize=False, foreach=None)
参数params代表网络模型中的参数
参数lr = learining rate,学习速率。
lr不能太大,也不能太小,太大会造成模型训练起来不稳定,太小训练比较慢。
建议刚开始lr大一些,后面就小一些。
其余参数为算法SGD本身特有的,初学时可以直接使用默认即可。
使用三步走:
对每个节点对应的梯度清0。 optim.zero_grad()
反向传播,计算节点梯度。result_loss.backward()
根据节点中的梯度对参数进行调优。optim.step()
# torch.optim.SGD(params, lr=<required parameter>, momentum=0, dampening=0, weight_decay=0, nesterov=False, *, maximize=False, foreach=None)
# 参数params代表网络模型中的参数
# 参数lr = learining rate,学习速率。
# lr不能太大,也不能太小,太大会造成模型训练起来不稳定,太小训练比较慢。
# 建议刚开始lr大一些,后面就小一些。
# 其余参数为算法SGD本身特有的,初学时可以直接使用默认即可。
import torch
import torchvision
from torch import nn
from torch.nn import Sequential, Conv2d, MaxPool2d, Flatten, Linear
from torch.utils.data import DataLoader
dataset = torchvision.datasets.CIFAR10("./dataset", train=False, download=True,
transform=torchvision.transforms.ToTensor())
dataloader = DataLoader(dataset, batch_size=1)
class MyModule(nn.Module):
def __init__(self) -> None:
super().__init__()
self.model1 = Sequential(
Conv2d(in_channels=3, out_channels=32, kernel_size=5, stride=1, padding=2),
MaxPool2d(kernel_size=2),
Conv2d(in_channels=32, out_channels=32, kernel_size=5, stride=1, padding=2),
MaxPool2d(kernel_size=2),
Conv2d(32, 64, 5, padding=2),
MaxPool2d(2),
Flatten(),
Linear(1024, 64),
Linear(64, 10),
)
def forward(self, x):
y = self.model1(x)
return y
myModule = MyModule()
# 损失函数
loss = 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)
# 对每个节点对应的梯度清0,由于上一次的梯度对于本次的梯度更新是没有用处的。
optim.zero_grad()
# 反向传播,计算节点梯度
result_loss.backward()
# 根据节点中的梯度对参数进行调优
optim.step()
3行之前的weight,grad不为none(非首次循环)
![](https://i-blog.csdnimg.cn/blog_migrate/de35fffd6d7bde7e8b69114dc5076083.png)
43行运行完毕,gard清零,data不变
![](https://i-blog.csdnimg.cn/blog_migrate/90f5bd417c5a4366722be1cd2d2364e4.png)
45行运行完毕,grad被重新计算,data不变
![](https://i-blog.csdnimg.cn/blog_migrate/ccfd176ff081e4153230019d0cbf243c.png)
47行运行完毕,grad不变,data更新(data就是模型中的参数)
![](https://i-blog.csdnimg.cn/blog_migrate/6586e340ea01ea89fa08db854daf22b6.png)
epoch - 多轮训练(类似于多打几轮牌)
-
单纯进行一轮训练,没有任何意义,需要进行多轮训练。
-
损失看的是进行一轮训练损失的总和。
import torch
import torchvision
from torch import nn
from torch.nn import Sequential, Conv2d, MaxPool2d, Flatten, Linear
from torch.utils.data import DataLoader
dataset = torchvision.datasets.CIFAR10("./dataset", train=False, download=True,
transform=torchvision.transforms.ToTensor())
dataloader = DataLoader(dataset, batch_size=1)
class MyModule(nn.Module):
def __init__(self) -> None:
super().__init__()
self.model1 = Sequential(
Conv2d(in_channels=3, out_channels=32, kernel_size=5, stride=1, padding=2),
MaxPool2d(kernel_size=2),
Conv2d(in_channels=32, out_channels=32, kernel_size=5, stride=1, padding=2),
MaxPool2d(kernel_size=2),
Conv2d(32, 64, 5, padding=2),
MaxPool2d(2),
Flatten(),
Linear(1024, 64),
Linear(64, 10),
)
def forward(self, x):
y = self.model1(x)
return y
myModule = MyModule()
# 损失函数
loss = nn.CrossEntropyLoss()
# 优化器
optim = torch.optim.SGD(myModule.parameters(), lr = 0.01)
for epoch in range(20):
running_loss = 0.0
for data in dataloader:
imgs, targets = data
outputs = myModule(imgs)
result_loss = loss(outputs, targets)
# 对每个节点对应的梯度清0,由于上一次的梯度对于本次的梯度更新是没有用处的。
optim.zero_grad()
# 反向传播,计算节点梯度
result_loss.backward()
# 根据节点中的梯度对参数进行调优
optim.step()
running_loss = running_loss + result_loss
print(running_loss)
![](https://i-blog.csdnimg.cn/blog_migrate/c6b1aa024b0420dc1d82e21cf69b6d65.png)
现有网络模型的改变和修改
1、参数pretrained 为false和true时的区别
vgg16_false = torchvision.models.vgg16(pretrained = False)
#False,下载的是网络模型,默认参数
vgg16_true = torchvision.models.vgg16(pretrained = True)
#True,下载的是网络模型,并且在数据集上面训练好的参数。
pretrained=False时,只是加载网络模型,把神经网络的代码加载了进来,其中的参数都是默认的参数,不需要下载。
pretrained=True时,它就要去从网络中下载,比如说卷积层对应的参数时多少,池化层对应的参数时多少等。这些参数都是在 ImageNet 数据集中训练好的。
torchvision-models
vgg16:
vgg16_f=torchvision.models.vgg16(pretrained=False)
vgg16_t=torchvision.models.vgg16(pretrained=True)
vgg16_t的weight:
![](https://i-blog.csdnimg.cn/blog_migrate/a7dc3e34062f6d10fe09c67f77f5208d.png)
vgg16_f的weight:
![](https://i-blog.csdnimg.cn/blog_migrate/84a7552cf828cf8e07dd7b58976f0518.png)
2、现有网络模型的使用与修改
如何利用现有的网络,改变网络的框架来符合我们的需求?
我们之前使用的数据集CIFAR10只分为10个类别
如果我们加载了vgg16模型,我们如何应用这个网络模型呢?
有两个方法:
(1)将 (6): Linear(in_features=4096, out_features=1000, bias=True)改为Linear(in_features=4096, out_features=10, bias=True)
(2)再加一个线性层 Linear(in_features=1000, out_features=10, bias=True)
import torchvision
# 利用现有的网络改动
from torch import nn
vgg16_false = torchvision.models.vgg16(pretrained=False)
vgg16_true = torchvision.models.vgg16(pretrained=True)
print(vgg16_true)
train_data = torchvision.datasets.CIFAR10('./dataset_ts', train=True, transform=torchvision.transforms.ToTensor(),download=True)
# 在vgg16的classifier下加一层模型,名叫add_linear,module名,in_feature=1000,out_feature=10
vgg16_true.add_module('add_linear', nn.Linear(1000, 10))
print(vgg16_true)
print(vgg16_false)
# 修改最后一行结构为out_feature=10
vgg16_false.classifier[6] = nn.Linear(4096, 10)
print(vgg16_false)
p26网络模型的保存与读取
保存:(以vgg16为例)
import torch
import torchvision
vgg16=torchvision.models.vgg16(pretrained=False)
#方式1(.pth不是必须的)
torch.save(vgg16,"vgg16.pth")
#方式2:将参数保存为字典格式(官方推荐)
torch.save(vgg16.state_dict(),"vgg16_2.pth")
读取:
import torch
import torchvision
vgg16=torchvision.models.vgg16(pretrained=False)
#方式一=》保存方式一(路径)
model=torch.load("vgg16.pth")
print(model)
#方式2=》字典格式
#注意方式2要printvgg16这个模型,不能model=xxx,print(model)
vgg16.load_state_dict(torch.load("vgg16_2.pth"))
print(vgg16)
注意,以方式一形式读取自定义模型时要先将该模型复制或引用到读取文件中,否则会报错
p27-29完整模型训练
载入模型、数据
加载数据
创建网络模型
初始化损失函数、优化器
设置训练网络的参数(训练、测试次数、轮数)
添加tensorboard(可视化)
for epoch:
for data in trainset:
分割数据,loss,清零,优化
for data in testset:
分割数据,计算准确度
展现每轮总loss,特定步数loss,保存模型
准确度计算思路:找出最大预测结果与target相同的图片数量,用其除以图片总数
import torch
import torchvision
#引入model模块,import * 表示引入model文件中的所有部分
from torch.utils.tensorboard import SummaryWriter
from model import *
from torch.utils.data import DataLoader
train_data=torchvision.datasets.CIFAR10("../datasets",train=True,transform=torchvision.transforms.ToTensor(),
download=True)
test_data=torchvision.datasets.CIFAR10("../datasets",train=False,transform=torchvision.transforms.ToTensor(),
download=True)
train_size=len(train_data)
test_size=len(test_data)
#格式化字符串的写法
print("size of train,test is:{},{}".format(train_size,test_size))
#利用dataloader加载
train_dataloader=DataLoader(train_data,64)
test_dataloader=DataLoader(test_data,64)
# 创建网络模型
wxy = WXY()
# 损失函数
loss_f = nn.CrossEntropyLoss()
# 优化器
# 1e-2=0.01
learning_rate = 1e-2
opt = torch.optim.SGD(wxy.parameters(), lr=learning_rate, )
# 设置训练网络的参数
# 记录训练次数
train_step = 0
# 测试次数
test_step = 0
# 训练轮数
epoch = 10
# 添加tensoeboard
writer = SummaryWriter("train_log")
for i in range(epoch):
print("第{}轮训练开始".format(i + 1))
# 训练步骤开始,开始取数据
# 有时不必要:test1.train()
for data in train_dataloader:
imgs, target = data
output = wxy(imgs)
loss = loss_f(output, target)
# 优化器优化模型
#清零梯度
opt.zero_grad()
#反向传播计算梯度
loss.backward()
#优化梯度
opt.step()
#增加训练步骤
train_step = train_step + 1
# loss.item更加规范(.item不会打印数据类型,例如tensor(5))
#按照数量去打印,每训练100次打印一次
if train_step % 100 == 0:
# loss.item() item把数值变成数字
print("训练次数{},loss值为{}".format(train_step, loss.item()))
writer.add_scalar("train_loss", loss.item(), train_step)
loss_total = 0
# 测试步骤开始
# 有时不必要:test1.eval()
total_correct = 0
#with表示的是后面的代码没有梯度,主要测试训练的模型是不是可以
with torch.no_grad():
for data in test_dataloader:
imgs, t = data
output = wxy(imgs)
loss = loss_f(output, t)
#求出测试集中总的loss
loss_total = loss_total + loss.item()
test_step = test_step + 1
# argmax参数:1为横向比较,2为纵向比较,output为64,10的矩阵
# output.argmax(1)==t是为了得到[Ture,False,True....]这种形式
# .sum:T为1,F为0
corect = (output.argmax(1) == t).sum()
total_correct = total_correct + corect
#计算准确率
accuracy = total_correct / test_size
print("测试集总loss{}".format(loss_total))
writer.add_scalar("test_loss", loss_total, test_step)
writer.add_scalar("accuracy", accuracy, test_step)
torch.save(wxy, "test1{}.pth".format(i))
print("模型已保存")
![](https://i-blog.csdnimg.cn/blog_migrate/6df6ef6e60dea9d1041e72d386a9357e.png)
![](https://i-blog.csdnimg.cn/blog_migrate/8b43c96d7f32013315670d1de4817849.png)
![](https://i-blog.csdnimg.cn/blog_migrate/e6cdb972c78611206cfd45ef4063cab7.png)
使用GPU训练模型
第一种方法:
![](https://i-blog.csdnimg.cn/blog_migrate/27ae7950e88e8aae892d68128d35d2a4.png)
import torch
import torchvision
from torch import nn
from torch.utils.data import DataLoader
from torch.utils.tensorboard import SummaryWriter
from model import *
#准备数据集
train_data = torchvision.datasets.CIFAR10('data', train=True, transform=torchvision.transforms.ToTensor(), download=True)
test_data = torchvision.datasets.CIFAR10('data', train=False, transform=torchvision.transforms.ToTensor(), download=True)
#数据集长度
train_data_size = len(train_data)
test_data_size = len(test_data)
print("训练数据集长度:{}".format(train_data_size))
print("测试数据集长度:{}".format(test_data_size))
#加载数据集(Dataloader)
train_dataloader = DataLoader(train_data, batch_size=64)
test_dataloader = DataLoader(test_data, batch_size=64)
#创建网络模型
tudui = WXY()
if torch.cuda.is_available():#1、网络模型
tudui = tudui.cuda()
#损失函数
loss_fn = nn.CrossEntropyLoss()
if torch.cuda.is_available():#2、损失函数
loss_fn = loss_fn.cuda()
#优化器
#learning_rate = 0.01
learning_rate = 1e-2
optimizer = torch.optim.SGD(tudui.parameters(), lr=learning_rate)
#参数
total_train_step = 0
total_test_step = 0
epoh = 10
#添加tensorbooard
writer = SummaryWriter("logs/trainlogs")
for i in range(epoh):
print("-----第{}轮训练-----".format(i+1))
#训练开始
tudui.train()#模型状态
for data in train_dataloader:
imgs, targets = data
if torch.cuda.is_available():#3、数据
imgs = imgs.cuda()
targets = targets.cuda()
outputs = tudui(imgs)
loss = loss_fn(outputs, targets)
#优化器调优
optimizer.zero_grad()
loss.backward()
optimizer.step()
total_train_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()#模型状态
total_test_loss = 0
total_accuracy = 0
with torch.no_grad():
for data in test_dataloader:
imgs, targets = data
if torch.cuda.is_available():#3、数据
imgs = imgs.cuda()
targets = targets.cuda()
outputs = tudui(imgs)
loss = loss_fn(outputs, targets)
total_test_loss = total_test_loss + loss.item()
accuracy = (outputs.argmax(1) == targets).sum()
total_accuracy = total_accuracy + accuracy
print("测试集loss:{}".format(total_test_loss))
print("测试集正确率: {}".format(total_accuracy / test_data_size))
total_test_step = total_test_step + 1
writer.add_scalar("test_loss", total_test_loss, total_test_step)
writer.add_scalar("test_accuracy", total_accuracy / test_data_size, total_test_step)
torch.save(tudui, "model/tudui_{}.pth".format(i))
writer.close()