参考书本:
任务时长:2天任务名称:PyTorch数据读取机制Dataloader与Dataset;数据预处理transforms模块机制
任务简介:学习PyTorch数据读取机制中的两个重要模块Dataloader与Dataset;熟悉数据预处理transforms方法的运行机制
详细说明:
本节第一部分介绍pytorch的数据读取机制,通过一个人民币分类实验来学习pytorch是如何从硬盘中读取数据的,并且深入学习数据读取过程中涉及到的两个模块Dataloader与Dataset
第二部分介绍数据的预处理模块transforms的运行机制,数据在读取到pytorch之后通常都需要对数据进行预处理,包括尺寸缩放、转换张量、数据中心化或标准化等等,这些操作都是通过transforms进行的,所以本节重点学习transforms的运行机制
并介绍数据标准化(Normalize)的使用原理
用valid 用来验证模型是否过拟合,用valid挑选模型
用test来测试挑选出来的模型的性能
sampler生成索引,样本的序号
DataSet根据索引来读取图片以及标签
例子:
# -*- coding: utf-8 -*-
"""
# @file name : train_lenet.py
# @author : tingsongyu
# @date : 2019-09-07 10:08:00
# @brief : 人民币分类模型训练
"""
import os
import random
import numpy as np
import torch
import torch.nn as nn
from torch.utils.data import DataLoader
import torchvision.transforms as transforms
import torch.optim as optim
from matplotlib import pyplot as plt
os.sys.path.append(r"G:my_github_projectskillsLearn_PyTorch02-01-DataLoader与Dataset")
print(os.sys.path)
from model.lenet import LeNet
from tools.my_dataset import RMBDataset
def set_seed(seed=1):
random.seed(seed)
np.random.seed(seed)
torch.manual_seed(seed)
torch.cuda.manual_seed(seed)
set_seed() # 设置随机种子
rmb_label = {"1": 0, "100": 1}
# 参数设置
MAX_EPOCH = 10
BATCH_SIZE = 16
LR = 0.01
log_interval = 10
val_interval = 1
# ============================ step 1/5 数据 ============================
# 设置路径
# 对于一个分类问题,这里data_dir目录下一般包括两个文件夹:train和valid,
# 每个文件件下面包含N个子文件夹,N是你的分类类别数,
# 每个子文件夹里存放的就是这个类别的图像
split_dir = os.path.join("..", "..", "data", "rmb_split")
train_dir = os.path.join(split_dir, "train")
valid_dir = os.path.join(split_dir, "valid")
# 设置均值和方差
norm_mean = [0.485, 0.456, 0.406]
norm_std = [0.229, 0.224, 0.225]
# 数据预处理
train_transform = transforms.Compose([
transforms.Resize((32, 32)), # 缩放
transforms.RandomCrop(32, padding=4), # 裁剪
transforms.ToTensor(), # 将图像转成张量数据
# 该函数作用对象需要是一个Tensor,所以在上一步通过transforms.ToTensor()生成Tensor
transforms.Normalize(norm_mean, norm_std),
])
valid_transform = transforms.Compose([
transforms.Resize((32, 32)),
transforms.ToTensor(),
transforms.Normalize(norm_mean, norm_std),
])
# 构建MyDataset实例
# Dataset必须是用户自己构建,构建时传入两个参数,
# data_dir:数据路径 即从哪读数据?
# transform:数据预处理
# 按住ctrl后用鼠标点击函数可以直接跳转到函数具体实现的位置
train_data = RMBDataset(data_dir=train_dir, transform=train_transform)
valid_data = RMBDataset(data_dir=valid_dir, transform=valid_transform)
# 构建DataLoder
# 数据迭代器,传入数据,数据大小等相关设置
# shuffle=True 表示每一个epoch中的样本都是乱序
train_loader = DataLoader(dataset=train_data, batch_size=BATCH_SIZE, shuffle=True)
valid_loader = DataLoader(dataset=valid_data, batch_size=BATCH_SIZE)
# ============================ step 2/5 模型 ============================
# 初始化卷积神经网络LeNet
net = LeNet(classes=2)
net.initialize_weights()
# ============================ step 3/5 损失函数 ============================
# 在PyTorch中采用torch.nn模块来定义网络的所有层,
# 比如卷积、降采样、损失层等等,这里采用交叉熵函数
# 分类任务通常采用交叉熵损失
criterion = nn.CrossEntropyLoss() # 选择损失函数
# ============================ step 4/5 优化器 ============================
# 优化器使用随机梯度下降
optimizer = optim.SGD(net.parameters(), lr=LR, momentum=0.9) # 选择优化器
# 定义学习率的变化策略,这里采用的是torch.optim.lr_scheduler模块的StepLR类,
# 表示每隔step_size个epoch就将学习率降为原来的gamma倍。
scheduler = torch.optim.lr_scheduler.StepLR(optimizer, step_size=10, gamma=0.1) # 设置学习率下降策略
# ============================ step 5/5 训练 ============================
train_curve = list()
valid_curve = list()
# 以epoch为周期,在每个epoch中会有多个iteration的训练,在每一个iteration中训练模型
for epoch in range(MAX_EPOCH):
loss_mean = 0.
correct = 0.
total = 0.
# 从DataLoader迭代器中不停地去获取一个Batchsize大小的数据
net.train()
for i, data in enumerate(train_loader): # 在每一个iteration中训练模型
# forward
inputs, labels = data # 每一次读取一个batchsize大小的数据
outputs = net(inputs) # 输入到模型中进行前向传播
# backward # 反向传播获取梯度
optimizer.zero_grad() # 将网络中的梯度清零
loss = criterion(outputs, labels) # 将输出的outputs和原来导入的labels作为loss函数的输入就可以得到损失
# 计算得到loss后就要回传损失
# 要注意的是这是在训练的时候才会有的操作,测试时候只有forward过程。
loss.backward()
# update weights
# 更新权重
# 回传损失过程中会计算梯度,
# 然后需要根据这些梯度更新参数,
# optimizer.step()就是用来更新参数的
optimizer.step()
# 统计分类情况
# 统计分类准确率
# 得到输出后(网络的全连接层的输出)
# 还希望能到到模型预测该样本属于哪个类别的信息,这里采用torch.max
# torch.max()的第一个输入是tensor格式,
# 所以用outputs.data而不是outputs作为输入
# 第二个参数1是代表dim的意思,也就是取每一行的最大值,
# 其实就是我们常见的取概率最大的那个index
_, predicted = torch.max(outputs.data, 1)
total += labels.size(0)
correct += (predicted == labels).squeeze().sum().numpy()
# 打印训练信息
loss_mean += loss.item()
train_curve.append(loss.item())
if (i+1) % log_interval == 0:
loss_mean = loss_mean / log_interval
print("Training:Epoch[{:0>3}/{:0>3}] Iteration[{:0>3}/{:0>3}] Loss: {:.4f} Acc:{:.2%}".format(
epoch, MAX_EPOCH, i+1, len(train_loader), loss_mean, correct / total))
loss_mean = 0.
scheduler.step() # 更新学习率
# validate the model
# 在每一个epoch中会进行验证集的测试,通过验证集来观察模型是否过拟合
if (epoch+1) % val_interval == 0:
correct_val = 0.
total_val = 0.
loss_val = 0.
net.eval()
with torch.no_grad():
for j, data in enumerate(valid_loader):
inputs, labels = data
outputs = net(inputs)
loss = criterion(outputs, labels)
_, predicted = torch.max(outputs.data, 1)
total_val += labels.size(0)
correct_val += (predicted == labels).squeeze().sum().numpy()
loss_val += loss.item()
valid_curve.append(loss_val/valid_loader.__len__())
print("Valid:t Epoch[{:0>3}/{:0>3}] Iteration[{:0>3}/{:0>3}] Loss: {:.4f} Acc:{:.2%}".format(
epoch, MAX_EPOCH, j+1, len(valid_loader), loss_val, correct_val / total_val))
train_x = range(len(train_curve))
train_y = train_curve
train_iters = len(train_loader)
valid_x = np.arange(1, len(valid_curve)+1) * train_iters*val_interval # 由于valid中记录的是epochloss,需要对记录点进行转换到iterations
valid_y = valid_curve
plt.plot(train_x, train_y, label='Train')
plt.plot(valid_x, valid_y, label='Valid')
plt.legend(loc='upper right')
plt.ylabel('loss value')
plt.xlabel('Iteration')
plt.show()
# ============================ inference ============================
BASE_DIR = os.path.dirname(os.path.abspath(__file__))
test_dir = os.path.join(BASE_DIR, "test_data")
test_data = RMBDataset(data_dir=test_dir, transform=valid_transform)
valid_loader = DataLoader(dataset=test_data, batch_size=1)
for i, data in enumerate(valid_loader):
# forward
inputs, labels = data
outputs = net(inputs)
_, predicted = torch.max(outputs.data, 1)
rmb = 1 if predicted.numpy()[0] == 0 else 100
print("模型获得{}元".format(rmb))
# -*- coding: utf-8 -*-
"""
# @file name : dataset.py
# @author : yts3221@126.com
# @date : 2019-08-21 10:08:00
# @brief : 各数据集的Dataset定义
"""
import os
import random
from PIL import Image
from torch.utils.data import Dataset
random.seed(1)
rmb_label = {"1": 0, "100": 1}
class RMBDataset(Dataset):
def __init__(self, data_dir, transform=None):
"""
rmb面额分类任务的Dataset
:param data_dir: str, 数据集所在路径
:param transform: torch.transform,数据预处理
"""
self.label_name = {"1": 0, "100": 1}
self.data_info = self.get_img_info(data_dir) # data_info存储所有图片路径和标签,在DataLoader中通过index读取样本
self.transform = transform
# 根据索引index返回图像及标签,即获取图像
def __getitem__(self, index):
path_img, label = self.data_info[index] # 通过self.data_info函数得到图像路径和标签
# 通过Image.open得到img
img = Image.open(path_img).convert('RGB') # 0~255
if self.transform is not None:
img = self.transform(img) # 在这里做transform,转为tensor等等
return img, label
# 查看数据长度,即样本的数量,数据集的数量
def __len__(self):
return len(self.data_info)
# 自定义的函数
# 用于获取路径和标签
@staticmethod
def get_img_info(data_dir):
data_info = list()
for root, dirs, _ in os.walk(data_dir):
# 遍历类别
for sub_dir in dirs:
img_names = os.listdir(os.path.join(root, sub_dir))
img_names = list(filter(lambda x: x.endswith('.jpg'), img_names))
# 遍历图片
for i in range(len(img_names)):
img_name = img_names[i]
path_img = os.path.join(root, sub_dir, img_name)
label = rmb_label[sub_dir]
data_info.append((path_img, int(label)))
return data_info
# -*- coding: utf-8 -*-
"""
# @file name : lenet.py
# @author : yts3221@126.com
# @date : 2019-08-21 10:08:00
# @brief : lenet模型定义
"""
import torch.nn as nn
import torch.nn.functional as F
class LeNet(nn.Module):
def __init__(self, classes):
super(LeNet, self).__init__()
self.conv1 = nn.Conv2d(3, 6, 5)
self.conv2 = nn.Conv2d(6, 16, 5)
self.fc1 = nn.Linear(16*5*5, 120)
self.fc2 = nn.Linear(120, 84)
self.fc3 = nn.Linear(84, classes)
def forward(self, x):
out = F.relu(self.conv1(x))
out = F.max_pool2d(out, 2)
out = F.relu(self.conv2(out))
out = F.max_pool2d(out, 2)
out = out.view(out.size(0), -1)
out = F.relu(self.fc1(out))
out = F.relu(self.fc2(out))
out = self.fc3(out)
return out
def initialize_weights(self):
for m in self.modules():
if isinstance(m, nn.Conv2d):
nn.init.xavier_normal_(m.weight.data)
if m.bias is not None:
m.bias.data.zero_()
elif isinstance(m, nn.BatchNorm2d):
m.weight.data.fill_(1)
m.bias.data.zero_()
elif isinstance(m, nn.Linear):
nn.init.normal_(m.weight.data, 0, 0.1)
m.bias.data.zero_()
class LeNet2(nn.Module):
def __init__(self, classes):
super(LeNet2, self).__init__()
self.features = nn.Sequential(
nn.Conv2d(3, 6, 5),
nn.ReLU(),
nn.MaxPool2d(2, 2),
nn.Conv2d(6, 16, 5),
nn.ReLU(),
nn.MaxPool2d(2, 2)
)
self.classifier = nn.Sequential(
nn.Linear(16*5*5, 120),
nn.ReLU(),
nn.Linear(120, 84),
nn.ReLU(),
nn.Linear(84, classes)
)
def forward(self, x):
x = self.features(x)
x = x.view(x.size()[0], -1)
x = self.classifier(x)
return x
# -*- coding: utf-8 -*-
"""
# @file name : 1_split_dataset.py
# @author : tingsongyu
# @date : 2019-09-07 10:08:00
# @brief : 将数据集划分为训练集,验证集,测试集
"""
import os
import random
import shutil
def makedir(new_dir):
if not os.path.exists(new_dir):
os.makedirs(new_dir)
if __name__ == '__main__':
random.seed(1)
dataset_dir = os.path.join("..", "..", "data", "RMB_data")
split_dir = os.path.join("..", "..", "data", "rmb_split")
train_dir = os.path.join(split_dir, "train")
valid_dir = os.path.join(split_dir, "valid")
test_dir = os.path.join(split_dir, "test")
train_pct = 0.8
valid_pct = 0.1
test_pct = 0.1
for root, dirs, files in os.walk(dataset_dir):
for sub_dir in dirs:
imgs = os.listdir(os.path.join(root, sub_dir))
imgs = list(filter(lambda x: x.endswith('.jpg'), imgs))
random.shuffle(imgs)
img_count = len(imgs)
train_point = int(img_count * train_pct)
valid_point = int(img_count * (train_pct + valid_pct))
for i in range(img_count):
if i < train_point:
out_dir = os.path.join(train_dir, sub_dir)
elif i < valid_point:
out_dir = os.path.join(valid_dir, sub_dir)
else:
out_dir = os.path.join(test_dir, sub_dir)
makedir(out_dir)
target_path = os.path.join(out_dir, imgs[i])
src_path = os.path.join(dataset_dir, sub_dir, imgs[i])
shutil.copy(src_path, target_path)
print('Class:{}, train:{}, valid:{}, test:{}'.format(sub_dir, train_point, valid_point-train_point,
img_count-valid_point))
# -*- coding: utf-8 -*-
"""
# @file name : train_lenet.py
# @author : tingsongyu
# @date : 2019-09-07 10:08:00
# @brief : 人民币分类模型训练
"""
import os
import random
import numpy as np
import torch
import torch.nn as nn
from torch.utils.data import DataLoader
import torchvision.transforms as transforms
import torch.optim as optim
from matplotlib import pyplot as plt
os.sys.path.append(r"G:my_github_projectskillsLearn_PyTorch02-01-DataLoader与Dataset")
print(os.sys.path)
from model.lenet import LeNet
from tools.my_dataset import RMBDataset
def set_seed(seed=1):
random.seed(seed)
np.random.seed(seed)
torch.manual_seed(seed)
torch.cuda.manual_seed(seed)
set_seed() # 设置随机种子
rmb_label = {"1": 0, "100": 1}
# 参数设置
MAX_EPOCH = 10
BATCH_SIZE = 16
LR = 0.01
log_interval = 10
val_interval = 1
# ============================ step 1/5 数据 ============================
# 设置路径
# 对于一个分类问题,这里data_dir目录下一般包括两个文件夹:train和valid,
# 每个文件件下面包含N个子文件夹,N是你的分类类别数,
# 每个子文件夹里存放的就是这个类别的图像
split_dir = os.path.join("..", "..", "data", "rmb_split")
train_dir = os.path.join(split_dir, "train")
valid_dir = os.path.join(split_dir, "valid")
# 设置均值和方差
norm_mean = [0.485, 0.456, 0.406]
norm_std = [0.229, 0.224, 0.225]
# 数据预处理
train_transform = transforms.Compose([
transforms.Resize((32, 32)), # 缩放
transforms.RandomCrop(32, padding=4), # 裁剪
transforms.ToTensor(), # 将图像转成张量数据
# 该函数作用对象需要是一个Tensor,所以在上一步通过transforms.ToTensor()生成Tensor
transforms.Normalize(norm_mean, norm_std),
])
valid_transform = transforms.Compose([
transforms.Resize((32, 32)),
transforms.ToTensor(),
transforms.Normalize(norm_mean, norm_std),
])
# 构建MyDataset实例
# Dataset必须是用户自己构建,构建时传入两个参数,
# data_dir:数据路径 即从哪读数据?
# transform:数据预处理
# 按住ctrl后用鼠标点击函数可以直接跳转到函数具体实现的位置
train_data = RMBDataset(data_dir=train_dir, transform=train_transform)
valid_data = RMBDataset(data_dir=valid_dir, transform=valid_transform)
# 构建DataLoder
# 数据迭代器,传入数据,数据大小等相关设置
# shuffle=True 表示每一个epoch中的样本都是乱序
train_loader = DataLoader(dataset=train_data, batch_size=BATCH_SIZE, shuffle=True)
valid_loader = DataLoader(dataset=valid_data, batch_size=BATCH_SIZE)
# ============================ step 2/5 模型 ============================
# 初始化卷积神经网络LeNet
net = LeNet(classes=2)
net.initialize_weights()
# ============================ step 3/5 损失函数 ============================
# 在PyTorch中采用torch.nn模块来定义网络的所有层,
# 比如卷积、降采样、损失层等等,这里采用交叉熵函数
# 分类任务通常采用交叉熵损失
criterion = nn.CrossEntropyLoss() # 选择损失函数
# ============================ step 4/5 优化器 ============================
# 优化器使用随机梯度下降
optimizer = optim.SGD(net.parameters(), lr=LR, momentum=0.9) # 选择优化器
# 定义学习率的变化策略,这里采用的是torch.optim.lr_scheduler模块的StepLR类,
# 表示每隔step_size个epoch就将学习率降为原来的gamma倍。
scheduler = torch.optim.lr_scheduler.StepLR(optimizer, step_size=10, gamma=0.1) # 设置学习率下降策略
# ============================ step 5/5 训练 ============================
train_curve = list()
valid_curve = list()
# 以epoch为周期,在每个epoch中会有多个iteration的训练,在每一个iteration中训练模型
for epoch in range(MAX_EPOCH):
loss_mean = 0.
correct = 0.
total = 0.
# 从DataLoader迭代器中不停地去获取一个Batchsize大小的数据
net.train()
for i, data in enumerate(train_loader): # 在每一个iteration中训练模型
# forward
inputs, labels = data # 每一次读取一个batchsize大小的数据
outputs = net(inputs) # 输入到模型中进行前向传播
# backward # 反向传播获取梯度
optimizer.zero_grad() # 将网络中的梯度清零
loss = criterion(outputs, labels) # 将输出的outputs和原来导入的labels作为loss函数的输入就可以得到损失
# 计算得到loss后就要回传损失
# 要注意的是这是在训练的时候才会有的操作,测试时候只有forward过程。
loss.backward()
# update weights
# 更新权重
# 回传损失过程中会计算梯度,
# 然后需要根据这些梯度更新参数,
# optimizer.step()就是用来更新参数的
optimizer.step()
# 统计分类情况
# 统计分类准确率
# 得到输出后(网络的全连接层的输出)
# 还希望能到到模型预测该样本属于哪个类别的信息,这里采用torch.max
# torch.max()的第一个输入是tensor格式,
# 所以用outputs.data而不是outputs作为输入
# 第二个参数1是代表dim的意思,也就是取每一行的最大值,
# 其实就是我们常见的取概率最大的那个index
_, predicted = torch.max(outputs.data, 1)
total += labels.size(0)
correct += (predicted == labels).squeeze().sum().numpy()
# 打印训练信息
loss_mean += loss.item()
train_curve.append(loss.item())
if (i+1) % log_interval == 0:
loss_mean = loss_mean / log_interval
print("Training:Epoch[{:0>3}/{:0>3}] Iteration[{:0>3}/{:0>3}] Loss: {:.4f} Acc:{:.2%}".format(
epoch, MAX_EPOCH, i+1, len(train_loader), loss_mean, correct / total))
loss_mean = 0.
scheduler.step() # 更新学习率
# validate the model
# 在每一个epoch中会进行验证集的测试,通过验证集来观察模型是否过拟合
if (epoch+1) % val_interval == 0:
correct_val = 0.
total_val = 0.
loss_val = 0.
net.eval()
with torch.no_grad():
for j, data in enumerate(valid_loader):
inputs, labels = data
outputs = net(inputs)
loss = criterion(outputs, labels)
_, predicted = torch.max(outputs.data, 1)
total_val += labels.size(0)
correct_val += (predicted == labels).squeeze().sum().numpy()
loss_val += loss.item()
valid_curve.append(loss_val/valid_loader.__len__())
print("Valid:t Epoch[{:0>3}/{:0>3}] Iteration[{:0>3}/{:0>3}] Loss: {:.4f} Acc:{:.2%}".format(
epoch, MAX_EPOCH, j+1, len(valid_loader), loss_val, correct_val / total_val))
train_x = range(len(train_curve))
train_y = train_curve
train_iters = len(train_loader)
valid_x = np.arange(1, len(valid_curve)+1) * train_iters*val_interval # 由于valid中记录的是epochloss,需要对记录点进行转换到iterations
valid_y = valid_curve
plt.plot(train_x, train_y, label='Train')
plt.plot(valid_x, valid_y, label='Valid')
plt.legend(loc='upper right')
plt.ylabel('loss value')
plt.xlabel('Iteration')
plt.show()
# ============================ inference ============================
BASE_DIR = os.path.dirname(os.path.abspath(__file__))
test_dir = os.path.join(BASE_DIR, "test_data")
test_data = RMBDataset(data_dir=test_dir, transform=valid_transform)
valid_loader = DataLoader(dataset=test_data, batch_size=1)
for i, data in enumerate(valid_loader):
# forward
inputs, labels = data
outputs = net(inputs)
_, predicted = torch.max(outputs.data, 1)
rmb = 1 if predicted.numpy()[0] == 0 else 100
print("模型获得{}元".format(rmb))
问题:
1 彻底解决Python(win)导包from import错误问题
一句话,关键是os.sys.path这个目录,这个目录有,就from import没问题,没有,就报错。解决办法就是千方百计加进去即可。
2.函数的参考文档,查看官网:
PyTorch