文章目录

  • pytorch基础(二)
  • 激活函数
  • SoftMax
  • DataLoader
  • 独热编码(使用scatter插值)
  • Dropout
  • 权重衰减
  • 优化器
  • SGD
  • Momentum
  • NAG
  • Adam
  • 保存模型与加载模型
  • 保存模型
  • 保存整个模型
  • 只保存模型的参数
  • 保存的模型文件中带超参数
  • 保存多个模型到文件
  • 使用fine-tuning预训练模型
  • 加载模型
  • 加载整个模型
  • 加载模型参数
  • GPU训练
  • 获取gpu对象
  • 判断是否有gpu
  • 将tensor放到gpu上
  • 将tensor放到GPU上
  • 查看tensor放在哪个设备上
  • 将model放到gpu上
  • 将model放到GPU上
  • 查看model在GPU还是cpu上
  • 将DataSet、DataLoader放到GPU上


pytorch基础(二)

激活函数

pytorch的激活函数在nn模块中!

import torch
from torch import nn
tanh = nn.Tanh()

x = torch.tensor([1, 2, 3, 4], dtype=float)
tanh = nn.Tanh()
y = tanh(x)
print(y)
结果:
tensor([0.7616, 0.9640, 0.9951, 0.9993], dtype=torch.float64)

ReLu、Sigmoid等都在nn中,直接调用即可获得其对应的函数。

SoftMax

import torch
from torch import nn

softmax = nn.Softmax(dim=1)

例如:softmax(x),x是一个shape=(64, 10)的一个数据。我们是对第一维度,就是10所在的那个维度进行softmax,也就是对10个数据作为一组进行softmax操作。

[[x0,x1,x2,x3,x4,x5,x6,x7,x8,x9],
 [x0,x1,x2,x3,x4,x5,x6,x7,x8,x9],
 ...x61
 [x0,x1,x2,x3,x4,x5,x6,x7,x8,x9]]

这里的64为批量大小!

再例如数据批量大小为1,也就是只有一个样本的情况是这样的:

import torch
from torch import nn

x = torch.tensor([1, 2, 3, 4], dtype=float)
# 默认dim就是0,这里写不写都可以
sofrmax = nn.Softmax(dim=0)
y = softmax(x)

DataLoader

DataLoader的dataset可以是list、tensor(只要是可迭代类型即可),for遍历DataLoader可以得到其中的每一个元素!

from torch.utils.data import DataLoader

x = torch.tensor([1, 2, 3, 4, 5, 6])
# 将y根据x编程独热向量,tolist是为了将y整个可以变成tensor类型(因为如果list中元素为tensor是变不成tensor类型的)
y = [torch.zeros((6,)).tolist() for i in x]
y = torch.tensor(y)
# 将x,y放到一个dataset中,以便转换成DataLoader后可以方便的取出数据
dataset = []
for i in range(len(x)):
    # 转成独热向量
    y[i][x[i]-1] = 1.0
    # 将x,y对用元素变成元组,放到dataset中[(x0,y0),(x1,y1)...]
    dataset.append((x[i], y[i]))
print(y)
print(dataset)
print()
    
batch_size = 2
train_loader = DataLoader(dataset=dataset,
                         batch_size=batch_size,
                         shuffle=True)
for x, y in train_loader:
    print("x:", x)
    print("y:", y)
结果:
tensor([[1., 0., 0., 0., 0., 0.],
        [0., 1., 0., 0., 0., 0.],
        [0., 0., 1., 0., 0., 0.],
        [0., 0., 0., 1., 0., 0.],
        [0., 0., 0., 0., 1., 0.],
        [0., 0., 0., 0., 0., 1.]])
[(tensor(1), tensor([1., 0., 0., 0., 0., 0.])), (tensor(2), tensor([0., 1., 0., 0., 0., 0.])), (tensor(3), tensor([0., 0., 1., 0., 0., 0.])), (tensor(4), tensor([0., 0., 0., 1., 0., 0.])), (tensor(5), tensor([0., 0., 0., 0., 1., 0.])), (tensor(6), tensor([0., 0., 0., 0., 0., 1.]))]

x: tensor([4, 2])
y: tensor([[0., 0., 0., 1., 0., 0.],
        [0., 1., 0., 0., 0., 0.]])
