pytorch学习笔记

pytorch框架 学习笔记

张量Tensors

概念:可以理解为是一个多维的数组,和Numpy的多维数组挺像的,但是张量可以在GPU上或者说硬件加速器上运行

1.tensor的创建

  • 列表中创建张量
a = [1,2,3.] #type(a)的结果是list
b = torch.tensor(a) #b的类型就是torch.Tensor
  • 从numpy数组中创建张量
a = np.random.normal((2,3)) #生成一个正态分布的数组array
torch.tensor(a) #变成tensor
  • 从另一个张量中初始化一个新的张量
b #比如b是一个tensor([1.84,1.66])
c = torch.ones_like(b) #c就变成了tensor([1.,1.]),如果是rand_like(b),那就是随机数的样子
  • 根据形状生成tensor
torch.rand([1,2]) #一行两列,元素内容是随机的
torch.rand((1,2)) #和上面的含义一样
#或者直接定义tensor里面的内容
a = torch.tensor([1,2],[2,3])	#2*2的张量

2.tensor的API

属性

tensor的一些属性:shape,dtype,device(在哪个设备上CPU or GPU)

range和arange
  • torch.range(start,end,step),是[start,end],也可以只写一个数字,就是从0开始range(start=0,end=3)
  • torch.arange()比上面的range短一个单位
eye生成矩阵
  • torch.eye(3),生成一个三阶方阵,对角线全是1
full填满元素
  • torch.full(size,fill_value),size规定几行几列,fill_size说明矩阵的元素值是多少
cat连接
  • torch.cat(tensors),连接张量
a = torch.rand([2,2])
b = torch.rand([2,3])
torch.cat([a,b],dim=1) #意思是把两个张量的列拼在一起,因为他们只有行数是一样的
chunk和spilt拆分张量
  • torch.chunk(tensor,拆分个数),拆分张量
b = torch.rand([3.2])
c,d = torch.chunk(b,chunks=2,dim=0) #c是2行2列,d是1行2列,因为3行没法整除2,默认是dim=0,沿着行分割
  • torch.spilt(),可以不用均分,不像chunk只能均分,spilt可以自定义划分大小
a = torch.arange(10).reshape(5,2)
torch.split(a, 2) #以2为单位划分
'''
(tensor([[0, 1],
         [2, 3]]),
 tensor([[4, 5],
         [6, 7]]),
 tensor([[8, 9]]))
'''
torch.split(a, [1,4])#以列表中的元素去划分,默认dim=0就是按行划分
'''
(tensor([[0, 1]]),
 tensor([[2, 3],
         [4, 5],
         [6, 7],
         [8, 9]]))
'''
squeeze和unsqueeze维度变化
  • torch.squeeze(input,dim=1),就是把大小维度为1的那些向量移除掉,比如输入的是A * 1 * B,那么最后就是A×B,dim就是指定消除为1的维数
  • torch.unsqueeze(input,dim),就是根据dim的数值对input的这个向量,的dim位置增加一个维度
#比如a的维度就是[2,3]
torch.unsqueeze(a,dim=0).shape #torch.size([1,2,3])
torch.unsqueeze(a,dim=1).shape #torch.size([2,1,3])
torch.unsqueeze(a,dim=-1).shape #torch.size([2,3,1]),-1就是在最后一个维度加
take寻找元素
  • torch.take(input,index),就是根据index去input这个张量里寻找元素,在寻找的时候会相当于把input这个张量铺平,铺成一维的再去寻找
src = torch.tensor([[4, 3, 5],
                    [6, 7, 8]])
torch.take(src, torch.tensor([0, 2, 5])) #结果是tensor([ 4,  5,  8])
tile复制张量
  • torch.tile(input,dims),复制的作用,dims规定第几维度复制几份
x = torch.tensor([1, 2, 3])
x.tile((2,))   #结果是tensor([1, 2, 3, 1, 2, 3])
y = torch.tensor([[1, 2], [3, 4]])
torch.tile(y, (2, 2)) 
'''结果是tensor([[1, 2, 1, 2],
        [3, 4, 3, 4],
        [1, 2, 1, 2],
        [3, 4, 3, 4]]),(2,2)指第一维度行复制两份,第二维度列也复制2份'''
transpose维度交换
  • torch.transpose(input,dim0,dim1),dim0和dim1就是即将交换的维度
unbind维度拆分
  • torch.unbind(),对于某一个维度,拆分成不同的小张量
a = torch.tensor([[1, 2, 3],[4, 5, 6],[7, 8, 9]]) #是一个3×3
torch.unbind(a,dim=0)
#结果是(tensor([1, 2, 3]), tensor([4, 5, 6]), tensor([7, 8, 9]))
#如果dim=1, (tensor([1, 4, 7]), tensor([2, 5, 8]), tensor([3, 6, 9]))
where判断
  • torch.where(),有点像是判断语句
