PyTorch学习笔记02

主要组成模块

​ 在完成一项机器学习任务时的步骤,首先需要对数据进行预处理,其中重要的步骤包括数据格式的统一和必要的数据变换,同时划分训练集和测试集。接下来选择模型,并设定损失函数和优化方法,以及对应的超参数(当然可以使用sklearn这样的机器学习库中模型自带的损失函数和优化器)。最后用模型去拟合训练集数据,并在验证集/测试集上计算模型表现

​ 深度学习和机器学习在流程上类似,但在代码实现上有较大的差异。首先,由于深度学习所需的样本量很大,一次加载全部数据运行可能会超出内存容量而无法实现;同时还有批(batch)训练等提高模型表现的策略,需要每次训练读取固定数量的样本送入模型中训练,因此深度学习在数据加载上需要有专门的设计。

​ 在模型实现上,深度学习和机器学习也有很大差异。由于深度神经网络层数往往较多,同时会有一些用于实现特定功能的层(如卷积层、池化层、批正则化层、LSTM层等),因此深度神经网络往往需要“逐层”搭建,或者预先定义好可以实现特定功能的模块,再把这些模块组装起来。这种“定制化”的模型构建方式能够充分保证模型的灵活性,也对代码实现提出了新的要求。

​ 接下来是损失函数和优化器的设定。这部分和经典机器学习的实现是类似的。但由于模型设定的灵活性,因此损失函数和优化器要能够保证反向传播能够在用户自行定义的模型结构上实现

​ 上述步骤完成后就可以开始训练了。我们前面介绍了GPU的概念和GPU用于并行计算加速的功能,不过程序默认是在CPU上运行的,因此在代码实现中,需要把模型和数据“放到”GPU上去做运算,同时还需要保证损失函数和优化器能够在GPU上工作。如果使用多张GPU进行训练,还需要考虑模型和数据分配、整合的问题。此外,后续计算一些指标还需要把数据“放回”CPU。这里涉及到了一系列有关于GPU的配置和操作

深度学习中训练和验证过程最大的特点在于读入数据是按批的,每次读入一个批次的数据,放入GPU中训练,然后将损失函数反向传播回网络最前面的层,同时使用优化器调整网络参数。这里会涉及到各个模块配合的问题。训练/验证后还需要根据设定好的指标计算模型表现。

1. 基本配置

1.1 包

import os
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import torch
import torch.nn as nn
import torch.optim as optim
from torch.utils.data import Dataset, DataLoader
from torchvision import transforms

1.2 超参数

有如下几个超参数可以设定,方便后续更改

# 配置其他超参数,如batch_size, num_workers, learning_rate以及总的epochs
batch_size = 256 # 批的大小
num_workers = 4  # 线程数
lr = 1e-4        # 学习率
epochs = 20      # 训练轮数
image_size = 28  # 图片尺寸

1.3 GPU的设置

# 方案一:使用os.environ,这种情况如果使用GPU不需要设置
os.environ['CUDA_VISIBLE_DEVICES'] = '0'

# 方案二:使用“device”,后续对要使用GPU的变量用.to(device)即可
device = torch.device("cuda:1" if torch.cuda.is_available() else "cpu")

2. 数据读入

​ PyTorch数据读入是通过Dataset+DataLoader的方式完成的,Dataset定义好数据的格式和数据变换形式,DataLoaderiterative的方式不断读入批次数据。

第一种数据读入方式仅适用于常见的数据集,如MINIST、CIFAR10等,P有Torch官方提供了数据下载,这种方式往往适用于快速测试方法(比如测试某个idea在MINIST数据集上有效)。

第二种数据读入方式需要自己搭建Dataset,这对于PyTorch应用于自己的工作十分重要。

同时,还需要对数据进行必要的变换,比如说需要将图片统一大小,以便后续能够输入网络训练;需要将数据格式转换为Tensor类,等等

这些变换可以很方便的借助torchvision包完成,这是PyTorch官方用于图像处理的工具库,上面提到的使用内置数据集的方式也要使用到。PyTorch的一大方便之处在于它是一套“生态”,有着官方和第三方各个领域的支持。

2.1数据转换

其中“data_transform”可以对图像进行一定的变换,如翻转、裁剪等操作,可自己定义。

from torchvision import transforms