x: tensor([5, 1])
y: tensor([[0., 0., 0., 0., 1., 0.],
        [1., 0., 0., 0., 0., 0.]])
x: tensor([3, 6])
y: tensor([[0., 0., 1., 0., 0., 0.],
        [0., 0., 0., 0., 0., 1.]])

独热编码(使用scatter插值)

除了上述自己写独热编码外,还可以使用tensor提供的scatter方法进行独热编码!

# 这是一个batch为6,共有6类的一个分类任务
label = torch.tensor([1, 2, 3, 4, 5, 6])
# 变成二维标签,[[1], [2], ..., [6]]
label = label.reshape(-1, 1)

# tensor.scatter(dim, index, src)
# dim: 对哪个维度进行插入
# index: 对哪个位置插值
# src: 插得值是啥
one_hot = torch.zeros(label.shape[0], 6).scatter(1, label-1, 1)
# zeros得到的是一个shape=(6, 6)的一个全0 tensor
# 对这个tensor进行插值,对1维、每行插得位置分别对应label-1中的每个元素,插得值为1
结果:
tensor([[1., 0., 0., 0., 0., 0.],
        [0., 1., 0., 0., 0., 0.],
        [0., 0., 1., 0., 0., 0.],
        [0., 0., 0., 1., 0., 0.],
        [0., 0., 0., 0., 1., 0.],
        [0., 0., 0., 0., 0., 1.]])

如果不懂我们使用一个样本进行:

label = torch.tensor([1])

one_hot = torch.zeros(6).scatter(0, label-1, 1)
结果:
tensor([1., 0., 0., 0., 0., 0.])

Dropout

import torch
from torch import nn

class Model(nn.Module):
    def __init__(self):
        super(Model, self).__init__()
        # 隐藏层有500个节点,接收输入为784大小的数据
        self.hidden = nn.Linear(784, 500)
        # 将输入的数据50%变为0,也就变相的让向它提供数据的那一层丢弃50%的节点。
        self.drop = nn.Dropout(p=0.5)
        # 10个分类
        self.out = nn.Linear(500, 10)
        
    def forward(self, x):
        x = self.hidden(x)
        x = self.drop(x)
        y_hat = self.out(x)
        return y_hat

权重衰减

权重衰减放在训练器中(而不用放在损失函数中)。因为它在反向传播的时候仍使用旧损失函数,在梯度下降更新参数(step)的时候在更新方法中添加了一个正则化项倒数,这样直接带入参数就可以实现权重衰减了!

import torch
from torch import nn, optim
optimizer = optim.SGD(model.parameters(), lr, weight_decay=0.1)
  • weight_decay 是正则化项的参数。(决定正则化项的重要程度,在模型中是超参数)

优化器

所有优化器的本质是梯度下降法!

SGD

SGD是基础算法。

  • 缺点:在鞍点时不能下降(最差情况下,现实中基本不可能遇到)

torch 编写自定义激活函数_加载

Momentum

使梯度下降具有惯性。

  • 优点:收敛快
  • 缺点:训练次数少的话,方向有可能不太正确(因为收敛快,所以寻找方向的时候不容易刹车),但训练次数多的话会调整回正确方向

NAG

是Momentum的一种改进算法。

  • 优点:比Momentum能更快的找回正确方向
  • 缺点:训练次数少的话,方向仍不太正确

Adam

随机梯度下降保持单一的学习率(即 alpha)更新所有的权重,学习率在训练过程中并不会改变。而 Adam 通过计算梯度的一阶矩估计和二阶矩估计而为不同的参数设计独立的自适应性学习率 Adam 算法 (也就是说Adam优化算法在训练过程中会最优化lr学习率)

  • 优点:比SGD好、快,比其他优化算法更稳定(不知道选哪个就用Adam)
import torch
from torch import nn, optim
optimizer = optim.Adam(model.parameters(), lr, weight_decay=0.1)

使用Adam优化算法,lr可以适当的调小一些。

法更稳定(不知道选哪个就用Adam)

import torch
from torch import nn, optim
optimizer = optim.Adam(model.parameters(), lr, weight_decay=0.1)

使用Adam优化算法,lr可以适当的调小一些。

保存模型与加载模型

保存模型

使用torch.save()进行保存

保存模型就是将模型序列化到磁盘上。pytorch使用的是python的pickle程序进行序列化的。