1666176230648
随机采样函数
  • 还有随机的采样函数:

    伯努利函数,normal正态分布函数

    randperm(4):传一个上界,适合建立随机的数据集,比如这个就是tensor([2,1,0,3]),且每次都不一样

Datesets & DateLoaders类

1、Datesets 类

​ 概念:用来处理单个训练样本,如何从磁盘中读取训练数据集,包括特征标签等,并且可以做一些简单的变形和数据的预处理,映射出来X和y

​ 预处理:如果想要自定义一个Datesets则需要先去继承官方的Datesets的这个类,必须先实现三个函数,_ init _, _ len _ ,

_ getitem _三个函数。

import os
import pandas as pd
from torchvision.io import read_image
#自定义一个自己的datasets的类
class CustomImageDataset(Dataset):
    def __init__(self, annotations_file, img_dir, transform=None, target_transform=None):
        self.img_labels = pd.read_csv(annotations_file) #annotations_file是文件目录路径
        self.img_dir = img_dir  #img_dir存储照片的目录
        self.transform = transform
        self.target_transform = target_transform

    def __len__(self): #返回数据集的大小,
        return len(self.img_labels)

    def __getitem__(self, idx): #通过idx索引返回一个样本,注意是只有一个样本,一个样本的image还有label
        #通过img_labels的第idx行第0列,获得一个文件名称
        img_path = os.path.join(self.img_dir, self.img_labels.iloc[idx, 0])
        image = read_image(img_path) #将文件中的照片加载到内存中,得到image
        label = self.img_labels.iloc[idx, 1]
        if self.transform: #可能需要对image进行一系列的变化,比如归一化,对他通道处理等等,就是一些预处理
            image = self.transform(image)
        if self.target_transform:
            label = self.target_transform(label) #如果对label处理,就用target_transform,跟上面含义一样
        return image, label

再来个简单点的初始化数据集Datasets的代码

# 创建数据集对象
class text_dataset(Dataset): #需要继承Dataset类
    def __init__(self, words, labels):
        self.words = words
        self.labels = labels

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

    def __getitem__(self, idx):
        label = self.labels[idx]
        word = self.words[idx]
        return word, label
2、DateLoaders类

​ 概念:是对于多个样本而言,通过Datasets获得单个训练样本之后,用DateLoaders变成我们随机梯度下降算法所以要的minibatches 的形式,就是小样本批量处理需要的数据。也可以在每个周期之后对数据进行打乱,也可以对数据固定的保存在GPU中,就是对数据进行的一系列操作

1666186496347
from torch.utils.data import DataLoader

train_dataloader = DataLoader(training_data, batch_size=64, shuffle=True)
test_dataloader = DataLoader(test_data, batch_size=64, shuffle=True)

#第一个参数就是Datasets的实例化,batch_size就是步长,
#shuffle就是每个训练周期之后是不是需要对这个数据进行一个打乱,
#Sampler就是采样方式,可以用默认的也可以自己实现一个,如果自定义了一个Sampler就不需要shuffle了
#batch_sampler就是batch级别的采样,想要以多大的batch构成一个minibatch,构建一批批样本
#num_workers就是默认值为0,默认使用主进程加载数据,也可以改成一个更大的数,通常取决于CPU的个数
#pin_memory就是可以把Tensor保存在GPU中,不需要重复保存
#drop_last:如果不是整数倍的,可以把最后剩下的扔掉 
3、举个例子
from torch.utils.data import DataLoader
#先进行初始化训练集和测试集
train_dataloader = DataLoader(training_data, batch_size=64, shuffle=True)
test_dataloader = DataLoader(test_data, batch_size=64, shuffle=True)
#开始显示图像和标签
#_ _iter_ _是dataloader里面的一个迭代器,返回一个迭代
#_ _next_ _,里面调用了next_data(),就得到了data,返回了一个minibatch的数据
train_features, train_labels = next(iter(train_dataloader)) 

print(f"Feature batch shape: {train_features.size()}")
print(f"Labels batch shape: {train_labels.size()}")
img = train_features[0].squeeze()
label = train_labels[0]
plt.imshow(img, cmap="gray")
plt.show()
print(f"Label: {label}")

'''结果展示:
Feature batch shape: torch.Size([64, 1, 28, 28])
Labels batch shape: torch.Size([64]) 返回两个值train_features是图片也就是特征就是X,而train_labels就是y
Label: 5,表示y值是5
'''
4、一个学习链接:

https://zhuanlan.zhihu.com/p/544362848

5 、Transforms

