pytorch学习
基本工具
Dataset、DataLoader、transforms,tensorboard
这四个东西之间的关系(简单理解)
-
Dataset
用于获取数据集,包括训练集和测试集,pytorch本身提供了一些数据集,可以使用命令直接下载获取
-
DataLoader
获取数据集后DataLoader负责分批传给网络进行训练,一些概念batch、epoch
-
transforms
用来对原始数据集进行处理,转换成tensor数据类型
-
tensorboard
简单理解就是进行数据展示(数据可视化,web端)
Dataset
提供一种方式获取数据及其label
主要提供两个功能
- 如何获取每一个数据及其label
- 告诉我们总共有多少数据
获取数据集有两种方式:直接从电脑文件中读入、直接下载获取torchvision官方提供的数据集
从电脑文件中读入数据集
- 创建一个类继承Dataset
- 确定数据文件所在的位置(路径/目录)和数据的标签
- 通过os和PIL Image相关操作进行处理
具体代码及细节如下
from torch.utils.data import Dataset
from PIL import Image
import os
# 定义MyData类继承Dataset类
class MyData(Dataset):
"""
构造函数 __init__(self, arg1, arg2...)
用该类创建一个对象时先自动调用构造函数,创建对象时可以指定初始化参数
构造函数中定义的self.root_dir self.label_dir等这些是实例属性
此处构造函数的具体作用是通过传入参数,获取某个标签下的所有图片信息
"""
def __init__(self, root_dir, label_dir):
self.root_dir = root_dir # dataset/train 注意是相对路径
self.label_dir = label_dir # ants
self.path = os.path.join(self.root_dir, self.label_dir) # dataset/train/ants
self.img_list = os.listdir(self.path) # ants下所有文件名列表
"""
__getitem__(self, idx)
通过下标idx获取一张图片
"""
def __getitem__(self, idx):
img_name = self.img_list[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_list)
# 定义参数
root_dir = "dataset/train"
ants_label_dir = "ants"
bees_label_dir = "bees"
ants_dataset = MyData(root_dir, ants_label_dir) # 实例化ants_dataset对象
bees_dataset = MyData(root_dir, bees_label_dir) # 实例化bees_dataset对象
train_dataset = ants_dataset + bees_dataset # 将两个数据集拼接
直接下载获取torchvision官方提供的数据集
在此过程中可结合transforms(对数据进行处理和变换)和tensorboard(展示数据)
具体代码实现及细节如下,读取torchvision下数据集CIFAR10
参数解释:
- root:原始数据文件存放路径
- train:是否读取训练数据集
- transform:transform处理,ToTensor()将图片转换为tensor类型
- download:是否要下载数据,初次运行需要下载,之后只需验证,不用下载
import torchvision
from torch.utils.tensorboard import SummaryWriter
# 指定读取数据集时将图片转为tensor
dataset_transforms = torchvision.transforms.Compose([
torchvision.transforms.ToTensor()
])
# 读取训练数据集
train_set = torchvision.datasets.CIFAR10(root="./dataset", train=True, transform=dataset_transforms, download=True)
# 读取测试数据集
test_set = torchvision.datasets.CIFAR10(root="./dataset", train=False, transform=dataset_transforms, download=True)
# 使用tensorboard展示测试数据集前10个图片
writer = SummaryWriter(log_dir="./logs")
for i in range(10):
img, label = test_set[i]
writer.add_image("tensor image", img, i)
writer.close()
transforms
什么是transforms
transforms是一个工具包,里面定义了很多工具类,可对图片进行各种处理,包括:修改大小、剪裁、类型转换等。transforms里面定义了ToTensor这样一个类,可以将PIL Image格式的图片转化为tensor
使用方法:首先要用ToTensor实例化一个对象tool,再把image作为参数调用tool,返回值就是这个图片的tensor
具体实现代码如下
from torchvision import transforms
from PIL import Image
img_path = "dataset/train/ants/5650366_e22b7e1065.jpg" # 原始图片相对路径
img = Image.open(img_path)
tensor_trans = transforms.ToTensor() # 实例化一个ToTensor对象
tensor_img = tensor_trans(img) # 传入image参数,获取该图片tensor返回值
# print(tensor_img)
什么是tensor
tensor(张量)是封装了原始图片信息以训练神经网络所需要的其他的一些新的数据类型
网络中处理的数据基本都是tensor类型,需要先将图片转化为tensor数据类型
DataLoader
Dataset是获取数据集,而Dataloader用来管理如何从数据集中取数据进行训练,例如:每次取几个数据、取完一轮之后要不要进行打乱、刚刚取过的一批数据要不要放到底部等
import torchvision
from torch.utils.data import DataLoader
from torch.utils.tensorboard import SummaryWriter
trans = torchvision.transforms.ToTensor()
# 准备测试数据集
test_set = torchvision.datasets.CIFAR10(root="./dataset", train=False, transform=trans, download=True)
# 64个图片为一批,打包为一个整体(batch_size)
# 一趟epoch后打乱顺序(shuffle=True)
# 最后一批不足64个舍去(drop_last=True)
dataloader = DataLoader(test_set, batch_size=64, shuffle=True, num_workers=0, drop_last=True)
writer = SummaryWriter(log_dir="./logs")
step = 0
for data in dataloader:
imgs, targets = data # data为一个batch,返回64个图片的tensor和标签
writer.add_images("dataloader-3", imgs, step)
step += 1
writer.close()
tensorboard
简单理解就是将数据可视化的工具
操作流程
-
实例化一个SummaryWriter对象writer
指定输出文件的路径
-
向writer中添加数据
-
关闭writer
-
启动tensorboard,去web端查看数据
from torch.utils.tensorboard import SummaryWriter
import numpy as np
from PIL import Image
# 创建编写器,保存日志
# log_dir保存路径 "./logs"当前目录下logs目录
writer = SummaryWriter(log_dir="./logs")
image_path = "data/train/bees_image/16838648_415acd9e3f.jpg"
img_PIL = Image.open(image_path)
img_array = np.array(img_PIL)
writer.add_image("test", img_array, 2, dataformats='HWC')
# 关闭
writer.close()
在中断命令行中输入启动命令
tensorboard --logdir=logs
点击本地web链接,在浏览器中打开即可
整体架构
准备数据
使用Dataset获取数据集,图片转化为tensor类型,使用DataLoader分批
# 准备数据集:训练数据集 测试数据集
train_data = torchvision.datasets.CIFAR10(root="./dataset", train=True, transform=torchvision.transforms.ToTensor(),
download=True)
test_data = torchvision.datasets.CIFAR10(root="./dataset", 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)
搭建并创建网络
网络搭建流程
- 定义一个网络类继承nn.Module
- 在构造函数中搭建指定网络
- 重写forward,接收参数为输入特征向量,返回经过网络前向传播的结果
搭建一个CIFAR10的卷积神经网络,3个卷积层,每一个卷积层后跟一个池化层,全连接层转化为10个输出类
创建网络:用定义好的类实例化一个对象
# 搭建神经网路
class Model(nn.Module):
def __init__(self):
super(Model, self).__init__()
self.model = nn.Sequential(
nn.Conv2d(3, 32, 5, 1, 2), # 卷积层1
nn.MaxPool2d(2), # 池化层1
nn.Conv2d(32, 32, 5, 1, 2), # 卷积层2
nn.MaxPool2d(2), # 池化层2
nn.Conv2d(32, 64, 5, 1, 2), # 卷积层3
nn.MaxPool2d(2), # 池化层3
nn.Flatten(), # 展开成一个向量1*1024
nn.Linear(1024, 64), # 全连接层
nn.Linear(64, 10)
)
# 前向传播,返回输出层结果
def forward(self, x):
x = self.model(x)
return x
model = Model() # 创建网络
注意此处跟着up教程做没有加激活函数,改进后代码如下
# 搭建卷积神经网络(添加激活函数)
class Model(nn.Module):
def __init__(self):
super(Model, self).__init__()
self.con1 = nn.Conv2d(3, 32, 5, 1, 2)
self.con2 = nn.Conv2d(32, 32, 5, 1, 2)
self.con3 = nn.Conv2d(32, 64, 5, 1, 2)
self.maxPool = nn.MaxPool2d(2)
self.ft = nn.Flatten()
self.linear1 = nn.Linear(1024, 64)
self.linear2 = nn.Linear(64, 10)
# 前向传播,返回输出层结果
def forward(self, x):
x = self.maxPool(F.relu(self.con1(x))) # 第1层卷积->非线性->池化
x = self.maxPool(F.relu(self.con2(x))) # 第2层卷积->非线性->池化
x = self.maxPool(F.relu(self.con3(x))) # 第3层卷积->非线性->池化
# x = nn.functional.relu(self.maxPool(self.con1(x)))
# x = nn.functional.relu(self.maxPool(self.con2(x)))
# x = nn.functional.relu(self.maxPool(self.con3(x)))
x = self.ft(x)
x = F.relu(self.linear1(x))
x = self.linear2(x)
return x
注意激活函数是在卷积之后池化之前使用
不加激活函数会出现过拟合,在训练集上的损失继续减少,而在测试集上的损失在20轮之前减小,之后持续增大
添加激活函数,大概在30轮之后也过拟合,但是与不加激活函数相比,其过拟合的程度要低,在测试集上的准确度也比不加激活函数略高一些
定义损失函数
交叉熵(pytorch定义好的损失函数,具体理论不清楚,需要学习⭐)
也相当于用nn.CrossEntropyLoss这个类实例化一个对象
loss_fn = nn.CrossEntropyLoss()
训练
- 确定训练轮数epoch
- 在每一轮中,分批(batch)遍历所有的训练数据集
- 在每一批中,根据输入前向传播获取输出结果
- 根据输出结果和真实值,调用损失函数
- 根据损失函数,误差逆传播获取梯度
- 更新参数
for i in range(epoch):
print("---------第{}轮训练开始---------".format(i))
# 训练步骤开始
for data in train_dataloader:
imgs, targets = data
outputs = model(imgs) # 前向传播获取输出
loss = loss_fn(outputs, targets) # 获取损失函数
# 优化器调优
optimizer.zero_grad() # 每次优化器梯度要重置
loss.backward() # 误差逆传播
optimizer.step() # 更新参数
测试
使用测试数据集进行测试一定要禁止计算梯度和更新参数,可以每一批训练完后查看测试结果
基本流程
- 根据输入前向传播获取输出结果
- 将输出结果和真实值进行比对,获取准确率
# 测试步骤开始
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 = model(imgs) # 前向传播获得输出
loss = loss_fn(outputs, targets) # 计算损失函数
total_test_loss += loss
accuracy = (outputs.argmax(1) == targets).sum() # 计算准确率
total_accuracy += accuracy
print("整体测试集上的Loss: {}".format(total_test_loss))
print("整体测试集上的正确率: {}".format(total_accuracy / test_data_size))
使用gpu训练
- 定义gpu设备
- 在创建模型、创建损失函数、输入数据时指定设备
# 定义训练的设备
device = torch.device("cuda")
model = Model()
model = model.to(device)
loss_fn = nn.CrossEntropyLoss()
loss_fn = loss_fn.to(device)
imgs, targets = data
imgs = imgs.to(device)
targets = targets.to(device)
添加上tensorboard展示的完成代码如下
import torch
import torchvision
from torch import nn
from torch.utils.data import DataLoader
from torch.utils.tensorboard import SummaryWriter
# from model import *
# 定义训练的设备
device = torch.device("cuda")
# 准备数据集:训练数据集 测试数据集
train_data = torchvision.datasets.CIFAR10(root="./dataset", train=True, transform=torchvision.transforms.ToTensor(),
download=True)
test_data = torchvision.datasets.CIFAR10(root="./dataset", 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)
# 搭建神经网路
class Model(nn.Module):
def __init__(self):
super(Model, self).__init__()
self.model = nn.Sequential(
nn.Conv2d(3, 32, 5, 1, 2),
nn.MaxPool2d(2),
nn.Conv2d(32, 32, 5, 1, 2),
nn.MaxPool2d(2),
nn.Conv2d(32, 64, 5, 1, 2),
nn.MaxPool2d(2),
nn.Flatten(),
nn.Linear(1024, 64),
nn.Linear(64, 10)
)
# 前向传播,返回输出层结果
def forward(self, x):
x = self.model(x)
return x
# 创建网络模型
model = Model()
model = model.to(device)
# 损失函数
loss_fn = nn.CrossEntropyLoss()
loss_fn = loss_fn.to(device)
# 优化器
learning_rate = 1e-2
optimizer = torch.optim.SGD(model.parameters(), learning_rate)
# 设置训练网络的一些参数
total_train_step = 0 # 记录训练的次数
total_test_step = 0 # 记录测试的次数
epoch = 10 # 训练轮数
# 添加tensorboard
writer = SummaryWriter(log_dir="./logs")
for i in range(epoch):
print("---------第{}轮训练开始---------".format(i))
# 训练步骤开始
for data in train_dataloader:
imgs, targets = data
imgs = imgs.to(device)
targets = targets.to(device)
outputs = model(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))
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
imgs = imgs.to(device)
targets = targets.to(device)
outputs = model(imgs)
loss = loss_fn(outputs, targets)
total_test_loss += loss
accuracy = (outputs.argmax(1) == targets).sum()
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
writer.close()