在pytorch中,模型(models)、张量(tensor)、文件(dictionaries)都可以序列化到磁盘上!

函数原型:torch.save(obj, f, pickle_model=<module '...'>, pickle_protocol=2)

参数

描述

obj

保持对象

f

保存的文件名字符串(或类文件对象)

pickle_module

用于 picking 元数据和对象的模块

pickle_protocol

指定pickle protocal可以覆盖默认参数

保存模型的格式:.pt\.pth\.pkl。三种都可以,格式上没有区别只是后缀不同罢了

保存整个模型

将整个模型的结构参数都保存下来。

在加载整个模型的时候就不需要进行模型的重构了,直接一个变量加载为一个模型。

# 用 pt pth pkl ckpt都可以
torch.save(model, "model.pt")
只保存模型的参数

只保存模型的参数在加载的时候需要将模型先实例化(获取结构)然后再加载参数。

这样需要有模型的类才行(而保存整个模型的形式不需要模型的类)

# 用 pt pth pkl ckpt都可以
torch.save(model.state_dict(), "model_params.pt")
保存的模型文件中带超参数
torch.save({
    'epoch': epoch,
    'model_state_dict': model.state_dict(),
    'optimizer_state_dict': optimizer.state_dict(),
    'loss': loss
}, "model.pt")

# 加载
model = MyModel()
optimizer = optim.SGD(*args, **kwargs)

checkpoint = torch.load("model.pt")

model.load_state_dict(checkpoint["model_state_dict"])
optimizer.load_state_dict(checkpoint["optimizer_state_dict"])
epoch = checkpoint["epoch"]
loss = checkpoint["loss"]

# 或model.train()
model.eval()
保存多个模型到文件
torch.save({
    "modelA_state_dict": modelA.state_dict(),
    "modelB_state_dict": modelB.state_dict()
}, "models_params.pt")

# 加载
checkpoint = torch.load("models_params.pt")
modelA = MyModelA()
modelB = MyModelB()
modelA.load_state_dict(checkpoint["modelA_state_dict"])
modelB.load_state_dict(checkpoint["modelB_state_dict"])

modelA.train()
modelB.train()
使用fine-tuning预训练模型
# 保存预训练模型A
torch.save(model.state_dict(), "model_param.pt")

# 加载预训练模型A到模型B中
model.load_state_dict("model_param.pt", strict=False)

这部分在迁移学习中很常用

在加载部分模型参数进行预训练的时候,很可能会碰到键不匹配的情况(模型权重都是按键值对的形式保存并加载回来的)。因此,无论是缺少键还是多出键的情况,都可以通过在load_state_dict()函数中设定strict参数为False来忽略不匹配的键。

如果想将某一层的参数加载到其他层,但是有些键不匹配,那么修改state_dict中参数的key可以解决这个问题。

加载模型

pytorch中加载模型使用python的解压工具(unpickling)来反序列化磁盘上的模型到内存中。

加载整个模型

函数原型:torch.load(f, map_location=None, puckle_model=<module 'pickle' from '...'>)

参数

描述

f

保存文件名的字符串(或类文件对象)

map_location

一个函数或字典规定如何映射存储设备

puckle_module

用于unpickling元数据和对象的模块(必须序列化文件时的pickle_module)

# 用 pt pth pkl 都可以
# 加载模型到cpu上
model = torch.load("model.pt")

# 加载模型到cpu上
model = torch.load("model.pt", map_location=torch.device('cpu'))

# 使用方法的形式,加载模型到cpu上
model = torch.load("model.pt", map_location=lambda storage, loc: storage)

# 加载模型到gpu1上
model = torch.load("model.pt", map_location=lambda storage, loc: storage.cuda(1))

# 将模型从gpu1映射到gpu0上
model = torch.load("model.pt", map_location={'cuda:1': 'cuda:0'})


# 注意一定要使用model.eval()来固定dropout和归一化层,否则每次推理会生成不同的结果
# 或model.train()
model.eval()
加载模型参数

函数原型:torch.nn.Module.load_state_dict(state_dict, strict=True)

参数

描述

state_dict

保存模型的字典

strict

state_dict中的key是否和model.state_dict()返回的key一致

model = MyModel()
# 模型将加载的权重复制到模型的权重中去
model.load_state_dict(torch.load("model_params.pt"))