概念:在读取图像之后,有时候在喂入神经网络之前可能还需要对图片的大小,图片的通道,图片的像素值进行一些修整变化,使其满足神经网络的要求。就是Transforms的作用。

Build Model实例

在pytorch中构建一个神经网络的实例,主要关于torch.nn,本质上神经网络上用到的模型都是继承于nn.Module。

1、获取训练用到的设备
device = "cuda" if torch.cuda.is_available() else "cpu"
print(f"Using {device} device")  #这两行代码可有可无?
2、初始化模块

通过对nn进行子类继承,并在__ init__中初始化神经网络层。在forward方法中实现对输入数据的操作

class NeuralNetwork(nn.Module):
    def __init__(self): #在这里需要定义好各个模块
        super(NeuralNetwork, self).__init__() #调用父类的创建模块,也可以这样写super().__init__() 
        self.flatten = nn.Flatten()
        self.linear_relu_stack = nn.Sequential(
            nn.Linear(28*28, 512), #线性层,第一个参数是输入特征的特征维度,第二个是隐含层的大小
            nn.ReLU(),		      #非线性的激活函数
            nn.Linear(512, 512),
            nn.ReLU(),
            nn.Linear(512, 10),  #最后输出一个10个类别的输出
        )

    def forward(self, x): #这个其实就是一个前向传播,用起来上面定义好的各个模块
        x = self.flatten(x) #对x的维度进行展开铺平
        logits = self.linear_relu_stack(x)
        return logits
3、初始化模块
model = NeuralNetwork().to(device) #to函数是module中方法,意思就是将各种优化及一系列的附属量都放在上面运算
print(model)
4、使用模块,输入数据
X = torch.rand(1, 28, 28, device=device) #初始化一个数据
logits = model(X)   #开始完成前向运算
pred_probab = nn.Softmax(dim=1)(logits) #dim表示对哪一个维度进行计算
y_pred = pred_probab.argmax(1)
print(f"Predicted class: {y_pred}")

5、讲解模块的模型层

input_image = torch.rand(3,28,28)
print(input_image.size())  #torch.Size([3, 28, 28])

#nn.Flatten
flatten = nn.Flatten() 	   #就是将后面的维度,除了第0维,也就是从第一维到最后一维度合并到一起
flat_image = flatten(input_image)
print(flat_image.size())   #torch.Size([3, 784])

#nn.Linear
#in_features就是线性层所接受的输入的特征大小,out_features就是输出的隐含层大小
layer1 = nn.Linear(in_features=28*28, out_features=20) 
hidden1 = layer1(flat_image)
print(hidden1.size())      #torch.Size([3, 20])

#nn.Sequential
#这个是一个关于模块的有序的容器
seq_modules = nn.Sequential(
    flatten,
    layer1,
    nn.ReLU(),
    nn.Linear(20, 10)
)
input_image = torch.rand(3,28,28)
logits = seq_modules(input_image) #最后size是(3,10),第一个维度是batchsize
#因为我们要做的是分类任务,所以之后还需要传给softmax
softmax = nn.Softmax(dim=1)
pred_probab = softmax(logits) #关于十个类的一个概率分布

Autograd自动微分

概念:训练神经网络的时候怎么使用他的自动微分机制,让他去帮我们算模型中每一个梯度,而我们一般又是基于loss去计算梯度的,pytorch中有一个内置的微分引擎,Autograd。他支持对任意计算图的梯度计算

下图一时正向传播和反向传播的一个图示

图二是一个正向传播的运算分解,各个元操作,在求梯度的公式写好,根据链式法则就可以把所有的节点的梯度写出来

并且注意梯度是具有累计的,不能自动清零,平常需要注意清零 grad.zero_()

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

1、举个小例子

input x, parameters w and b, and some loss function。一层的神经网络小例子

import torch
x = torch.ones(5)  	# input tensor(1,5)
y = torch.zeros(3)  # expected output
w = torch.randn(5, 3, requires_grad=True) #requires_grad=True表示在做前向运算时,自动微分会自动记录参数的微分公式
b = torch.randn(3, requires_grad=True) #(1,3)
z = torch.matmul(x, w)+b
loss = torch.nn.functional.binary_cross_entropy_with_logits(z, y) #是一个二分类的交叉熵

#开始计算梯度 
loss.backward() #需要注意这里的loss必须是一个标量,如果是一个张量,就需要在backward里面加一个和loss的shape一样格式的向量,这时候就会对可以计算的父节点,都会计算出来一个梯度,比如各个参数或者说复合函数中间的函数名字,什么雅克比向量乘积
print(w.grad)
print(b.grad)

总结:写好前向传播,然后最后让loss变成一个标量,就可以调用backward()去求得梯度了

2、不需要计算梯度

