pytorch学习笔记(2)

一、pytorch主要模块

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

由于深度神经网络层数往往较多,同时会有一些用于实现特定功能的层(如卷积层、池化层、批正则化层、LSTM层等),因此深度神经网络往往需要“逐层”搭建,或者预先定义好可以实现特定功能的模块,再把这些模块组装起来。

但由于模型设定的灵活性,因此损失函数和优化器要能够保证反向传播能够在用户自行定义的模型结构上实现。

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

import os
import numpy as np
import torch
import torch.nn as nn
from torch.utils.data import Dataset, DataLoader
import torch.optim as optimizer

深度学习有统一设置的超参数

  • batch size
  • 初始学习率(初始)
  • 训练次数(max_epochs)
  • GPU配置
batch_size = 16  # 批次的大小
lr = 1e-4  # 优化器的学习率
max_epochs = 100
# 方案一:使用os.environ,这种情况如果使用GPU不需要设置
os.environ['CUDA_VISIBLE_DEVICES'] = '0,1'
# 方案二:使用“device”,后续对要使用GPU的变量用.to(device)即可
device = torch.device("cuda:1" if torch.cuda.is_available() else "cpu")

二、数据读入

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

使用自带数据集

pytorch数据读取的核心是torch.utils.data.DataLoader

import torchvision 
import torchvision.transforms as transforms
from torch.utils.data import DataLoader

'''
root:保存数据的目录(自定义)
train:是否作为训练集(train_set为True, test_set为False)
transform:数据进行怎样的处理,transforms.ToTensor()表示由 PIL -> tensor
download:是否进行下载,若没有数据集就会进行下载,若检测到有的话,直接使用(建议设置为True)
'''
test_set = torchvision.datasets.CIFAR10(root="path",train=False,transform=transforms.ToTensor(),download=True)


# num_workers:用多少个子进程用来读取数据,0代表只是用main process来读取数据
test_dataloader = DataLoader(dataset=test_set, batch_size=_int_, shuffle=True, num_workers=0, drop_last=False)


#  测试第一个数据
img, target = test_set[0]  # test_set就是一群图片和target的集合

print(img)
print(target)
print(test_set.classes)
print(type(test_set))

自定义数据类:需要继承pytorch的Dataset类

主要有三个函数:

  • __init__:向类中传入外部参数,同时定义样本集
  • __getitem__:逐个读取样本集合中的元素-->next()函数
  • __len__:返回数据集的样本数

例如:

import torch
from torchvision import datasets
train_data = datasets.ImageFolder(path, transform=data_transform)
val_data = datasets.ImageFolder(path, transform=data_transform)

pytorch自带的ImageFolder类的用于读取按一定结构存储的图片数据

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

class MyDataset(Dataset):
    def __init__(self, data_dir, info_csv, image_list, transform=None):
        """
    data_dir: path to image directory.
    info_csv: path to the csv file containing image indexes with corresponding labels
    image_list: path to the txt file contains image names to training/validation set
        """
        label_info = pd.read_csv(info_csv)
        image_file = open(image_list).readlines()
        self.data_dir = data_dir
        self.image_file = image_file
        self.label_info = label_info
        self.transform = transform

    def __getitem__(self, index):
        """
            index: the index of item Returns:image and its labels
        """
        image_name = self.image_file[index].strip('\n')
        raw_label = self.label_info.loc[self.label_info['Image_index'] == image_name]
        label = raw_label.iloc[:,0]
        image_name = os.path.join(self.data_dir, image_name)
        image = Image.open(image_name).convert('RGB')
        if self.transform is not None:
            image = self.transform(image)
        return image, label

    def __len__(self):
        return len(self.image_file)

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

from torch.utils.data import DataLoader

train_loader = torch.utils.data.DataLoader(train_data, batch_size=batch_size, num_workers=4, shuffle=True, drop_last=True)
# num_workers:有多少个进程用于读取数据
# drop_last:对于样本最后一部分没有达到批次数的样本,使其不再参与训练
val_loader = torch.utils.data.DataLoader(val_data, batch_size=batch_size, num_workers=4, shuffle=False)

import matplotlib.pyplot as plt
images, labels = next(iter(val_loader))
print(images.shape)
plt.imshow(images[0].transpose(1,2,0))
plt.show()

模型构建

Module 类是 nn 模块里提供的一个模型构造类,是所有神经⽹网络模块的基类。下面继承 Module 类构造多层感知机。定义的 MLP 类重载了 Module 类的 init 函数和 forward 函数。它们分别用于创建模型参数和定义前向计算。

import torch
from torch import nn

class MLP(nn.Module):
  # 声明带有模型参数的层,这里声明了两个全连接层
  def __init__(self, **kwargs):
    # 调用MLP父类Block的构造函数来进行必要的初始化。这样在构造实例时还可以指定其他函数
    super(MLP, self).__init__(**kwargs)
    self.hidden = nn.Linear(784, 256)
    self.act = nn.ReLU()
    self.output = nn.Linear(256,10)
    
   # 定义模型的前向计算,即如何根据输入x计算返回所需要的模型输出
  def forward(self, x):
    o = self.act(self.hidden(x))
    return self.output(o)  
if __name__ == "__main__":
    X = torch.rand(2,784)
    net = MLP()
    print(net)
    net(X)
