anaconda3 安装
下载地址:https://mirrors.tuna.tsinghua.edu.cn/anaconda/archive/
下载过后,配置环境变量,如下所示。
python 和 anaconda 都是python环境,将之前配置的python环境变量删掉,替换为下面。
配置过后在cmd控制台检验版本
conda --version
提示未找到的话就重启下主机。
将先前的python版本 加入到 后来的anaconda
安装过 anaconda 后,输入指令
conda env list # 查看 conda 中的python 环境
当要采用某一conda 环境时
activate base # 激活环境
deactivate # 关闭环境
anaconda 默认只有一个 base 环境,添加新环境:
conda create --name python310 python=3.10 # python310 是文件夹、最后是指定版本
创建过后在 安装 anaconda3 的目录的 envs 下可看到 python310
关键的一步来了。如果想用自己之前安装的python版本加入到 anaconda3 中,则直接找到自己python的位置,将其中的所有文件粘贴到此处 python310 文件夹中。直接替换即可!
使用 jupyter
activate 环境 # 先激活某一环境
jupyter notebook # 进入编辑页面
在编辑界面常用快捷键:
- Tab键:代码补全
- Ctrl+Enter:运行当前cell
- A/a:在上方新建 cell
- B/a:在下方新建 cell
- 双击D/d:删除当前 cell
- Z:撤销删除
在 pycharm 配置 conda 环境
如果是新建项目:pycharm 配置 conda 环境
如果是拿到开源项目,进行配置:打开设置 settings -> Python Interpreter -> Add Interpreter -> Add Local Interpreter -> Conda Environment -> 注意是第一个 Interpreter 最右面选取 conda 环境位置(一般是在终端通过 conda create 创建的环境都在 anaconda3下的envs包下,选中想要的环境下的 python.exe 即可配置完成)
问题:在 pycharm 终端无法激活 conda 环境
解决办法
Dataset 获取数据集
Dataset 主要是提供了一种方式去获取数据以及数据的 label
下面以蚂蚁和蜜蜂的数据集图片为例,运用 Dataset。
import os.path
from torch.utils.data import Dataset
from PIL import Image
class MyData(Dataset): # 继承自Dataset
def __init__(self, root_dir, label_dir):
self.root_dir = root_dir # 数据总目录
self.label_dir = label_dir # 总目录下的某label数据集名称
self.path = os.path.join(self.root_dir, self.label_dir) # 拼接路径
self.img_path = os.listdir(self.path) # 拼接后的路径下的该label所有图片
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 # 返回图片,以及该图片 label
def __len__(self):
return len(self.img_path)
root_dir = "dataset/train"
ants_label_dir = "ants_image"
bees_label_dir = "bees_image"
ants_dataset = MyData(root_dir, ants_label_dir) # 根据路径获取到蚂蚁数据集
bees_dataset = MyData(root_dir, bees_label_dir) # 根据路径获取到蜜蜂数据集
print(ants_dataset[1]) # 通过索引获取值时,相当于是调用 __getitem__ 方法,返回img 和 label
train_dataset = ants_dataset + bees_dataset # 数据集之间可直接相加
Tensorboard
常用于展示图象,比如若想查看训练过程中图像每一步的变化,每一阶段的显示结果。
例如:如下代码
from torch.utils.tensorboard import SummaryWriter
writer = SummaryWriter("logs")
for i in range(100):
writer.add_scalar("y=x", i, i)
writer.close()
执行过后在终端输入:tensorboard --logdir=logs
在给出的网址查看:
再如下面代码:
import numpy as np
from torch.utils.tensorboard import SummaryWriter
from PIL import Image
writer = SummaryWriter("logs")
image_path = "dataset/train/ants_image/0013035.jpg"
img_PIL = Image.open(image_path)
img_array = np.array(img_PIL) # 将类型转为 numpy 类型
print(type(img_array))
print(img_array.shape) # 形式为 HWC
writer.add_image("test", img_array, 1, dataformats="HWC") # 四个参数:名称、图像、步骤数、指定数据格式(默认接收CHW,不加会报错)
writer.close()
执行上面代码后,做如下改动,再执行代码。
image_path = "dataset/train/bees_image/16838648_415acd9e3f.jpg"
writer.add_image("test", img_array, 1, dataformats="HWC") # 四个参数:名称、图像、步骤数、指定数据格式(默认接收CHW,而我们图像转为numpy格式后是HWC形式,不因此需指定格式)
去网页查看:
可以对不同的名称,分别展示:
writer.add_image("train", img_array, 2, dataformats="HWC") # 这里用train名称
Transforms
主要用于对图片做变换、做处理
执行结构如下所示:输入图片-创建具体工具-使用工具-输出结果
tensor 数据类型包装了反向神经网络中所需要的一些理论基础参数。在神经网络中需要此数据类型进行训练。
下面结合 tensorboard,writer.add_image 传入 tensor 类型。
from PIL import Image
from torch.utils.tensorboard import SummaryWriter
from torchvision import transforms
img_path = "dataset/train/ants_image/0013035.jpg"
img = Image.open(img_path)
writer = SummaryWriter("logs")
tensor_trans = transforms.ToTensor() # transforms的 ToTensor类,接收 PIL image 或 numpy.array 类型转为 tensor类型的图片
tensor_img = tensor_trans(img) # 将图片转换为 tensor 类型,这里传入的是 PIL image类型。当用 cv2 来读取图片时:cv_img = cv2.imread(img_path) 得到的是 numpy.array类型。
print(tensor_img)
writer.add_image("Tensor_img", tensor_img, 1) # tensorboard既接收 numpy.array 类型,又接收 tensor 类型
writer.close()
展示:
注:在一个方法的括号中按 ctrl + p,可查看需要传入的参数。
在 structure 可以看到 transforms.py 下的所有工具类
接下来简要再练习两个:
归一化 Normalize:
归一化是为了消除奇异值,即样本数据中与其他数据相比特别大或特别小的数据,这样可以加快训练速度。
对图片每一信道的值(比如图片的RGB值)归一化
# ToTensor
trans_tensor = transforms.ToTensor() # transforms的 ToTensor类,接收 PIL image 或 numpy.array 类型转为 tensor类型的图片
img_tensor = trans_tensor(img) # 将图片转换为 tensor 类型
print(img_tensor)
writer.add_image("Tensor_img", img_tensor, 1) # tensorboard既接收 numpy 类型,又接收 tensor 类型
# Normalize 传入每个信道的均值 和 标准差
trans_norm = transforms.Normalize([0.5, 0.5, 0.5], [0.5, 0.5, 0.5])
img_norm = trans_norm(img_tensor) # 需要输入一个 tensor 图片
writer.add_image("Normalize", img_norm, 1)
展示
换不同的参数,多执行几步,展示:
Resize
对图像进行等比的缩放。
# Resize
print(img.size)
trans_resize = transforms.Resize((128, 128))
img_resize = trans_resize(img)
img_resize_tensor = trans_tensor(img_resize)
writer.add_image("Resize", img_resize_tensor, 2)
print(img_resize)
256 x 256 -> 128 x 128
Compose
主要是对多个转换操作合并,逐个执行。
# Compose
# Resize 只传一个参数时,另一参数会进行等比缩放
trans_resize_2 = transforms.Resize(128) # 原图片为 768 x 512 ,这里只传入128,较短的512会变成128,然后768等比例缩放。
trans_compose = transforms.Compose([trans_resize_2, trans_tensor]) # 先进行 resize 变换,再进行 tensor 转换
img_resize_2 = trans_compose(img)
writer.add_image("Compose", img_resize_2, 2)
RandomCrop 随机裁剪
# 仅输入一个参数,从 768x512中 裁剪 512x512
trans_random = transforms.RandomCrop(512)
trans_compose_2 = transforms.Compose([trans_random, trans_tensor])
for i in range(10):
img_crop = trans_compose_2(img)
writer.add_image("RandomCrop", img_crop, i)
可查看到每一步图片的大小都不变,但裁剪位置在变化。
Torchvision 中的数据集使用
在 pytorch 官网可查找常用数据集
这里测试采用 CIFAR-10 数据集
下载测试 CIFAR-10 数据集代码如下:
import torchvision
train_set = torchvision.datasets.CIFAR10(root="./dataset", train=True, download=True) # 当已经下载过后,再执行,就不会再下载
test_set = torchvision.datasets.CIFAR10(root="./dataset", train=False, download=True)
print(test_set.classes) # 数据集中所有的label
img, target = test_set[0] # test_set 第一个数据,返回图片,和该图片的label索引
print(test_set.classes[target]) # 展示该图片label
img.show() # 展示图片
下面结合 transforms 和 tensorboard 进行测试:
import torchvision
from torch.utils.tensorboard import SummaryWriter
dateset_transforms = torchvision.transforms.Compose([ # 变换规则,这里只将 PIL img 转为 tensor
torchvision.transforms.ToTensor()
])
# 指定变换规则,对全体数据执行此变换
train_set = torchvision.datasets.CIFAR10(root="./dataset", train=True, transform=dateset_transforms, download=True) # 当已经下载过后,再执行,就不会再下载
test_set = torchvision.datasets.CIFAR10(root="./dataset", train=False, transform=dateset_transforms, download=True)
writer = SummaryWriter("logs")
for i in range(10):
img_tensor, target = train_set[i] # 获取 tensor
print(target)
writer.add_image("CIFAR", img_tensor, i)
writer.close()
终端输入 :
tensorboard --logdir=logs --port=6007
展示:
Dataloader
前面说了 dataset,dataset作用主要是告诉数据集的位置,以及通过索引获取到每一个数据。
dataloader的作用是通过设置一些参数,来从 dataset 中分批次取出数据。
官方介绍:官方介绍
如下代码:
import torchvision
from torch.utils.data import DataLoader
from torch.utils.tensorboard import SummaryWriter
# 获取数据集、并通过 ToTensor 将数据集转为 tensor
test_data = torchvision.datasets.CIFAR10("./dataset", train=False, transform=torchvision.transforms.ToTensor())
# 从 test_data 中获取,每次取64个,shuffle采取不打乱(epoch每一轮都一样),num_workers多进程取0,drop_last 为数据集对64取模的余数是否舍弃
test_loader = DataLoader(dataset=test_data, batch_size=64, shuffle=False, num_workers=0, drop_last=True)
# 测试数据集中第一张图片及 target
img, target = test_data[0]
print(img.shape) # [3,32,32]
print(target) # 3
writer = SummaryWriter("logs")
step = 0
# 每一个 data 对应每次取出的64个数据
for data in test_loader:
imgs, target = data
writer.add_images("dataloader", imgs, step) # 注意这里是 add_images
step = step + 1
writer.close()
在终端执行:
tensorboard --logdir=logs --port=6007 --samples_per_plugin=images=10000000
–samples_per_plugin=images=10000000 目的是 展示出每一 step
展示如下:每一 step 展示64个数据
当把 shuffle 设为 True 时,可发现两轮 epoch 展示的数据不一致。
import torchvision
from torch.utils.data import DataLoader
from torch.utils.tensorboard import SummaryWriter
# 获取数据集、并通过 ToTensor 将数据集转为 tensor
test_data = torchvision.datasets.CIFAR10("./dataset", train=False, transform=torchvision.transforms.ToTensor())
# 从 test_data 中获取,每次取64个,shuffle采取不打乱(epoch每一轮都一样),num_workers多进程取0,drop_last 为数据集对64取模的余数是否舍弃
test_loader = DataLoader(dataset=test_data, batch_size=64, shuffle=True, num_workers=0, drop_last=True)
# 测试数据集中第一张图片及 target
img, target = test_data[0]
print(img.shape) # [3,32,32]
print(target) # 3
writer = SummaryWriter("logs")
# shuffle = True ,每轮从 test_loader 中取数据会不一致!
for epoch in range(2):
step = 0
for data in test_loader:
imgs, target = data
writer.add_images("Epoch: {}".format(epoch), imgs, step) # 注意这里是 add_images
step = step + 1
writer.close()
展示如下:
神经网络
官方文档:https://pytorch.org/docs/stable/nn.html
卷积层
输入图像 C x H x W,输入卷积核 C x ?x ?,卷积核在输入图像进行卷积,图像对应位一一相乘,得到一层channel。根据输出结果要求的 channel 个数 n,就需要采取 n 个卷积核对输入图像卷积来得到 n 层 channel
输入数据图像 --> 执行卷积 --> 查看卷积后的图像与原图像对比
import torch
import torchvision.datasets
from torch import nn
from torch.utils.data import DataLoader
from torch.utils.tensorboard import SummaryWriter
dataset = torchvision.datasets.CIFAR10("./dataset", train=False, transform=torchvision.transforms.ToTensor(),
download=True)
dataloader = DataLoader(dataset, batch_size=64)
# 两步骤:
# 1、初始化,构造卷积函数
# 2、通过输入,执行卷积,返回结果
class NN(nn.Module):
def __init__(self): # 初始化,构造卷积函数
super(NN, 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
nn = NN() # 拿到卷积函数
writer = SummaryWriter("./logs")
step = 0
for data in dataloader:
imgs, targets = data # imgs 的shape 为 [64, 3, 32, 32] NCHW N为数量,C为颜色通道(这里是RGB三通道),HW分别为图像高和宽
output = nn(imgs) # 图像执行卷积 [64, 3, 32, 32] --> [64, 6, 30, 30] (每一个3*3*3的卷积核形成一个channel,最后输出6个channel,就需要6个3*3*3的卷积核)
output = torch.reshape(output, (-1,3,30,30)) # 由于输出 channel 为6,图像无法展示,因此此处通过 reshape 将其 channel 转为3,砍去的维度平铺在水平方向,N为-1,自动匹配个数
writer.add_images("input", imgs, step) # 卷积前图像
writer.add_images("output", output, step) # 卷积后图像
step = step + 1
在控制台输入:
tensorboard --logdir=logs --port=6007 --samples_per_plugin=images=10000000
展示:
池化层
池化作用:在保留大部分图像特征的情况下,去除冗余参数,减少数据量,使训练的更快。
输入图像:C x H x N,输入池化核 ?x ?,这里做最大池化(将输入图像中每池化核大小仅保留最大值),池化前后 channel 不变。
输入数据图像 --> 执行池化 --> 查看池化后的图像与原图像对比
import torch
import torchvision.datasets
from torch import nn
from torch.utils.data import DataLoader
from torch.utils.tensorboard import SummaryWriter
dataset = torchvision.datasets.CIFAR10("./dataset", train=False, transform=torchvision.transforms.ToTensor(),
download=True)
dataloader = DataLoader(dataset, batch_size=64)
# 两步骤:
# 1、初始化,构造卷积函数
# 2、通过输入,执行卷积,返回结果
class NN(nn.Module):
def __init__(self): # 初始化,构造卷积函数
super(NN, self).__init__()
self.maxpool1 = nn.MaxPool2d(kernel_size=3, ceil_mode=False) # 定义池化核为 3*3 池化前后 channel 不变。
def forward(self, input): # 执行卷积
output = self.maxpool1(input)
return output
nn = NN() # 拿到卷积函数`
writer = SummaryWriter("./logs")
step = 0
for data in dataloader:
imgs, targets = data
writer.add_images("input", imgs, step)
print(imgs.shape)
outputs = nn(imgs)
print(outputs.shape)
writer.add_images("output", outputs, step)
step = step + 1
writer.close()
上述代码的 print :
池化过后,C 不变, H 和 W 明显缩小,因为我 池化核 为 3*3,未设置 padding、并且 ceil_mode 设的为 false,因此就是 32/10 向下取整,即10。
若将 ceil_mode 设为 True,则为 11*11
在 tensorboard 上查看:
如果展示为同一尺寸,可发现图像特征明显模糊,但仍保留大部分特征。
非线性变换(非线性激活)
非线性变换主要是为网络中引入非线性特征,非线性特征越多,才能训练出符合各种特征的模型。
常用的有 Relu、Sigmoid
import torch
import torchvision.datasets
from torch import nn
from torch.utils.data import DataLoader
from torch.utils.tensorboard import SummaryWriter
dataset = torchvision.datasets.CIFAR10("./dataset", train=False, transform=torchvision.transforms.ToTensor(),
download=True)
dataloader = DataLoader(dataset, batch_size=64)
class NN(nn.Module):
def __init__(self):
super(NN, self).__init__()
self.sigmoid = nn.Sigmoid()
def forward(self, x):
output = self.sigmoid(x)
return output
nn = NN()
writer = SummaryWriter("./logs")
step = 0
for data in dataloader:
imgs, targets = data
writer.add_images("input", imgs, step)
outputs = nn(imgs) # sigmoid 非线性变换
writer.add_images("output", outputs, step)
step = step + 1
writer.close()
线性层(全连接层)
线性层又称为全连接层,其上的每个神经元都与上一层所有神经元相连。实现对前一层的线性组合,线性变换。
上图 g1 的值就等于 k1 * x1+b1 + k1 * x2+b2 + … + k1 * xd+bd
对于上面的 vgg16,线性层对应于 输入 [1 x 1 x 4096 ] --> [1 x 1 x 1000]
代码演示:
import torch
import torchvision.datasets
from torch import nn
from torch.utils.data import DataLoader
dataset = torchvision.datasets.CIFAR10("./dataset", train=False, transform=torchvision.transforms.ToTensor(),
download=True)
dataloader = DataLoader(dataset, batch_size=64)
class NN(nn.Module):
def __init__(self):
super(NN, self).__init__()
self.linear = nn.Linear(196608, 10)
def forward(self, input):
return self.linear(input)
nn = NN()
for data in dataloader:
imgs, targets = data
print(imgs.shape)
output = torch.flatten(imgs)
print(output.shape) # 摊平后的 图像shape,作为线性层 input
output = nn(output)
print(output.shape) # 线性层后的 图像shape
Sequential 的使用
前面提到的CIFAR-10 数据集是根据图片图片内容,识别出图片类别,-10 意思是一共有10类
此数据集网络架构模型如下:
通过下面公式,由上面图片已知 input、output、kernel_size、计算出执行卷积操作时 padding、stride。计算得到padding 为2,stride 为1。
代码实现CIFAR-10 结构:
import torch
from torch import nn
class NN(nn.Module):
def __init__(self):
super(NN, self).__init__()
self.model1 = nn.Sequential( # Sequential 的作用就是将一系列变换封装到一个 model
nn.Conv2d(3, 32, 5, padding=2, stride=1),
nn.MaxPool2d(2),
nn.Conv2d(32, 32, 5, padding=2, stride=1),
nn.MaxPool2d(2),
nn.Conv2d(32, 64, 5, padding=2, stride=1),
nn.MaxPool2d(2),
nn.Flatten(),
nn.Linear(1024, 64),
nn.Linear(64, 10)
)
def forward(self, x):
x = self.model1(x) # 只需执行此 model 就可以了,缩减代码量
return x
nn = NN()
print(nn) # 查看构造的架构
input = torch.ones((64, 3, 32, 32)) # 构造输入,shape与图片一样,假设64张图,
output = nn(input)
print(output.shape) # 查看输出
输出:
可通过 tensorboard 查看输入nn后,执行的细节
import torch
from torch import nn
from torch.utils.tensorboard import SummaryWriter
class NN(nn.Module):
def __init__(self):
super(NN, self).__init__()
self.model1 = nn.Sequential(
nn.Conv2d(3, 32, 5, padding=2, stride=1),
nn.MaxPool2d(2),
nn.Conv2d(32, 32, 5, padding=2, stride=1),
nn.MaxPool2d(2),
nn.Conv2d(32, 64, 5, padding=2, stride=1),
nn.MaxPool2d(2),
nn.Flatten(),
nn.Linear(1024, 64),
nn.Linear(64, 10)
)
def forward(self, x):
x = self.model1(x)
return x
nn = NN()
print(nn)
input = torch.ones((64, 3, 32, 32))
output = nn(input)
print(output.shape)
writer = SummaryWriter("./logs")
writer.add_graph(nn, input)
writer.close()
损失函数
理解为:用来计算 输入 与 目标结果的误差
import torch
from torch import nn
inputs = torch.tensor([1, 2, 3], dtype=torch.float32)
targets = torch.tensor([1, 2, 5], dtype=torch.float32)
print(inputs)
print(targets)
inputs = torch.reshape(inputs, (1, 1, 1, 3)) # 通过reshape 转为我们要的维度 即从 tensor([1, 2, 3]) --> tensor([[[[1, 2, 3]]]])
targets = torch.reshape(targets, (1, 1, 1, 3)) # tensor([1, 2, 5]) --> tensor([[[[1, 2, 5]]]])
print(inputs)
print(targets)
loss = nn.L1Loss()
result = loss(inputs, targets)
print(result)
下面用交叉熵函数作为损失函数,计算一下 CIFAR-10 输入一张图片后的误差
交叉熵损失函数计算公式:
在CIFAR-10中,x 就是包含10个数据,分别指代输入图像在10个类别的取值。x[class] 代表实际类别的那个取值,当 x[class]越大,则 -x[class] 越小,即推测到实际类别的命中率越高,最终 loss 就越小。
import torchvision
from torch import nn
from torch.utils.data import DataLoader
dataset = torchvision.datasets.CIFAR10("./dataset", train=False, transform=torchvision.transforms.ToTensor(),
download=True)
dataloader = DataLoader(dataset, batch_size=1)
class NN(nn.Module):
def __init__(self):
super(NN, self).__init__()
self.model1 = nn.Sequential(
nn.Conv2d(3, 32, 5, padding=2, stride=1),
nn.MaxPool2d(2),
nn.Conv2d(32, 32, 5, padding=2, stride=1),
nn.MaxPool2d(2),
nn.Conv2d(32, 64, 5, padding=2, stride=1),
nn.MaxPool2d(2),
nn.Flatten(),
nn.Linear(1024, 64),
nn.Linear(64, 10)
)
def forward(self, x):
x = self.model1(x)
return x
loss = nn.CrossEntropyLoss() # 损失函数为交叉熵
nn = NN()
for data in dataloader:
imgs, targets = data
outputs = nn(imgs)
print(outputs) # 输出为 [10],即每个类比的取值
print(targets) # 实际类别
result_loss = loss(outputs, targets) # 交叉熵公式计算误差
print(result_loss) # 输出损失
损失函数计算得来的误差是为了后续的反向传播,反向传播针对每一个卷积参数都会有一个梯度,优化过程中就是通过梯度对参数进行优化,以达到降低loss的目的。
优化器
通过刚才说的损失函数,每一次经过网络得到损失后,可通过反向传播计算得到每个网络中每个参数的梯度,再利用优化器通过反向传播的参数梯度,对参数进行优化调整。
import torchvision
import torch
from torch import nn
from torch.utils.data import DataLoader
dataset = torchvision.datasets.CIFAR10("./dataset", train=False, transform=torchvision.transforms.ToTensor(),
download=True)
dataloader = DataLoader(dataset, batch_size=1)
class NN(nn.Module):
def __init__(self):
super(NN, self).__init__()
self.model1 = nn.Sequential(
nn.Conv2d(3, 32, 5, padding=2, stride=1),
nn.MaxPool2d(2),
nn.Conv2d(32, 32, 5, padding=2, stride=1),
nn.MaxPool2d(2),
nn.Conv2d(32, 64, 5, padding=2, stride=1),
nn.MaxPool2d(2),
nn.Flatten(),
nn.Linear(1024, 64),
nn.Linear(64, 10)
)
def forward(self, x):
x = self.model1(x)
return x
loss = nn.CrossEntropyLoss() # 损失函数为交叉熵
nn = NN()
optim = torch.optim.SGD(nn.parameters(), lr=0.01) # 选用SGD随机梯度下降优化,传入网络中的参数,学习率设为0.01,其他参数取默认
for data in dataloader:
print("---------------")
for epoch in range(20): # 对每一个数据,执行20次,每一次都在优化
imgs, targets = data
outputs = nn(imgs) # 通过神经网络,得到输出
result_loss = loss(outputs, targets) # 通过神经网络的输出,得到损失(网络输出与真实target的差距)
print(result_loss)
optim.zero_grad() # 将网络模型中每个可以调节的参数对应的梯度调为0,这个是必要的,防止出现误差问题。
result_loss.backward() # 反向传播,求出每一个参数的梯度
optim.step() # 对每一个参数进行调优
查看输出结果:
现有模型网络的使用与修改
见如下代码所示:
import torchvision
from torch import nn
from torchvision.models import VGG16_Weights
vgg16_false = torchvision.models.vgg16(weights=None) # 只模型,参数未经过训练
vgg16_true = torchvision.models.vgg16(weights=VGG16_Weights.DEFAULT) # 模型带调好的参数
print(vgg16_true)
vgg16_true.classifier.add_module('add_linear', nn.Linear(1000, 10)) # 在模型的 classifier 中增加一层
print(vgg16_true)
print(vgg16_false)
vgg16_false.classifier[6] = nn.Linear(4096, 10) # 修改模型的 classifier 中的第 7 步操作(下标从0开始)
print(vgg16_false)
代码分别针对 vgg16 的两次模型变化如下:
网络模型的保存和读取
方式一:保存 模型结构 + 模型参数
# 保存方式1,模型结构 + 模型参数
torch.save(vgg16, "vgg16_method1.pth")
读取时:
# 保存方式一时的加载方式
model = torch.load("vgg16_method1.pth")
print(model)
注意:方式一在保存时,如果是自己的模型,读取时需要注意将自己模型所在的类 import 进来。
方式二:只保存模型参数(官方推荐)
torch.save(vgg16.state_dict(), "vgg16_method2.pth")
读取时:
# 保存方式二时的加载方式:先获取模型,然后把参数加载进去
vgg16 = torchvision.models.vgg16(weights=None)
vgg16.load_state_dict(torch.load("vgg16_method2.pth"))
print(vgg16)
完整的模型训练
创建 model.py 用来存储模型,我选用的仍是针对 CIFAR-10 。
from torch import nn
class NN(nn.Module):
def __init__(self):
super(NN, self).__init__()
self.model1 = nn.Sequential(
nn.Conv2d(3, 32, 5, padding=2, stride=1),
nn.MaxPool2d(2),
nn.Conv2d(32, 32, 5, padding=2, stride=1),
nn.MaxPool2d(2),
nn.Conv2d(32, 64, 5, padding=2, stride=1),
nn.MaxPool2d(2),
nn.Flatten(),
nn.Linear(1024, 64),
nn.Linear(64, 10)
)
def forward(self, x):
x = self.model1(x)
return x
创建 full_train_model.py 来训练模型
import torchvision
import torch
from torch.utils.data import DataLoader
from model import * # 引入模型
from torch import nn
# 准备数据集
train_data = torchvision.datasets.CIFAR10("../dataset", train=True, transform=torchvision.transforms.ToTensor(),
download=True)
test_data = torchvision.datasets.CIFAR10("../dataset", train=False, transform=torchvision.transforms.ToTensor(),
download=True)
# length 长度
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)
# 创建神经网络模型
mymodel = NN()
# 损失函数为交叉熵
loss_fn = nn.CrossEntropyLoss()
# 选用SGD随机梯度下降优化
optim = torch.optim.SGD(mymodel.parameters(), lr=0.01)
# 设置训练网络的一些参数
# 记录训练的次数
total_train_step = 0
# 记录测试的次数
total_train_step = 0
# 训练的轮数
epoch = 20
for i in range(epoch):
print("--------第 {} 轮训练开始--------".format(i+1))
for data in train_dataloader:
imgs, targets = data
outputs = mymodel(imgs) # 通过神经网络,得到输出
loss = loss_fn(outputs, targets) # 通过神经网络的输出,得到损失(网络输出与真实target的差距)
# 优化器优化网络模型
optim.zero_grad() # 将网络模型中每个可以调节的参数对应的梯度调为0,这个是必要的,防止出现误差问题。
loss.backward() # 反向传播,求出每一个参数的梯度
optim.step() # 对每一个参数进行调优
total_train_step = total_train_step + 1
if total_train_step % 100 == 0:
print("训练次数: {},Loss: {}".format(total_train_step, loss.item())) # item 是将tensor中数据拿出来,tensor(1) --> 1
# 每一轮的对现有模型进行测试
print("--------第 {} 轮测试开始--------".format(i+1))
total_test_loss = 0
with torch.no_grad(): # 保证在下面的代码没有梯度。只是单纯的测试
for data in test_dataloader:
imgs, targets = data
outputs = mymodel(imgs)
loss = loss_fn(outputs, targets)
total_test_loss = total_test_loss + loss.item()
print("整体测试集上的Loss:{}".format(total_test_loss))
经过 20 轮训练可看到结果如下:
随着模型训练的持续,test_loss 的总值持续降低。
添加 tensorboard 查看:
- train_loss 每100次训练的变化
- test_loss 每轮的变化
- test_accuracy 预测正确率每轮的变化
import torchvision
import torch
from torch.utils.data import DataLoader
from torch.utils.tensorboard import SummaryWriter
from model import * # 引入模型
from torch import nn
# 准备数据集
train_data = torchvision.datasets.CIFAR10("../dataset", train=True, transform=torchvision.transforms.ToTensor(),
download=True)
test_data = torchvision.datasets.CIFAR10("../dataset", train=False, transform=torchvision.transforms.ToTensor(),
download=True)
# length 长度
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)
# 创建神经网络模型
mymodel = NN()
# 损失函数为交叉熵
loss_fn = nn.CrossEntropyLoss()
# 选用SGD随机梯度下降优化
optim = torch.optim.SGD(mymodel.parameters(), lr=0.01)
# 设置训练网络的一些参数
# 记录训练的次数
total_train_step = 0
# 记录测试的次数
total_test_step = 0
# 训练的轮数
epoch = 20
# 添加 tensorboard
writer = SummaryWriter("../logs")
for i in range(epoch):
print("--------第 {} 轮训练开始--------".format(i+1))
for data in train_dataloader:
imgs, targets = data
outputs = mymodel(imgs) # 通过神经网络,得到输出
loss = loss_fn(outputs, targets) # 通过神经网络的输出,得到损失(网络输出与真实target的差距)
# 优化器优化网络模型
optim.zero_grad() # 将网络模型中每个可以调节的参数对应的梯度调为0,这个是必要的,防止出现误差问题。
loss.backward() # 反向传播,求出每一个参数的梯度
optim.step() # 对每一个参数进行调优
total_train_step = total_train_step + 1
if total_train_step % 100 == 0:
print("训练次数: {},Loss: {}".format(total_train_step, loss.item())) # item 是将tensor中数据拿出来,tensor(1) --> 1
writer.add_scalar("train_loss", loss.item(), total_train_step)
# 每一轮的对现有模型进行测试
print("--------第 {} 轮测试开始--------".format(i+1))
total_test_loss = 0
total_accuracy = 0
with torch.no_grad(): # 保证在下面的代码没有梯度。只是单纯的测试
for data in test_dataloader:
imgs, targets = data
outputs = mymodel(imgs)
loss = loss_fn(outputs, targets)
total_test_loss = total_test_loss + loss.item()
accuracy = (outputs.argmax(1) == targets).sum() # outputs.argmax(1) 指对outputs每一行取最大值,也就是预测的结果。然后与实际结果targets进行比较,求和得出预测正确的个数
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 = total_test_step + 1
torch.save(mymodel, "mymodel_{}.pth".format(i+1))
print("第 {} 轮训练模型已保存".format(i+1))
writer.close()
在 tensorboard上查看:
每一轮模型的保存:
使用GPU训练
1、对网络模型、损失函数、数据的输入等 后加 .cuda()
2、指定device,然后 to()
教程:使用GPU训练
模型验证
已知模型的10个类别的索引如下:
去网上随机找了两张图片:
创建 verify.py,代码如下:
import torch
import torchvision
from PIL import Image
image_path ="imgs/plane.png"
image = Image.open(image_path)
image = image.convert("RGB") # png格式是四个通道,除了RGB三个通道外还有一个透明度通道,因此这里做转化只保留其RGB通道
transform = torchvision.transforms.Compose([torchvision.transforms.Resize((32, 32)), # 更改输入图片的尺寸为待验证模型的输入尺寸
torchvision.transforms.ToTensor()])
image = transform(image)
print(image.shape)
model = torch.load("mymodel_20.pth") # 获取到模型,训练时第二十次成功率在60+
print(model)
image = torch.reshape(image, (1, 3, 32, 32)) # 改变图片维度 为 输入维度,模型输入的维度为4维
model.eval()
with torch.no_grad():
output = model(image)
print(output) # 输出结果
print(output.argmax(1)) # 最终类别对应的索引
当输入 plane 图片时,输出结果为:
刚好对应上面展示的飞机类别的索引。
当把输入图像改为 dog:
image_path ="imgs/dog1.png"
输出结果为:
对应狗类别的索引。
逻辑斯蒂回归
最常用的是 sigmoid 函数:返回结果 0-1 ,常用于分类问题,计算每一类的概率
还有一些返回 -1~1 的逻辑斯蒂函数
与线性回归的区别:在线性回归的基础上运用逻辑斯蒂函数(用 σ 来表示)
文章评论