分两种情况:

(1)将神经网络中的某些参数标记为冻结参数。这是微调预处理网络的常见场景

(2)当模型训练好之后只需要测试或者推理的时候,只进行正向传递时,为了加快计算速度,因为在不跟踪梯度的张量上进行计算会更有效

#有的时候是不需要计算梯度的,这时候就可以这么处理
z = torch.matmul(x, w)+b
print(z.requires_grad)   #true

with torch.no_grad():   #也可以换种方式处理:z_det = z.detach(),下面打印z_det.requires_grad也是false
    z = torch.matmul(x, w)+b
print(z.requires_grad)	#false

训练模型

为模型喂入一些数据,根据目标函数,不断迭代去调节参数,使得模型的预测值达到最好的效果

1、首先完成准备工作
import torch
from torch import nn
from torch.utils.data import DataLoader
from torchvision import datasets
from torchvision.transforms import ToTensor
#创建训练集和测试集,如果是自己的数据集,就需要自己按照上面讲述的datasets去创建类,然后实现那三个方法
training_data = datasets.FashionMNIST(
    root="data",
    train=True,
    download=True,
    transform=ToTensor()
)
test_data = datasets.FashionMNIST(
    root="data",
    train=False,
    download=True,
    transform=ToTensor()
)
#构建好datasets就扔给dataloader
train_dataloader = DataLoader(training_data, batch_size=64)
test_dataloader = DataLoader(test_data, batch_size=64)
#构建模型,一般写模型的时候都需要先继承nn.Module
class NeuralNetwork(nn.Module):
    def __init__(self):
        super(NeuralNetwork, self).__init__() #首先调用父类方法,初始化方法
        self.flatten = nn.Flatten()
        self.linear_relu_stack = nn.Sequential(
            nn.Linear(28*28, 512),
            nn.ReLU(),
            nn.Linear(512, 512),
            nn.ReLU(),
            nn.Linear(512, 10),
        )

    def forward(self, x):
        x = self.flatten(x)
        logits = self.linear_relu_stack(x)
        return logits

model = NeuralNetwork()
2、初始化一些超参数

概念:不需要去优化,但是会影响训练效果的一些参数

learning_rate = 1e-3
batch_size = 64
epochs = 5
3、设置目标函数
# 如果是分类的话就选择交叉熵函数
# 回归的话nn.MSELoss,nn.NLLLoss,或者 nn.CrossEntropyLoss
loss_fn = nn.CrossEntropyLoss()
4、构建优化器

优化器里面对参数进行更新,训练的时候需要注意:

(1)每一步优化之前都要对优化器中的参数梯度置零: optimizer.zero_grad()

(2)一旦loss是个标量,使用了 loss.backward() ,就可以使用 optimizer.step() 对模型中所有的参数进行更新

optimizer = torch.optim.SGD(model.parameters(), lr=learning_rate) #model.parameters()默认优化所有参数
5、完整的实现
def train_loop(dataloader, model, loss_fn, optimizer):
    size = len(dataloader.dataset)
    for batch, (X, y) in enumerate(dataloader): #枚举,每一次都是得到一个小批次
    #enumerate会给每一个(x,y)添加一个序号batch,用来表明训练到了第几组小批次样本
        pred = model(X) #这个就是那个logits,通过模型训练出来的结果
        loss = loss_fn(pred, y) #得到loss的标量
		#反向传播
        optimizer.zero_grad() #要记得置零
        loss.backward()
        optimizer.step()

        if batch % 100 == 0: #每训练100次就看一下结果
            loss, current = loss.item(), batch * len(X)
            print(f"loss: {loss:>7f}  [{current:>5d}/{size:>5d}]")


def test_loop(dataloader, model, loss_fn):
    size = len(dataloader.dataset)
    num_batches = len(dataloader)
    test_loss, correct = 0, 0

    with torch.no_grad():  #会提高效率,因为是测试集
        for X, y in dataloader:
            pred = model(X)
#item()返回的是一个浮点型数据,所以我们在求loss或者accuracy时,一般使用item(),而不是直接取它对应的元素
            test_loss += loss_fn(pred, y).item()
            correct += (pred.argmax(1) == y).type(torch.float).sum().item()

    test_loss /= num_batches
    correct /= size
    print(f"Test Error: \n Accuracy: {(100*correct):>0.1f}%, Avg loss: {test_loss:>8f} \n")

上述训练是单步训练和单步测试,但是我们不仅只是完成一次

loss_fn = nn.CrossEntropyLoss()
optimizer = torch.optim.SGD(model.parameters(), lr=learning_rate)
#迭代次数
epochs = 10
for t in range(epochs):
    print(f"Epoch {t+1}\n-------------------------------")
    train_loop(train_dataloader, model, loss_fn, optimizer)
    test_loop(test_dataloader, model, loss_fn)