image_size = 28
data_transform = transforms.Compose([
    transforms.ToPILImage(),  # 这一步决定于后续的数据读取方式,如果使用内置数据集则不需要
    transforms.Resize(image_size),
    transforms.ToTensor()
])

2.2 Dataset

  • 针对官方数据集

    from torchvision import datasets
    
    train_data = datasets.FashionMNIST(root='./',
                                       train=True,    			 # 训练集
                                       download=True, 			 # 需要下载
                                       transform=data_transform)  # 数据转换
    test_data = datasets.FashionMNIST(root='./',
                                      train=False,				 # 测试集
                                      download=True,
                                      transform=data_transform)
    
  • 自己的数据集

    我们可以定义自己的Dataset类来实现灵活的数据读取,定义的类需要继承PyTorch自身的Dataset类。主要包含三个函数:

    • __init__: 用于向类中传入外部参数,同时定义样本集
    • __getitem__: 用于逐个读取样本集合中的元素,可以进行一定的变换,并将返回训练/验证所需的数据
    • __len__: 用于返回数据集的样本数

​ 其中图片存放在一个文件夹,另外有一个csv文件给出了图片名称对应的标签。这种情况下需要自己来定义Dataset类:

class FMDataset(Dataset):
    def __init__(self, df, transform=None):
        """
        df:数据
        transform:施加的变化是什么
        """
        self.df = df
        self.transform = transform
        # 提取所有行的从第一列开始往后所有的元素,转换为uint8(图像专属类型)
        self.images = df.iloc[:, 1:].values.astype(np.uint8)
        # 提取所有行的第一列
        self.labels = df.iloc[:, 0].values

    def __len__(self):
        """
        数据集的长度
        """
        return len(self.images)

    def __getitem__(self, idx):
        """
        根据index查找某一条示例
        """
        # 图像信息(这里强行加了一个通道)
        image = self.images[idx].reshape(28, 28, 1)
        # 标签
        label = int(self.labels[idx])
        if self.transform is not None:
            # 如果transform不是None,就对image进行变换
            image = self.transform(image)
        else:
            # 如果不进行transform,就将所有的pixel值除以255方便模型处理
            image = torch.tensor(image / 255, dtype=torch.float)
        label = torch.tensor(label, dtype=torch.long)
        return image, label
# 读取数据集
train_df = pd.read_csv("./fashion-mnist_train.csv")
test_df = pd.read_csv("./fashion-mnist_test.csv")
# 实例化
train_data = FMDataset(train_df, data_transform)
test_data = FMDataset(test_df, data_transform)

2.3 DataLoader

构建好Dataset后,就可以使用DataLoader来按批次读入数据了

train_loader = DataLoader(
    train_data,  # 从哪个Dataset内load data
    batch_size=batch_size,  # 每一批加载多少数据,每次输入多少条数据
    shuffle=True,  # 是否打乱读取
    num_workers=num_workers,  # 使用多少线程读取
    drop_last=True)  # 是否丢掉最后一批数据(在训练的时候最后一批数据往往不到一个batch)

test_loader = DataLoader(test_data,
                         batch_size=batch_size,
                         shuffle=True,
                         num_workers=num_workers)

PyTorch中的DataLoader的读取可以使用next和iter来完成

image, label = next(iter(train_loader))
print(image.shape, label.shape)
plt.imshow(image[0][0], camp='gray')

3. 模型构建

PyTorch中神经网络构造一般是基于 Module 类的模型来完成的,它让模型构造更加灵活。

3.1神经网络的构建

搭建CNN卷积神经网络

class Net(nn.Module):
    def __init__(self):
        super(Net, self).__init__()
        self.conv = nn.Sequential(      # 卷积操作——序贯模型
            nn.Conv2d(1, 32, 5),        # 2维卷积
            nn.ReLU(),                  # 激活函数
            nn.MaxPool2d(2, stride=2),  # 池化层
            nn.Dropout(0.3),
            nn.Conv2d(32, 64, 5),
            nn.ReLU(),
            nn.MaxPool2d(2, stride=2),
            nn.Dropout(0.3)
        )
        self.fc = nn.Sequential(       # 全连接层
            nn.Linear(64*4*4, 512),    # 从64*4*4到512
            nn.ReLU(),                 # 激活函数
            nn.Linear(512, 10)         # 从512到10
        )

    def forward(self, x):
        x = self.conv(x)
        x = x.view(-1, 64*4*4)         # 拉平便于后续的全连接层操作
        x = self.fc(x)
        # x = nn.functional.normalize(x)
        return x
    