# 注意一定要使用model.eval()来固定dropout和归一化层,否则每次推理会生成不同的结果
# 或model.train()
model.eval()

注意一定要使用model.eval()来固定dropout和归一化层,否则每次推理会生成不同的结果!(或使用model.train()

GPU训练

获取gpu对象

import torch
# 冒号后面指定几号gpu;不写冒号以及后面的数字则默认0号gpu
device = torch.device("cuda:0")
# 指定cpu
device = torch.device("cpu")
import torch
# 冒号后面指定几号gpu;不写冒号以及后面的数字则默认0号gpu
device = torch.device("cuda:0")
# 指定cpu
device = torch.device("cpu")

判断是否有gpu

torch.cuda.is_available()
  • 有则返回True
  • 没有则返回False

根据设备是否有GPU选择是用GPU还是CPU:

device = torch.device("cuda" if torch.cuda.is_available() else "cpu")

将tensor放到gpu上

将tensor放到GPU上
# 获取gpu后,在创建tensro的时候就放到gpu上
tensor = torch.tensor([2,3,4], device = device)
# 获取GPU后,将已经创建的tensor放到GPU上
device = torch.device("cuda")
tensor = torch.tensor([1,2,3]).to(device)
# 在没有获取gpu的情况下,放到一个指定的gpu上(n为gpu号,默认为0)
tensor = torch.tensor([3,4,5]).cuda(n)
查看tensor放在哪个设备上
print(tensor.device)

将model放到gpu上

将model放到GPU上
model.to(device)
model.cuda(n)
查看model在GPU还是cpu上

注意!model对象并没有device属性,我们在查看的时候查看它的参数在哪个设备上即可。

model = BusinessIndexModel(config)
print(model.parameters)
print(next(model.parameters()).device)
<bound method Module.parameters of BusinessIndexModel(
  (linear1): Linear(in_features=6, out_features=100, bias=True)
  (relu1): ReLU()
  (linear2): Linear(in_features=100, out_features=50, bias=True)
  (relu2): ReLU()
  (linear3): Linear(in_features=50, out_features=10, bias=True)
  (relu3): ReLU()
  (linear4): Linear(in_features=10, out_features=1, bias=True)
)>
cpu

将DataSet、DataLoader放到GPU上

class BusinessDataSet(Dataset):
    def __init__(self, x, y):
        self.x_data = x
        self.y_data = y
        
    def __getitem__(self, index):
        data = torch.tensor(self.x_data[index], dtype=torch.float32, device=device)
        label = torch.tensor(self.y_data[index], dtype=torch.float32, device=device)
        return data, label
    
    def __len__(self):
        return len(self.x_data)
    

business_dataset = BusinessDataSet(train_features, train_labels)
business_dataloader = DataLoader(business_dataset, business_model_config.batch_size,shuffle=True)

for x, y in business_dataloader:
    print(x.device)
    break
  1. DataSet的getitem方法会返回切片后的新tensor,所以必须在指定新tensor的设备
  2. DataLoader只是一个分发器,它并没有数据,所拥有的数据都在DataSet中。它调用的就是DataSet的getitem方法获取每个batch,所以只要每个item都在gpu上即可。所以就必须在DataSet中的getitem中指定设备。
  3. DataLoader通过 __ len __ / batch_size 来知道有多少个batch
x = torch.tensor([1,2,3,4,5,6,7,8])

class XDataSet(dataset.Dataset):
    def __init__(self, data):
        self.data = data
        
    def __getitem__(self, index):
        return self.data[index]
    
    def __len__(self):
        return 6
    
data = XDataSet(x)
loader = dataloader.DataLoader(data, batch_size=2)
for i, data in enumerate(loader):
    print("i={}    data={}".format(i, data))
i=0    data=tensor([1, 2])
i=1    data=tensor([3, 4])
i=2    data=tensor([5, 6])
x = torch.tensor([1,2,3,4,5,6,7,8])

class XDataSet(dataset.Dataset):
    def __init__(self, data):
        self.data = data
        
    def __getitem__(self, index):
        return self.data[index]
    
    def __len__(self):
        return 4
    
data = XDataSet(x)
loader = dataloader.DataLoader(data, batch_size=2)
for i, data in enumerate(loader):
    print("i={}    data={}".format(i, data))
i=0    data=tensor([1, 2])
i=1    data=tensor([3, 4])

所以,在做自然语言处理的时候shape=(batch_size, time_stemp, word_dim),所以len=(所有词的长度) / time_stemp

以下代码为自然语言处理的dataset

all_data = []
for sentence in txt_words:
    for word in sentence:
        all_data.append(word_to_index[word])

class TxtRNNDataSet(dataset.Dataset):
    def __init__(self, words):
        self.data = words
    
    def __getitem__(self, index):
        data = self.data[index*TxtRNNConfig.time_stemp : (index+1)*TxtRNNConfig.time_stemp]
        label = self.data[index*TxtRNNConfig.time_stemp+1 : (index+1)*TxtRNNConfig.time_stemp+1]
        return data, label
    
    def __len__(self):
        return len(self.data) // TxtRNNConfig.time_stemp
    
    
all_data = torch.tensor(all_data, dtype=torch.float32)
all_dataset = TxtRNNDataSet(all_data)
all_dataloader = dataloader.DataLoader(all_dataset, batch_size=TxtRNNConfig.batch_size)

for i, (x, y) in enumerate(all_dataloader):
    if i > 4:
        break
    print("x:  {}".format(x))
    print("y:  {}".format(y))
x:  tensor([[ 0.,  1.,  2.,  3.,  0.,  4.],
        [ 5.,  6.,  7.,  8.,  3.,  0.],
        [ 4.,  5.,  9., 10., 11.,  3.],
        [ 9., 10.,  7., 12.,  3., 13.],
        [14., 14., 14., 10., 15., 15.]])
y:  tensor([[ 1.,  2.,  3.,  0.,  4.,  5.],
        [ 6.,  7.,  8.,  3.,  0.,  4.],
        [ 5.,  9., 10., 11.,  3.,  9.],
        [10.,  7., 12.,  3., 13., 14.],
        [14., 14., 10., 15., 15., 16.]])
x:  tensor([[16.,  5.,  3., 17., 18., 19.],
        [ 3., 20., 13., 21., 22., 23.],
        [24.,  3., 25., 26.,  3., 20.],
        [13., 27.,  5.,  3., 28., 18.],
        [20., 13., 29., 18., 30., 31.]])
y:  tensor([[ 5.,  3., 17., 18., 19.,  3.],
        [20., 13., 21., 22., 23., 24.],
        [ 3., 25., 26.,  3., 20., 13.],
        [27.,  5.,  3., 28., 18., 20.],
        [13., 29., 18., 30., 31.,  3.]])
x:  tensor([[ 3., 32., 18., 20., 13., 33.],
        [18., 30., 31.,  3., 34., 18.],
        [20., 13., 35., 18., 30., 31.],
        [ 3., 36., 18., 20., 13., 37.],
        [18., 30., 31.,  3., 36., 18.]])
y:  tensor([[32., 18., 20., 13., 33., 18.],
        [30., 31.,  3., 34., 18., 20.],
        [13., 35., 18., 30., 31.,  3.],
        [36., 18., 20., 13., 37., 18.],
        [30., 31.,  3., 36., 18., 20.]])
x:  tensor([[20., 13., 37., 18., 30., 31.],
        [ 3., 28., 18., 20., 13., 29.],
        [18., 30., 31.,  3., 32., 18.],
        [20., 13., 33., 18., 30., 31.],
        [ 3., 34., 18., 20., 13., 35.]])
y:  tensor([[13., 37., 18., 30., 31.,  3.],
        [28., 18., 20., 13., 29., 18.],
        [30., 31.,  3., 32., 18., 20.],
        [13., 33., 18., 30., 31.,  3.],
        [34., 18., 20., 13., 35., 18.]])
x:  tensor([[18., 30., 31.,  3., 36., 18.],
        [20., 13., 37., 18., 30., 31.],
        [ 3., 36., 18., 20., 13., 37.],
        [18., 30., 31.,  3.,  0.,  1.],
        [ 2.,  3.,  0.,  4.,  5.,  6.]])
y:  tensor([[30., 31.,  3., 36., 18., 20.],
        [13., 37., 18., 30., 31.,  3.],
        [36., 18., 20., 13., 37., 18.],
        [30., 31.,  3.,  0.,  1.,  2.],
        [ 3.,  0.,  4.,  5.,  6.,  7.]])