print("Done!")

保存和加载模型

官网地址:https://pytorch.org/tutorials/recipes/recipes/saving_and_loading_models_for_inference.html

两种保存和加载方式:可以只保存state_dict,存储了模型的参数和buffer;或者把整个模型保存下来

首先初始化数据,然后定义模型,初始化模型,优化参数…代码同上,就不重复了,然后开始使用state_dict保存

1、state_dict保存和加载模型
#写一个文件名字
PATH = "state_dict_model.pt"

#保存:其中net是实例化模型的对象名称,net.state_dict()就获取了net的所有参数和buffer缓冲量
torch.save(net.state_dict(), PATH) #但是上述只是保存了模型的参数,并没有保存模型的图

#导入:导入的时候需要保存模型的图,首先需要把模型的图建立起来,再去导入参数和缓存什么的
model = Net() #Net()就是自己定义的模型的类名
model.load_state_dict(torch.load(PATH))
model.eval()  #就是主要是针对model 在训练时和评价时不同的 Batch Normalization 和 Dropout 方法模式
#其实上面这个用在预测的时候,因为预测的时候,网络参数是不能发生变化的

上面的model.eval()讲解链接:https://blog.csdn.net/sazass/article/details/116616664

2、对整个模型进行保存和加载
# Specify a path
PATH = "entire_model.pt"

# Save
torch.save(net, PATH)

# Load
model = torch.load(PATH) #和上面的方法相比,这种方法不需要再重新构建一个图结构了
model.eval()
3、保存和加载checkpoint

怎么去保存和加载一个一般的checkpoint

这个的作用是可以做一个暂缓的空间,可以方便暂停一下,方便之后继续训练

然后开始举例子,首先还是初始化数据,然后定义模型,初始化模型,优化参数…代码同上,就不重复了

先是保存:

# Additional information
EPOCH = 5  #因为在训练的时候外面肯定有个epoch,我们这里就举例每五次保存一次checkpoint
PATH = "model.pt"
LOSS = 0.4

torch.save({
            'epoch': EPOCH,  #当前训练多少个周期
            'model_state_dict': net.state_dict(), #保存模型的参数
            'optimizer_state_dict': optimizer.state_dict(), #保存优化器内部的状态
            'loss': LOSS, #保存当前的训练loss,或者是验证loss
            }, PATH)

下述是加载:

model = Net()
optimizer = optim.SGD(net.parameters(), lr=0.001, momentum=0.9)

checkpoint = torch.load(PATH)
model.load_state_dict(checkpoint['model_state_dict'])
optimizer.load_state_dict(checkpoint['optimizer_state_dict'])
epoch = checkpoint['epoch']
loss = checkpoint['loss']

model.eval() #做推理的时候用这个,因为预测或者说推理的时候,网络参数是不能发生变化的
# - or -
model.train() #做训练的时候用这个
4、保存加载多个模型

还是老样子,同样的流程只不过这次我们用两个模型举例,因为我们是展示保存加载多个模型

保存:

#现在是初始化两个模型,和优化算法
netA = Net()
netB = Net()
optimizerA = optim.SGD(netA.parameters(), lr=0.001, momentum=0.9)
optimizerB = optim.SGD(netB.parameters(), lr=0.001, momentum=0.9)

#开始保存
PATH = "model.pt"
#用一个字典保存起来
torch.save({
            'modelA_state_dict': netA.state_dict(),
            'modelB_state_dict': netB.state_dict(),
            'optimizerA_state_dict': optimizerA.state_dict(),
            'optimizerB_state_dict': optimizerB.state_dict(),
            }, PATH)

加载:

modelA = Net()
modelB = Net()
optimModelA = optim.SGD(modelA.parameters(), lr=0.001, momentum=0.9)
optimModelB = optim.SGD(modelB.parameters(), lr=0.001, momentum=0.9)

checkpoint = torch.load(PATH)
modelA.load_state_dict(checkpoint['modelA_state_dict'])
modelB.load_state_dict(checkpoint['modelB_state_dict'])
optimizerA.load_state_dict(checkpoint['optimizerA_state_dict'])
optimizerB.load_state_dict(checkpoint['optimizerB_state_dict'])

modelA.eval()
modelB.eval()
# - or -
modelA.train()
modelB.train()

李沐深度学习模块讲解

1、层和块

(1)Sequential类

仔细地看看Sequential类是如何工作的, 回想一下Sequential的设计是为了把其他模块串起来。 为了构建我们自己的简化的MySequential, 我们只需要定义两个关键函数:

  1. 一种将块逐个追加到列表中的函数。
  2. 一种前向传播函数,用于将输入按追加块的顺序传递给块组成的“链条”。