# 模型实例化
model = Net()
model = model.cuda()   # GPU操作(直接.cuda())

3.2 损失函数

使用torch.nn模块自带的CrossEntropy损失
PyTorch会自动将整型的label转换为one-hot型,用于计算CE loss
这里需要确定label是从0开始的,同时模型不用加softmax层(使用logstic计算)

criterion = nn.CrossEntropyLoss()
# criterion = nn.CrossEntropyLoss(weight=[1,1,1,1,3,1,1,1,1,1])对于4的惩罚加大

3.3 优化器

使用Adam优化器

optimizer = optim.Adam(model.parameters(), lr=0.001)

4. 训练和评估

各自封装成函数,方便后续调用
关注两者的区别

  • 模型状态设置

  • 是否需要出示话优化器

  • 是否需要将loss传回网络

  • 是否需要每步更新optimaizer

  • 模型状态设置

    • model.train()、model.eval()
  • 训练流程:读取、转换、梯度清零、输入、计算损失、反向传播、参数更新

  • 验证流程:读取、转换、输入、计算损失、计算指标

4.1 训练

def train(epoch):
    model.train()  # 训练模式
    train_loss = 0  # loss初始化为0
    for data, label in train_loader:  # 循环所有data_loader
        data, label = data.cuda(), label.cuda()  # 数据放在GPU上
        optimizer.zero_grad  # 将梯度清零,防止梯度回传时累加
        output = model(data)  # 前向传播
        loss = criterion(output, label)  # 损失函数计算
        loss.backward()  # 反向传播
        optimizer.step()  # 优化器更新权重
        train_loss = train_loss / len(train_loader.dataset)
        print(f'Epoch: {epoch} \tTraining Loss: {train_loss}')

4.2评估

def val(eopch):
    model.eval()  # 验证模式
    val_loss = 0
    gt_labels = []  # 正确打标记的
    pred_labels = []  # 预测的结果
    with torch.no_grad():  # 不做梯度的计算
        data, label = data.cuda(), label.cuda()
        output = model(data)
        preds = torch.argmax(output, 1)  # 在10维中求最大的,就是预测结果
        gt_labels.append(label.cpu().data.numpy())
        pred_labels.append(preds.cpu().data.numpy())
        loss = criterion(output, label)  #损失不需要回传了
        val_loss += loss.item() * data * size(0)
    val_loss = val_loss / len(test_loader.dataset)
    gt_labels, pred_labels = np.concatenate(gt_labels), np.concatenate(
        pred_labels)
    acc = np.sum(gt_labels == pred_labels) / len(pred_labels)
    print(
        f'Epoch: {epoch} \tValidation Loss: {train_loss:6f}, Accuracy: {acc:6f}'
    )
for epoch in range(1, epochs + 1):
    train(epoch)
    val(epoch)

5.完整代码

import os
import numpy as np
import pandas as pd
import torch
import torch.nn as nn
import torch.optim as optim
from torch.utils.data import Dataset, DataLoader
from torchvision import transforms

# 使用os.environ(以后的运算全在GPU进行)
os.environ['CUDA_VISIBLE_DEVICES'] = '0'

# 配置其他超参数,如batch_size, num_workers, learning_rate以及总的epochs
batch_size = 256
num_workers = 4
lr = 1e-4
epochs = 20
image_size = 28

# 设置数据变换
data_transform = transforms.Compose([
    transforms.ToPILImage(),  # 这一步决定于后续的数据读取方式,如果使用内置数据集则不需要
    transforms.Resize(image_size),
    transforms.ToTensor()
])


# 数据读入:读入csv格式的文件,自行构建Dataset类
class FMDataset(Dataset):
    def __init__(self, df, transform=None):
        """
        继承官方的Dataset
        :param df: 数据
        :param transform: 数据变换方式
        """
        self.df = df
        self.transform = transform
        # 提取所有行的从第一列开始往后所有的元素,转换为uint8(图像专属类型)
        self.images = df.iloc[:, 1:].values.astype(np.uint8)
        # 提取所有行的第一列
        self.labels = df.iloc[:, 0].values

    def __len__(self):
        """
        数据集的长度
        """
        return len(self.images)

    def __getitem__(self, idx):
        """
        根据index查找某一条示例
        """
        # 图像信息(这里强行加了一个通道)
        image = self.images[idx].reshape(28, 28, 1)
        # 标签
        label = int(self.labels[idx])
        if self.transform is not None:
            # 如果transform不是None,就对image进行变换
            image = self.transform(image)
        else:
            # 如果不进行transform,就将所有的pixel值除以255方便模型处理
            image = torch.tensor(image / 255, dtype=torch.float)
        label = torch.tensor(label, dtype=torch.long)
        return image, label