MLP(
  (hidden): Linear(in_features=784, out_features=256, bias=True)
  (act): ReLU()
  (output): Linear(in_features=256, out_features=10, bias=True)
)

神经网路中常见的层

自定义——不含模型参数的层

构造一个 FirstLayer 类通过继承 Module 类自定义了一个输入减掉均值后输出的层,并将层的计算定义在了 forward 函数里。这个层里不含模型参数。

import torch
from torch import nn

class FirstLayer(nn.Module):
    def __init__(self, **kwargs):
        super(MyLayer, self).__init__(**kwargs)
    def forward(self, x):
        return x - x.mean()  
# 测试该层
layer = MyLayer()
layer(torch.tensor([1, 2, 3, 4, 5], dtype=torch.float))
tensor([-2., -1.,  0.,  1.,  2.])

自定义——含模型参数的层

Parameter 类其实是 Tensor 的子类,如果一 个 Tensor 是 Parameter ,那么它会⾃动被添加到模型的参数列表里。所以在⾃定义含模型参数的层时,我们应该将参数定义成 Parameter ,除了直接定义成 Parameter 类外,还可以使⽤ ParameterList 和 ParameterDict 分别定义参数的列表和字典。

class MyListDense1(nn.Module):
    def __init__(self):
        super(MyListDense, self).__init__()
        self.params = nn.ParameterList([nn.Parameter(torch.randn(4, 4)) for i in range(3)])
        self.params.append(nn.Parameter(torch.randn(4, 1))) # 新增

    def forward(self, x):
        for i in range(len(self.params)):
            x = torch.mm(x, self.params[i])
        return x

net = MyListDense1()
print(net)

class MyDictDense2(nn.Module):
    def __init__(self):
        super(MyDictDense, self).__init__()
        self.params = nn.ParameterDict({
                'linear1': nn.Parameter(torch.randn(4, 4)),
                'linear2': nn.Parameter(torch.randn(4, 1))
        })
        self.params.update({'linear3': nn.Parameter(torch.randn(4, 2))}) # 新增

    def forward(self, x, choice='linear1'):
        return torch.mm(x, self.params[choice])

net = MyDictDense2()
print(net)

二维卷积层

二维卷积层将输入和卷积核做互相关运算,并加上一个标量偏差来得到输出。模型参数包括了卷积核和标量偏差。在训练模型的时候,通常先对卷积核随机初始化,然后不断迭代卷积核和偏差。

import torch
from torch import nn

# 卷积运算(二维互相关)
def corr2d(X, K): 
    h, w = K.shape
    X, K = X.float(), K.float()
    Y = torch.zeros((X.shape[0] - h + 1, X.shape[1] - w + 1))
    for i in range(Y.shape[0]):
        for j in range(Y.shape[1]):
            Y[i, j] = (X[i: i + h, j: j + w] * K).sum()
    return Y

# 二维卷积层
class Conv2D(nn.Module):
    def __init__(self, kernel_size):
        super(Conv2D, self).__init__()
        self.weight = nn.Parameter(torch.randn(kernel_size))
        self.bias = nn.Parameter(torch.randn(1))

    def forward(self, x):
        return corr2d(x, self.weight) + self.bias

池化层

池化层每次对输入数据的一个固定形状窗口(⼜称池化窗口)中的元素计算输出。不同于卷积层里计算输⼊和核的互相关性,池化层直接计算池化窗口内元素的最大值或者平均值。该运算也 分别叫做最大池化或平均池化。在二维最⼤池化中,池化窗口从输入数组的最左上方开始,按从左往右、从上往下的顺序,依次在输⼊数组上滑动。当池化窗口滑动到某⼀位置时,窗口中的输入子数组的最大值即输出数组中相应位置的元素。

import torch
from torch import nn

def pool2d(X, pool_size, mode='max'):
    p_h, p_w = pool_size
    Y = torch.zeros((X.shape[0] - p_h + 1, X.shape[1] - p_w + 1))
    for i in range(Y.shape[0]):
        for j in range(Y.shape[1]):
            if mode == 'max':
                Y[i, j] = X[i: i + p_h, j: j + p_w].max()
            elif mode == 'avg':
                Y[i, j] = X[i: i + p_h, j: j + p_w].mean()
    return Y

X = torch.tensor([[0, 1, 2], [3, 4, 5], [6, 7, 8]], dtype=torch.float)

pool2d(X, (2, 2))
pool2d(X, (2, 2), 'avg')

out:
tensor([[4., 5.],
	    [7., 8.]])
tensor([[4., 5.],
	    [7., 8.]])

模型示例

LeNet

简单的前馈神经网络 (feed-forward network)(LeNet)。它接受一个输入,然后将它送入下一层,一层接一层的传递,最后给出输出。

  1. 定义包含一些可学习参数(或者叫权重)的神经网络

  2. 在输入数据集上迭代

  3. 通过网络处理输入

  4. 计算 loss (输出和正确答案的距离)

  5. 将梯度反向传播给网络的参数

  6. 更新网络的权重,一般使用一个简单的规则:weight = weight - learning_rate * gradient

我们只需要定义 forward 函数,backward函数会在使用autograd时自动定义,backward函数用来计算导数。我们可以在 forward 函数中使用任何针对张量的操作和计算。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值