下面的MySequential类提供了与默认Sequential类相同的功能。就是初始化的时候,里面的一个个layer放进_module中,然后再前向运算,也就是返回的时候,再从modules.values()中一个个的取出来

class MySequential(nn.Module):
    def __init__(self, *args):
        super().__init__()
        for idx, module in enumerate(args):
            # 这里,module是Module子类的一个实例。我们把它保存在'Module'类的成员
            # 变量_modules中。_module的类型是OrderedDict
            self._modules[str(idx)] = module
    def forward(self, X):
        # OrderedDict保证了按照成员添加的顺序遍历它们
        for block in self._modules.values():
            X = block(X)
        return X
    
 #下面就是调用
net = MySequential(nn.Linear(20, 256), nn.ReLU(), nn.Linear(256, 10))
net(X)
(2)前向传播函数中执行代码
Sequential类使模型构造变得简单, 允许我们组合新的架构,而不必定义自己的类。 然而,并不是所有的架构都是简单的顺序架构。 当需要更强的灵活性时,我们需要定义自己的块。 例如,我们可能希望在前向传播函数中执行Python的控制流。 此外,我们可能希望执行任意的数学运算, 而不是简单地依赖预定义的神经网络层。 

​ 在这个FixedHiddenMLP模型中,我们实现了一个隐藏层, 其权重(self.rand_weight)在实例化时被随机初始化,之后为常量。 这个权重不是一个模型参数,因此它永远不会被反向传播更新。 然后,神经网络将这个固定层的输出通过一个全连接层。注意,在返回输出之前,模型做了一些不寻常的事情: 它运行了一个while循环,在L1范数大于1的条件下, 将输出向量除以2,直到它满足条件为止。 最后,模型返回了X中所有项的和。 注意,此操作可能不会常用于在任何实际任务中, 我们只是向你展示如何将任意代码集成到神经网络计算的流程中。

class FixedHiddenMLP(nn.Module):
    def __init__(self):
        super().__init__()
        # 不计算梯度的随机权重参数。因此其在训练期间保持不变
        self.rand_weight = torch.rand((20, 20), requires_grad=False)
        self.linear = nn.Linear(20, 20)

    def forward(self, X):
        X = self.linear(X)
        # 使用创建的常量参数以及relu和mm函数,mm就是矩阵相乘
        X = F.relu(torch.mm(X, self.rand_weight) + 1)
        # 复用全连接层。这相当于两个全连接层共享参数
        X = self.linear(X)
        # 控制流
        while X.abs().sum() > 1:
            X /= 2
        return X.sum()

​ 上面主要想讲的其实就是,我们可以自己定义一下模型里面的运算,不只是一层一层的叠加,我们可以进行适当的修改,添加一些我们需要的参数,或者运算,或是变形。

2、参数管理

(1)参数访问

其中net就是实例化的模型,[0]、[1]这些东西就是代指的是 Sequential类定义的模型里面的层。下面代码以及运行结是:

print(net[2].state_dict())
OrderedDict([('weight', tensor([[ 0.0251, -0.2952, -0.1204,  0.3436, -0.3450, -0.0372,  0.0462,  0.2307]])), ('bias', tensor([0.2871]))])

所以我们可以通过以下语句去访问模型中的参数:

print(type(net[2].bias)) #<class 'torch.nn.parameter.Parameter'>
print(net[2].bias)		#tensor([0.2871], requires_grad=True)
print(net[2].bias.data)  #tensor([0.2871])
(2)参数初始化

​ 其中normal_这种后面带有一个下划线的表示是,替代模型中参数的值,而不是调用此函数有个返回值

​ 下面代码展示的是,首先调用内置的初始化器。 下面的代码将所有权重参数初始化为标准差为0.01的高斯随机变量, 且将偏置参数设置为0

def init_normal(m):   #m指的就是模块
    if type(m) == nn.Linear:
        nn.init.normal_(m.weight, mean=0, std=0.01) #之所以可以直接用m.weight 是因为模型中的参数名,看上面第一部分参数访问的结果中就是weight和bias
        nn.init.zeros_(m.bias)
net.apply(init_normal)  #意思就是将init_normal这个初始化函数放到模块net中
net[0].weight.data[0], net[0].bias.data[0]  #(tensor([1., 1., 1., 1.]), tensor(0.))

​ 我们还可以对某些块应用不同的初始化方法。 例如,下面我们使用Xavier初始化方法初始化第一个神经网络层, 然后将第三个神经网络层初始化为常量值42。