# 数据集读取
train_df = pd.read_csv("./fashion-mnist_train.csv")
test_df = pd.read_csv("./fashion-mnist_test.csv")
# 实例化
train_data = FMDataset(train_df, data_transform)
test_data = FMDataset(test_df, data_transform)

# 定义DataLoader的类,以便训练和测试时数据的加载
train_loader = DataLoader(
    train_data,  # 从哪个Dataset内load data
    batch_size=batch_size,  # 每一批加载多少数据,每次输入多少条数据
    shuffle=True,  # 是否打乱读取
    num_workers=num_workers,  # 使用多少线程读取
    drop_last=True)  # 是否丢掉最后一批数据(在训练的时候最后一批数据往往不到一个batch)

test_loader = DataLoader(
    test_data,
    batch_size=batch_size,
    shuffle=True,
    num_workers=num_workers)


# CNN网络搭建
class Net(nn.Module):
    def __init__(self):
        super(Net, self).__init__()
        self.conv = nn.Sequential(  # 卷积操作——序贯模型
            nn.Conv2d(1, 32, 5),  # 2维卷积
            nn.ReLU(),  # 激活函数
            nn.MaxPool2d(2, stride=2),  # 池化层
            nn.Dropout(0.3),
            nn.Conv2d(32, 64, 5),
            nn.ReLU(),
            nn.MaxPool2d(2, stride=2),
            nn.Dropout(0.3)
        )
        self.fc = nn.Sequential(  # 全连接层
            nn.Linear(64 * 4 * 4, 512),  # 从64*4*4到512
            nn.ReLU(),  # 激活函数
            nn.Linear(512, 10)  # 从512到10
        )

    def forward(self, x):
        x = self.conv(x)
        x = x.view(-1, 64 * 4 * 4)  # 拉平便于后续的全连接层操作
        x = self.fc(x)
        # x = nn.functional.normalize(x)
        return x


# 模型训练
def train(epoch):
    model.train()  # 训练模式
    train_loss = 0  # loss初始化为0
    for data, label in train_loader:  # 循环所有data_loader
        data, label = data.cuda(), label.cuda()  # 数据放在GPU上
        optimizer.zero_grad()  # 将梯度清零,防止梯度回传时累加
        output = model(data)  # 前向传播
        loss = criterion(output, label)  # 损失函数计算
        loss.backward()  # 反向传播
        optimizer.step()  # 优化器更新权重
        train_loss += loss.item() * data.size(0)
    train_loss + train_loss / len(train_loader.dataset)
    print(f'Epoch: {epoch} \tTraining Loss: {train_loss:.6f}')


# 模型评估
def val(eopch):
    model.eval()  # 验证模式
    val_loss = 0
    gt_labels = []  # 正确打标记的
    pred_labels = []  # 预测的结果
    with torch.no_grad():  # 不做梯度的计算
        for data, label in test_loader:
            data, label = data.cuda(), label.cuda()
            output = model(data)
            preds = torch.argmax(output, 1)  # 在10维中求最大的,就是预测结果
            gt_labels.append(label.cpu().data.numpy())
            pred_labels.append(preds.cpu().data.numpy())
            loss = criterion(output, label)  # 损失不需要回传了
            val_loss += loss.item() * data.size(0)
    val_loss = val_loss / len(test_loader.dataset)
    gt_labels, pred_labels = np.concatenate(gt_labels), np.concatenate(pred_labels)
    acc = np.sum(gt_labels == pred_labels) / len(pred_labels)
    print(
        f'Epoch: {epoch} \tValidation Loss: {val_loss:6f}, Accuracy: {acc:6f}'
    )


if __name__ == '__main__':
    # 模型实例化
    model = Net()
    model = model.cuda()  # GPU操作(直接.cuda())

    criterion = nn.CrossEntropyLoss()

    optimizer = optim.Adam(model.parameters(), lr=0.001)

    for epoch in range(1, epochs + 1):
        train(epoch)
        val(epoch)
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值