def init_xavier(m):  
    if type(m) == nn.Linear:
        nn.init.xavier_uniform_(m.weight) #就是固定的初始化xavier的方法,这个xavier是一种追求数值稳定性的初始化方法,就是之后梯度下降的时候尽可能的不会梯度爆炸也不会梯度消失
def init_42(m):
    if type(m) == nn.Linear:
        nn.init.constant_(m.weight, 42)

net[0].apply(init_xavier)
net[2].apply(init_42)
print(net[0].weight.data[0])   #tensor([-0.4792,  0.4968,  0.6094,  0.3063])
print(net[2].weight.data)      #tensor([[42., 42., 42., 42., 42., 42., 42., 42.]])
(3)参数绑定

有时我们希望在多个层间共享参数: 我们可以定义一个稠密层,然后使用它的参数来设置另一个层的参数

# 我们需要给共享层一个名称,以便可以引用它的参数
shared = nn.Linear(8, 8)
net = nn.Sequential(nn.Linear(4, 8), nn.ReLU(),
                    shared, nn.ReLU(),
                    shared, nn.ReLU(),
                    nn.Linear(8, 1))
net(X)
# 检查参数是否相同
print(net[2].weight.data[0] == net[4].weight.data[0]) #tensor([True, True, True, True, True, True, True, True])
net[2].weight.data[0, 0] = 100
# 确保它们实际上是同一个对象,而不只是有相同的值
print(net[2].weight.data[0] == net[4].weight.data[0]) #tensor([True, True, True, True, True, True, True, True])

这个例子表明第三个和第五个神经网络层的参数是绑定的。 它们不仅值相等,而且由相同的张量表示。 因此,如果我们改变其中一个参数,另一个参数也会改变。 你可能会思考:当参数绑定时,梯度会发生什么情况?

答案是由于模型参数包含梯度,因此在反向传播期间第二个隐藏层 (即第三个神经网络层)和第三个隐藏层(即第五个神经网络层)的梯度会加在一起。

3、自定义层

定义具有参数的层, 这些参数可以通过训练进行调整。 我们可以使用内置函数来创建参数,这些函数提供一些基本的管理功能。 比如管理访问、初始化、共享、保存和加载模型参数。 这样做的好处之一是:我们不需要为每个自定义层编写自定义的序列化程序。

现在,让我们实现自定义版本的全连接层。 回想一下该层需要两个参数,一个用于表示权重另一个用于表示偏置项。 在此实现中,我们使用修正线性单元作为激活函数。 该层需要输入参数:in_unitsunits,分别表示输入数和输出数。

class MyLinear(nn.Module):
    def __init__(self, in_units, units):
        super().__init__()
        self.weight = nn.Parameter(torch.randn(in_units, units)) #nn.Parameter()添加的参数会被添加到Parameters列表中,会被送入优化器中随训练一起学习更新
        # torch.nn.parameter.Parameter(data=None,requires_grad=True)
        self.bias = nn.Parameter(torch.randn(units,))
    def forward(self, X):
        linear = torch.matmul(X, self.weight.data) + self.bias.data
        return F.relu(linear)
linear = MyLinear(5, 3)
linear.weight   
#打印的结果:tensor([[-1.4779, -0.6027, -0.2225],
#       [ 1.1270, -0.6127, -0.2008],
#       [-2.1864, -1.0548,  0.2558],
#       [ 0.0225,  0.0553,  0.4876],
#       [ 0.3558,  1.1427,  1.0245]], requires_grad=True)

GPU & CPU

1、计算设备

​ 在PyTorch中,CPU和GPU可以用torch.device('cpu')torch.device('cuda')表示。 应该注意的是,cpu设备意味着所有物理CPU和内存, 这意味着PyTorch的计算将尝试使用所有CPU核心。 然而,gpu设备只代表一个卡和相应的显存。 如果有多个GPU,我们使用torch.device(f'cuda:{i}') 来表示第i块GPU(i从0开始)。 另外,cuda:0cuda是等价的。

import torch
from torch import nn
torch.device('cpu'), torch.device('cuda'), torch.device('cuda:1')
#(device(type='cpu'), device(type='cuda'), device(type='cuda', index=1)) index=1的意思是在第二块GPU上

#我们可以查询可用gpu的数量
torch.cuda.device_count()

现在我们定义了两个方便的函数, 这两个函数允许我们在不存在所需所有GPU的情况下运行代码。

注意之后下面讲别的内容的时候会用到这两个关键函数

def try_gpu(i=0):  #比如,try_gpu(1),如果存在超过两块的GPU,就返回第二块GPU,方便之后设置在哪块gpu上存储运行数据
    """如果存在,则返回gpu(i),否则返回cpu()"""
    if torch.cuda.device_count() >= i + 1:
        return torch.device(f'cuda:{i}')
    return torch.device('cpu') #如果参数i不合理或者没有gpu的话,就继续使用cpu

def try_all_gpus():  #意思是返回所有可用的gpu
    """返回所有可用的GPU,如果没有GPU,则返回[cpu(),]"""
    devices = [torch.device(f'cuda:{i}')
             for i in range(torch.cuda.device_count())]
    return devices if devices else [torch.device('cpu')]
2、张量与GPU

以查询张量所在的设备。 默认情况下,张量是在CPU上创建的。

x = torch.tensor([1, 2, 3])
x.device #运行结果:device(type='cpu')

需要注意的是,无论何时我们要对多个项进行操作, 它们都必须在同一个设备上。 例如,如果我们对两个张量求和, 我们需要确保两个张量都位于同一个设备上, 否则框架将不知道在哪里存储结果,甚至不知道在哪里执行计算

3、存储在GPU上

​ 有几种方法可以在GPU上存储张量。 例如,我们可以在创建张量时指定存储设备。接 下来,我们在第一个gpu上创建张量变量X。 在GPU上创建的张量只消耗这个GPU的显存。 我们可以使用nvidia-smi命令查看显存使用情况。 一般来说,我们需要确保不创建超过GPU显存限制的数据

X = torch.ones(2, 3, device=try_gpu())  #这里就用到了上面定义的函数try_gpu(),没有参数的时候就是使用第一块GPU,也就是'cuda:0'
X
#运行结果:tensor([[1., 1., 1.],
#       [1., 1., 1.]], device='cuda:0')

假设你至少有两个GPU,下面的代码将在第二个GPU上创建一个随机张量

Y = torch.rand(2, 3, device=try_gpu(1))
Y  #tensor([[0.1379, 0.4532, 0.9869],
   #     [0.4779, 0.7426, 0.9622]], device='cuda:1')
4、复制

如果我们要计算X + Y,我们需要决定在哪里执行这个操作。 如下图所以, 我们可以将X传输到第二个GPU并在那里执行操作。 不要简单地X加上Y,因为这会导致异常, 运行时引擎不知道该怎么做:它在同一设备上找不到数据会导致失败。 由于Y位于第二个GPU上,所以我们需要将X移到那里, 然后才能执行相加运算

1667100133143
Z = X.cuda(1)
print(X)
print(Z)
'''运行结果:
tensor([[1., 1., 1.],
        [1., 1., 1.]], device='cuda:0')
tensor([[1., 1., 1.],
        [1., 1., 1.]], device='cuda:1')
'''
#现在数据在同一个GPU上(Z和Y都在),我们可以将它们相加
Y + Z
'''运行结果:
tensor([[1.1379, 1.4532, 1.9869],
        [1.4779, 1.7426, 1.9622]], device='cuda:1')
'''

假设变量Z已经存在于第二个GPU上。 如果我们还是调用Z.cuda(1)会发生什么?它将返回Z而不会复制并分配新内存。

Z.cuda(1) is Z #结果是:True
5、神经网络与GPU

类似地,神经网络模型可以指定设备。 下面的代码将模型参数放在GPU上, 当输入为GPU上的张量时,模型将在同一GPU上计算结果

net = nn.Sequential(nn.Linear(3, 1))
net = net.to(device=try_gpu())   #.to()就是放在gpu上的函数

让我们确认模型参数存储在同一个GPU上

net[0].weight.data.device  #  device(type='cuda', index=0)

一设备上找不到数据会导致失败。 由于Y位于第二个GPU上,所以我们需要将X移到那里, 然后才能执行相加运算

1667100133143
Z = X.cuda(1)
print(X)
print(Z)
'''运行结果:
tensor([[1., 1., 1.],
        [1., 1., 1.]], device='cuda:0')
tensor([[1., 1., 1.],
        [1., 1., 1.]], device='cuda:1')
'''
#现在数据在同一个GPU上(Z和Y都在),我们可以将它们相加
Y + Z
'''运行结果:
tensor([[1.1379, 1.4532, 1.9869],
        [1.4779, 1.7426, 1.9622]], device='cuda:1')
'''

假设变量Z已经存在于第二个GPU上。 如果我们还是调用Z.cuda(1)会发生什么?它将返回Z而不会复制并分配新内存。

Z.cuda(1) is Z #结果是:True
5、神经网络与GPU

类似地,神经网络模型可以指定设备。 下面的代码将模型参数放在GPU上, 当输入为GPU上的张量时,模型将在同一GPU上计算结果

net = nn.Sequential(nn.Linear(3, 1))
net = net.to(device=try_gpu())   #.to()就是放在gpu上的函数

让我们确认模型参数存储在同一个GPU上

net[0].weight.data.device  #  device(type='cuda', index=0)
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值