20240325_AI小字典

20240325_AI小字典

PyTorch基础

Torch自称为神经网络界的Numpy,因为它能将torch产生的tensor放在GPU中加速运算,就像Numpy会把array放在CPU中加速运算。Torch和Numpy有着很好的兼容性,可以自由的转换numpy的array和torch的tensor。

import torch
import torch as t
from torch import optim
import numpy as np

np_data = np.arange(6).reshape((2,3))
torch_data = torch.from_numpy(np_data)
tensor2array = torch_data.numpy()               #tensor转为numpy的array
array2tensor = torch.from_numpy(np_data)        # array转为pytorch的tensor

torch_data

tensor([[0, 1, 2],
        [3, 4, 5]])
data = [[1,2],[3,4]]
tensor_data = t.FloatTensor(data)  # 将data转为float32类型的tensor
tensor_data
tensor([[1., 2.],
        [3., 4.]])

一般是安装两个部分:pytorch和torchvision。前者是Pytorch的主模块,后者是一些库,包括一些网络的预先训练好的model和各种资源

官网支持pip和conda两种安装方式;(最好用镜像,自己装过两次,没弄明白。。。)

x1 = t.Tensor(2,3)   #构建了2*3的矩阵,并未初始化
x2 = t.rand(3,4)      #使用[0,1]均匀分布随机初始化二维数组

Tensor可以通过.cuda方法转为GPU的Tensor,从而加速运算。
Tensor还支持很多操作,包括数学运算、线性代数、选择、切片(与numpy类似)

import torch as t
import numpy as np

x = t.rand(3,4)
y = t.rand(3,4)

if t.cuda.is_available(): # 本机GPU是不可用的!
    x = x.cuda()
    y = y.cuda()
    
result = x + y
print(result) # 如果cuda可用 则会标记 GPU device 一般是 cuda:0


tensor([[0.3, 0.9, 0.7, 1.1],
        [1.0, 1.5, 1.4, 0.5],
        [1.6, 1.3, 0.6, 1.3]])

Tensor和numpy的数组间互操作非常容易且快速。Tensor不支持的操作,可以先转为numpy数组处理,之后再转回Tensor。Tensor和numpy对象共享内存,所以他们之间的转换很快,几乎不会消耗资源。这意味着其中一个变了,另一个也会随之改变。

深度学习的算法实质上是通过反向传播求导数,Pytorch的Autograd模块实现了此功能。在Tensor上的所有操作,Autograd都能为他们自动提供微分,避免手动计算导数的复杂过程。

autograd.Variable类是Autograd中的核心类,它简单封装了Tensor,并支持几乎所有Tensor的操作。Tensor在被封装为Variable后,可以调用它的.backward实现反向传播,自动计算所有梯度。

Variable主要包含三个属性:

  • (1)data:保存Variable所包含的Tensor。
  • (2)grad:保存data对应的梯度,grad也是个Variable,而不是Tensor,他和data的形状一样。
  • (3)grad_fn:指向一个Function对象,这个Function用来反向传播计算输入的梯度。

grad在反向传播过程中是累加的,这意味着每次运行反向传播,梯度都会累加之前的梯度,所以反向传播之前需要把梯度清零。

Variable和Tensor具有几乎一致的接口,在实际使用中可以无缝切换。

Autograd实现了反向传播功能,但是直接用来写深度学习代码在很多情况下还是有点复杂,torch.nn是专门为神经网络设计的模块化接口。nn构建于Autograd之上,可以用来定义和运行神经网络。nn.Module是nn中最重要的类,可以把它看作一个网络的封装,包含网络各层定义及forward方法,调用forward(input)方法,可返回前向传播的结果。

定义网络时,需要继承nn.Module,并实现它的forward方法,把网络中具有可学习参数的层放在构造函数__init__中。如果某一层(如ReLU)不具有可学习的参数,则既可以放在构造函数中,也可以不放。

import torch.nn as nn
import torch.nn.functional as F

class Net(nn.Module):
    def __init__(self):
        # nn.Module子类的函数必须在构造函数中执行父类的构造函数
        # 下式等价于nn.Module.__init__(self)
        super(Net,self).__init__()

        # 卷积层
        # 卷积层’1‘表示输入图片为单通道,’6‘表示输出通道数,’5‘表示卷积核为5*5
        self.conv1 = nn.Conv2d(1,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,10)

    def forward(self, x):
        # 卷积-->激活-->池化
        x = F.max_pool2d(F.relu(self.conv1(x)),(2,2))
        x = F.max_pool2d(F.relu(self.conv2(x)),2)

        # reshape,’-1‘表示自适应
        x = x.view(x.size()[0],-1)
        x = F.relu(self.fc1(x))
        x = F.relu(self.fc2(x))
        x = self.fc3(x)
        return x

net = Net()
print(net)


Net(
  (conv1): Conv2d(1, 6, kernel_size=(5, 5), stride=(1, 1))
  (conv2): Conv2d(6, 16, kernel_size=(5, 5), stride=(1, 1))
  (fc1): Linear(in_features=400, out_features=120, bias=True)
  (fc2): Linear(in_features=120, out_features=84, bias=True)
  (fc3): Linear(in_features=84, out_features=10, bias=True)
)

只要在nn.Module的子类中定义了forward函数,backward函数就会被自动实现(利用Autograd)。在forward函数中可使用任何Variable支持的函数,还可以使用if,for循环,print,log等Python语法,写法和标准的Python写法一致。

网络的可学习参数通过net.parameters()返回,net.named_parameters可同时返回可学习的参数及名称。

forward函数的输入和输出都是Variable,只有Variable才具有自动求导功能,Tensor是没有的,所以在输入时,需要把Tensor封装成Variable。不过0.4版本以后,这两个已经整合到了一起,不再需要这步了。

params = list(net.parameters())
print(len(params))
for name,parameters in net.named_parameters():
    print(name,':',parameters.size())
10
conv1.weight : torch.Size([6, 1, 5, 5])
conv1.bias : torch.Size([6])
conv2.weight : torch.Size([16, 6, 5, 5])
conv2.bias : torch.Size([16])
fc1.weight : torch.Size([120, 400])
fc1.bias : torch.Size([120])
fc2.weight : torch.Size([84, 120])
fc2.bias : torch.Size([84])
fc3.weight : torch.Size([10, 84])
fc3.bias : torch.Size([10])

nn实现了神经网络种大多数的损失函数,例如nn.MSELoss用来计算均方误差,nn.CrossEntropyLoss用来计算交叉熵损失。
如果对loss进行反向传播溯源(使用grad_fn属性),可看到它的计算图:

当调用loss.backward()函数时,该图会动态生成并自动微分,也会自动计算图中参数的导数。

input_ = t.randn(1,1,32,32)
output = net(input_)

from torchsummary import summary
print("以下是输出的模型参数信息")
summary(net, input_size=input_.squeeze(0).size(), device="cpu")

target = t.arange(0,10).reshape(1,10)
target = target.float()

criterion = nn.MSELoss()
loss = criterion(output,target)
net.zero_grad()
print('反向传播之前的conv1.bias的梯度')
print(net.conv1.bias.grad)
loss.backward()
print('反向传播之后的conv1.bias的梯度')

print(net.conv1.bias.grad)
以下是输出的模型参数信息
----------------------------------------------------------------
        Layer (type)               Output Shape         Param #
================================================================
            Conv2d-1            [-1, 6, 28, 28]             156
            Conv2d-2           [-1, 16, 10, 10]           2,416
            Linear-3                  [-1, 120]          48,120
            Linear-4                   [-1, 84]          10,164
            Linear-5                   [-1, 10]             850
================================================================
Total params: 61,706
Trainable params: 61,706
Non-trainable params: 0
----------------------------------------------------------------
Input size (MB): 0.00
Forward/backward pass size (MB): 0.05
Params size (MB): 0.24
Estimated Total Size (MB): 0.29
----------------------------------------------------------------
反向传播之前的conv1.bias的梯度
tensor([0., 0., 0., 0., 0., 0.])
反向传播之后的conv1.bias的梯度
tensor([-1.2e-01,  1.7e-02,  9.4e-06,  3.6e-02,  1.7e-02,  4.6e-02])
loss.grad_fn
<MseLossBackward0 at 0x7f98e4166a90>

在反向传播计算完所有参数的梯度后,还需要使用优化方法更新网络的权重和参数。torch.optim中实现了深度学习中绝大多数的优化方法,例如RMSProp、Adam、SGD等,便于使用。

optimizer = optim.SGD(net.parameters(),lr=0.01)

# 在训练过程中,先把梯度清零
optimizer.zero_grad() # 使用优化器 控制net参数 替代net.zero_grad()

# 计算损失
input = t.randn(1,1,32,32)
output = net(input)
target = t.arange(0,10).reshape(1,10)
target = target.float()

criterion = nn.MSELoss()
loss = criterion(output,target)

# 反向传播
loss.backward()
# 更新参数
optimizer.step()

一个简单神经网络实例

主要步骤如下:

  • (1)使用torchvision加载并预处理CIFAR-10数据集
  • (2)定义网络
  • (3)定义损失函数和优化器
  • (4)训练网络并更新网络参数
  • (5)测试网络

使用的数据集是CIFAR-10,这是一个常用的彩色图片数据集,他有10个类别:airplane、automobile、bird、cat、deer、dog、frog、horse、ship和truck。每张图片都是3×32×32,即3通道,分辨率为32×32.

Dataset对象是一个数据集,可以按下标访问,返回形如(data,label)的数据。Dataloader是一个可迭代的对象,它将dataset返回的每一条数据样本拼接成一个batch,并提供多线程加速优化和数据打乱等操作。当程序对dataset的所有数据遍历完一遍之后,对Dataloader也完成了一次迭代。

原文链接:https://blog.csdn.net/haha0825/article/details/102851998

import torchvision as tv
import torchvision.transforms as transforms
from torchvision.transforms import ToPILImage

import torch.nn as nn
import torch as t
import torch.nn.functional as F
import torch.optim as optim


# 定义对数据的预处理
transform = transforms.Compose([
    transforms.ToTensor(),      # 转为Tensor
    transforms.Normalize((0.5,0.5,0.5),(0.5,0.5,0.5))   # 归一化
        ])
# 训练集
trainset = tv.datasets.CIFAR10(
    root='I:/pycharm-project/DL_code-pytorch/data/',
    train=True,
    download=False,
    transform=transform)

trainloader = t.utils.data.DataLoader(
    trainset,
    batch_size=4,
    shuffle=True,
    num_workers=0)

# 测试集
testset = tv.datasets.CIFAR10(
    'I:/pycharm-project/DL_code-pytorch/data/',
    train=False,
    download=False,
    transform=transform)
testloader = t.utils.data.DataLoader(
    testset,
    batch_size=4,
    shuffle=False,
    num_workers=0)

classes = ('plane','car','bird','cat','deer','dog',
           'frog','horse','ship','truck')
           
# 定义网络
class Net(nn.Module):
    def __init__(self):
        # nn.Module子类的函数必须在构造函数中执行父类的构造函数
        # 下式等价于nn.Module.__init__(self)
        super(Net,self).__init__()

        # 卷积层
        # 卷积层’1‘表示输入图片为单通道,’6‘表示输出通道数,’5‘表示卷积核为5*5
        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,10)

    def forward(self, x):
        # 卷积-->激活-->池化
        x = F.max_pool2d(F.relu(self.conv1(x)),(2,2))
        x = F.max_pool2d(F.relu(self.conv2(x)),2)

        # reshape,’-1‘表示自适应
        x = x.view(x.size()[0],-1)
        x = F.relu(self.fc1(x))
        x = F.relu(self.fc2(x))
        x = self.fc3(x)
        return x

net = Net()

# 损失函数和优化器
criterion = nn.CrossEntropyLoss()
optimizer = optim.SGD(net.parameters(),lr=0.001,momentum=0.9)

# 训练网络
optimizer.zero_grad() # 梯度清零
for epoch in range(2):
    running_loss = 0.0
    for i,data in enumerate(trainloader,start=0):
        # 输入数据
        inputs,labels = data
        if t.cuda.is_available():
            inputs,labels = inputs.cuda(), labels.cuda()
        
        # 前向传播和反向传播
        outputs = net(inputs)
        loss = criterion(outputs,labels)
        loss.backward()
        
        # 更新参数/梯度
        optimizer.step()
        optimizer.zero_grad() # 梯度清零(这样两次梯度清零 方便使用 梯度累计,即多个steps后才进行optimizer.step())
        
        # 打印log信息
        running_loss += loss.item()
        if i%2000 ==1999:
            print('[%d,%5d] loss: %.3f'%(epoch+1,i+1,running_loss/2000))
            running_loss = 0.0
    # 这里 是记录了局部(2000step)的avg损失,也可以在一次epoch后 计算一次迭代dataset的loss均值
    
    # 还可以结合使用schedule进一步控制学习率的衰减
    print('Finished Training')

# 预测
correct = 0
total = 0
for data in testloader:
    images,labels = data
    outputs = net(images)
    _,predicted = t.max(outputs.data,1)
    total += labels.size(0)
    correct += (predicted == labels).sum()

    print('10000张测试集中的准确率为:%d %%' % (100*correct/total))

Tensor

创建Tensor

tensor的接口设计的与numpy类似,以便用户使用。

从接口的角度讲,对tensor的操作可分为两类:

  • (1)torch.function,如torch.save等
  • (2)torch.function,如tensor.view等

为方便使用,对tensor的大部分操作同时支持这两类接口。

从存储的角度讲,对tensor的操作可分为两类:

  • (1)不会修改自身的数据,如a.add(b),加法的结果会返回一个新的tensor。
  • (2)会修改自身的数据,如a.add_(b),加法的结果仍存储在a中,a被修改了。

函数名以_结尾的都是inplace方式,即会修改调用者自己的数据,在实际应用中需加以区分。

在Pytorch中新建tensor的常用方法:

函数功能
Tensor(*sizes)基础构造函数
ones(*sizes)全1Tensor
zeors(*sizes)全0Tensor
eye(*sizes)对角线为1,其他为0
arange(s, e, step)从s到e,步长为step
linspace(s, e, steps)从s到e, 均匀切分成setps份
rand/randn(*sizes)均匀/标准分布
normal(mean, std)/uniform(from, to)正太分布/均匀分布
randperm(m)随机跑列

使用Tensor函数新建tensor是最复杂多变的方式

import torch as t
# 指定tensor的形状
a = t.Tensor(2,3)
print(a)  # a的数值取决于内存空间的状态


# 用list的数据创建tensor
b = t.Tensor([[1,2,3],[4,5,6]])
print(b)
 
# 把tensor转为list
c = b.tolist() # 如果是单变量的张量(即标量 )取值 可以使用 .item() 方法
print(c)

d = t.Tensor((2,3))   # 创建一个元素为2和3的tensor
print(d)

tensor([[9.8091e-45, 0.0000e+00, 0.0000e+00],
        [0.0000e+00, 0.0000e+00, 0.0000e+00]])
tensor([[1., 2., 3.],
        [4., 5., 6.]])
[[1.0, 2.0, 3.0], [4.0, 5.0, 6.0]]
tensor([2., 3.])
b.size(), b.numel() # 还可以利用tensor.shape直接查看tensor的形状
(torch.Size([2, 3]), 6)
print(t.ones(2,3))         # 全1
print(t.zeros(2,3))        # 全0
print(t.arange(1,6,2))     # 从1到6,步长为2
print(t.randn(2,3))        # 标准分布
print(t.linspace(2,10,3))  # 从2到10,均匀切分成3份
print(t.randperm(5))       # 随机排列
print(t.eye(2,3))          # 对角线为1,其他为0,不要求行数和列数相等
tensor([[1., 1., 1.],
        [1., 1., 1.]])
tensor([[0., 0., 0.],
        [0., 0., 0.]])
tensor([1, 3, 5])
tensor([[ 0.2527,  0.3769, -1.4167],
        [ 0.4913,  0.9340,  2.2304]])
tensor([ 2.,  6., 10.])
tensor([3, 1, 0, 2, 4])
tensor([[1., 0., 0.],
        [0., 1., 0.]])

操作Tensor

维度扩展:

a = t.arange(0,6)

a.view(2,3)   # 调整a的形状
a.view(-1,3)  # 当某一维为-1时,会自动计算它的大小
a.unsqueeze(1)  # 在第一维上增加‘1’
a.unsqueeze(-2) # -2表示倒数第二个维度
a.squeeze(0)  # 压缩第0维的‘1’
a.squeeze()   # 压缩所有维度为‘1’的

tensor([0, 1, 2, 3, 4, 5])

resize是另一种可用来调整size的方法,但与view不同,他可以修改tensor的尺寸。如果新尺寸超过了原尺寸,会自动分配新的内存空间,而如果新尺寸小于原尺寸,则之前的数据依旧会被保存。

Tensor支持与numpy.ndarray类似的索引操作,语法上也类似。一般情况下索引出来的结果与原tensor共享内存,即修改一个,另一个也会跟着修改。

索引取值/设值:

gather是一个比较复杂的操作:

  • gather(input, dim, index) 根据index,在dim维度上选取数据,输出的size与index一样;
a = t.arange(0,16).view(4,4)
print(a)
# 选取对角线的元素
index1 = t.LongTensor([[0,1,2,3]])
print(a.gather(0,index1))
# 选取反对角线上的元素
index2 = t.LongTensor([[3,2,1,0]]).t()
print(a.gather(1,index2))
# 选取反对角线上的元素,与上面的不同
index3 = t.LongTensor([[3,2,1,0]])
print(a.gather(0,index3))
# 选取两个对角线上的元素
index4 = t.LongTensor([[0,1,2,3],[3,2,1,0]]).t()
print(a.gather(1,index4))
tensor([[ 0,  1,  2,  3],
        [ 4,  5,  6,  7],
        [ 8,  9, 10, 11],
        [12, 13, 14, 15]])
tensor([[ 0,  5, 10, 15]])
tensor([[ 3],
        [ 6],
        [ 9],
        [12]])
tensor([[12,  9,  6,  3]])
tensor([[ 0,  3],
        [ 5,  6],
        [10,  9],
        [15, 12]])

与gather相对应的逆操作是scatter_,gather把数据从input中按index取出,而scatter_是把取出的数据再放回去。注意scatter_函数是inplace操作。

# 把两个对角线元素放回到指定位置
a = t.arange(0,16).view(4,4).float()
index = t.LongTensor([[0,1,2,3],[3,2,1,0]]).t()
b = a.gather(1,index)
c = t.zeros(4,4)
d = c.scatter_(1,index,b)
print(d)
tensor([[ 0.,  0.,  0.,  3.],
        [ 0.,  5.,  6.,  0.],
        [ 0.,  9., 10.,  0.],
        [12.,  0.,  0., 15.]])

数据类型:

  • Tensor有很多数据类型,每个类型分别对应CPU和GPU版本(HalfTensor除外):
数据类型CPU TGPU T
32bit floattorch.FloatTensortorch.cuda.FloatTensor
64bit floattorch.DoubleTensortorch.cuda.DoubleTensor
16bit floattorch.cuda.HalfTensor
8bit UInt(0~255)torch.ByteTensortorch.cuda.ByteTensor
16bit Inttorch.ShortTensortorch.cuda.ShortTensor
32bit Inttorch.IntTensortorch.cuda.IntTensor
64bit Inttorch.LongTensortorch.cuda.LongTensor

默认的tensor是FloatTensor类型的;HalfTensor是专门为GPU设计的,同样的元素个数显存占用仅为float的一半,极大缓解GPU显存不足的问题;

注意HalfTensor的精度有限,有可能溢出;

类型转换:

  • 各类型之间可以相互转换,一般使用type(new_type)的方式,同时还有floatlonghalf等快捷方式。CPU tensor与GPU tensor之间的转换通过tensor.cudatensor.cpu的方式。Tensor还有一个new方法,用法与t.Tensor一样,会调用该tensor对应类型的构造函数,生成与当前tensor类型一致的tensor。
d.type(torch.IntTensor)
tensor([[ 0,  0,  0,  3],
        [ 0,  5,  6,  0],
        [ 0,  9, 10,  0],
        [12,  0,  0, 15]], dtype=torch.int32)
test = t.Tensor(np.arange(6).reshape((2,3)))
print(test)
test.long(),test.half(), test.int(), test.double()
tensor([[0., 1., 2.],
        [3., 4., 5.]])





(tensor([[0, 1, 2],
         [3, 4, 5]]), tensor([[0., 1., 2.],
         [3., 4., 5.]], dtype=torch.float16), tensor([[0, 1, 2],
         [3, 4, 5]], dtype=torch.int32), tensor([[0., 1., 2.],
         [3., 4., 5.]], dtype=torch.float64))

逐元素操作函数:

函数功能
abs/sqrt/dic/exp/fmod/log/pow绝对值/平方根/除法/指数/求余/求对/求幂
cos/sim/asin/atan2/cosh三角函数
ceil/round/floor/trunc上取整/四舍五入/下取整/只保留整数部分
clamp(input, min, max)超过min和max部分截断 注意是超过
sigmod/tanh…激活函数

对于很多操作,例如div、mul、pow、fmod等,PyTorch都实现了运算符重载,所以可以直接使用运算符。例如:a**2 等价于 torch.pow(a,2)。

归并操作:

  • 此类操作会使输出形状小于输入形状,并可以沿着某一维度进行指定操作。如加法sum,既可以计算整个tensor的和,也可以计算tensor中每一行或每一列的和
函数功能
mean/sum/median/mode均值/和/中位数/众数
norm/dist范数/距离
std/var标准差/方差
cumsum/cumprod累加/累乘

以上大多数函数都有一个参数dim,用来指定这些操作是哪个维度上执行的。假如输入形状是(m,n,k),并指定dim=0,输出形状会是(1,n,k)(n,k),是否有1取决于 keepdim=True与否;

比较操作:

函数功能
gt/lt/ge/le/eq/ne>/</>=/<=/==/!= 已实现运算符重载,返回结果是一个ByteTensor,可用来选取元素
topk最大的k个数
sort排序
max/min比较两个tensor的最大值和最小值,或取单个tensor的最大值(可以指定维度);与一个数的比较可以用clamp;
a = t.linspace(0,15,6).view(2,3)
b = t.linspace(15,0,6).view(2,3)
print(a)
print(b)
print(a>b) 
print(a[a>b])  # a中大于b的元素
print(t.max(b,dim=1))
print(t.max(a,b))
tensor([[ 0.,  3.,  6.],
        [ 9., 12., 15.]])
tensor([[15., 12.,  9.],
        [ 6.,  3.,  0.]])
tensor([[False, False, False],
        [ True,  True,  True]])
tensor([ 9., 12., 15.])
torch.return_types.max(
values=tensor([15.,  6.]),
indices=tensor([0, 0]))
tensor([[15., 12.,  9.],
        [ 9., 12., 15.]])

线性代数:

  • Pytorch的线性函数主要封装了Blas和Lapack,其用法和接口都与之类似
函数功能
trace矩阵的迹(对角线元素之和)
diag对角线元素
triu/tril上三角/下三角阵,可指定偏移量
mm/bmm矩阵登发,batch的矩阵乘法
addmm/addbmm/addmv矩阵运算
t转置
dot/cross内积 /外积
inverse求逆矩阵
svd奇异值分解

沿维度复制扩充张量:

  • unsqueeze或者view:补齐某一维形状
  • expand或者expand_as:重复数组(并不会真的复制数组,不会额外占用空间)

注意 repead 会复制数据,会额外占用空间;

持久化:

  • Tensor的保存和加载十分简单,使用t.save和t.load即可完成相应的功能。在save/load时可指定使用的pickle模块,在load时还可以将GPU tensor映射到CPU或者其他GPU上。
if t.cuda.is_available():
    a = a.cuda(1)   # 把a转为GPU1上的tensor
    t.save(a,'a.pth')
    b = t.load('a.pth')  # 加载为b,存储于GPU1上(因为保存时tensor就在GPU1上)
    c = t.load('a.pth',map_location = lambda storage,loc:storage)  # 加载为c,存储于CPU
    d = t.load('a.pth',map_location = {'cuda:1':'cuda:0'})    # 加载为d,存储于GPU0上

向量化:

  • 向量化计算是一种特殊的并行计算方式,一般程序在同一时间只执行一个操作的方式,它可在同一时间执行多个操作,通常是对不同的数据执行同样的一个或一批指令,或者说把指令应用于一个数组/向量上。向量化可极大地提高科学计算的效率。所以在科学计算程序中尽量避免使用Python原生的for循环,尽量使用向量化的数值计算。
def for_loop_add(x,y):
    result = []
    for i,j in zip(x,y):
        result.append(i+j)
    return t.Tensor(result)
x = t.zeros(100)
y = t.ones(100)
%timeit -n 1000 for_loop_add(x,y)
%timeit -n 1000 x+y
540 µs ± 14.7 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)
2.66 µs ± 273 ns per loop (mean ± std. dev. of 7 runs, 1000 loops each)

其他:

  • t.function都有一个参数out,这时产生的结果将保存在out指定的tensor之中。
  • t.set_printoptions可以用来设置打印tensor时的数值精度和格式。
a = t.randn(2,3)
print(a)
t.set_printoptions(precision=1)
print(a)
tensor([[ 0.4801019132,  0.6366290450,  0.0691231713],
        [-1.8064947128,  1.3155564070, -0.1140790433]])
tensor([[ 0.5,  0.6,  0.1],
        [-1.8,  1.3, -0.1]])

SOTA

SOTA也就是state-of-the-art,若某篇论文能够称为SOTA,就表明其提出的算法(模型)的性能在当前是最优的。

统计机器学习中的监督学习

统计机器学习:从数据出发,提取数据的特征,抽象出数据的模型,发现数据中的知识,又回到对数据的分析与预测中去。

统计学习关于数据的基本假设是同类数据具有一定的统计规律。同类数据就是有某种共同性质的数据,因为只有相同性质,才具有统计规律,才可以用概率统计的方法来进行处理。一般情况下,用随机变量描述数据中的特征,用概率分布描述数据的统计规律。

  • 统计学习最终为了让计算机更加智能化,或者说使计算机某些性能得到提高。
  • 统计学习包括监督学习、非监督学习、半监督学习和强化学习。
  • 统计学习以方法为中心,学习方法三要素为:模型+策略+算法。

其中监督学习:

  • 任务是学习一个模型,使模型能够对任意给定的输入,对其相应的输出做一个好的预测。

在监督学习中,所有输入(输出)可能的取值的集合称为输入(输出)空间。输入与输出空间可以是有限元素的集合,也可以是整个欧氏空间(欧氏空间解释)。输入与输出空间既可以是同一个空间,也可以是不同的空间;通常输出空间远远小于输入空间。

输入输出变量可以是连续的也可以是不连续的:

  • 两者均为连续变量的问题称为回归问题
  • 输出变量为有限个离散变量的问题称为分类问题
  • 两者均为变量序列的问题称为标注问题

监督学习假设输入与输出的随机变量X和Y遵循联合概率分布P(X,Y),训练数据与测试数据被看做是依联合概率分布P(X,Y)独立同分布产生的。在学习过程中,我们假设P(X,Y)存在,实际上它是未知的。
输入空间到输出空间的映射的集合称为假设空间。它的确定意味着学习范围的确定。
监督学习的模型可以是概率模型或非概率模型,对具体输入进行相应的预测时,记作P(y|x)或y = f(x).

在学习过程中,学习系统利用给定的训练数据集,通过学习(训练)得到一个模型,用决策函数Y=f(X)或者条件概率分布P(Y|X)表示。学习系统通过不断地学习,训练,最后选取一个最好的模型。

一直都使用这种方式在实践和解决问题;

余弦相似度计算

余弦相似度:

  • 余弦相似度用向量空间中两个向量夹角的余弦值作为衡量两个个体间差异的大小。相比距离度量,余弦相似度更加注重两个向量在方向上的差异,而非距离或长度上。

余弦距离:

  • 余弦距离定义为1.0减去余弦相似度

欧氏距离:

  • 欧氏距离是最常见的距离度量,衡量的是多维空间中各个点之间的绝对距离

(1, 768)的两个特征向量,计算其相似度:

from sklearn.metrics.pairwise import cosine_similarity, cosine_distances, euclidean_distances

test1 = t.Tensor(np.arange(6)).unsqueeze(0).tolist()
test2 = t.Tensor(np.arange(6,12)).unsqueeze(0).tolist()
                
cosine_similarity(test1,test2), cosine_distances(test1,test2), euclidean_distances(test1,test2)
(array([[0.92065812]]), array([[0.07934188]]), array([[14.69693846]]))

范数和范数归一化

范数是一个标量,它是对向量(或者矩阵)的度量:

  • 0范数,表示向量中非零元素的个数。
  • 1范数,表示向量中各个元素绝对值之和。
  • 2范数,表示向量中各个元素平方和 的 1/2 次方,L2 范数又称 Euclidean 范数或者 Frobenius 范数。
  • p范数,表示向量中各个元素绝对值 p 次方和 的 1/p 次方。

np.linalg.norm

  • ord,表示范数的种类,默认为2 范数。ord = np.inf 表示无穷范数
    • ord=None,表示求整体的矩阵元素平方和,再开根号
    • ord=1,表示求列和的最大值
    • ord=2,|λE-ATA|=0,求特征值,然后求最大特征值的算术平方根
    • ord为无穷大,表示求行和的最大值
  • axis
    • axis=0 表示按列向量来进行处理,求多个列向量的范数;
    • axis =1 表示按行向量来进行处理,求多个行向量的范数
  • keepdims:表示是否保持矩阵的二位特性,True表示保持,False表示不保持,默认为False

经L2范数归一化处理后的向量 计算的余弦相似度的值 与直接使用向量计算的值是一样的,因此实际作为特征向量的值往往是实现已做好L2范数归一化处理后的值;


def norm(X):
    """
        X shape = (1, N)
        L2范数归一化处理
    """
    return X / np.linalg.norm(X, axis=1, keepdims=True)

def o_dist(vector1,vector2):    
    """
        vector shape = (N,)
    """
    dist_ap = np.linalg.norm(vector1 - vector2) 
    return dist_ap

def cos_sim(vector1, vector2):
    """
        vector shape = (N,)
    """
    cos_sim = vector1.dot(vector2) / (np.linalg.norm(vector1) * np.linalg.norm(vector2))
    return cos_sim

test1 = norm(np.array(test1))[0]
test2 = norm(np.array(test2))[0] 

od = o_dist(test1,test2) # 欧式距离
cs = cos_sim(test1,test2) # 余弦相似度
sim = 0.5 + 0.5 * cs # 
cs, sim, od

(0.9206581171508078, 0.9603290585754038, 0.39835130939710073)

梯度累计的一种写法(兼容DDP)

from contextlib import nullcontext # py > 3.7
import torch.nn.parallel.DistributedDataParallel as DDP
    
    
# 与单GPU不同的地方:rain_sampler.set_epoch(epoch),这行代码会在每次迭代的时候获得一个不同的生成器,
# 每一轮开始迭代获取数据之前设置随机种子,通过改变传进的epoch参数改变打乱数据顺序。通过设置不同的随机种子,
# 可以让不同GPU每轮拿到的数据不同
    
if local_rank != -1:
    model = DDP(model, device_ids=[args.gpu])
    
# K 这里指累计的epoch数
optimizer.zero_grad()
for i, (data, label) in enumerate(dataloader):
    my_context = model.no_sync if local_rank != -1 and i % K != 0 else nullcontext
    
    with my_context():
        prediction = model(data)
        loss = loss_fn(prediction, label) / K
        loss.backward() # 梯度累计 
    if i % K == 0:
        optimizer.step()
        optimizer.zero_grad()
        

关于冻结参数

冻结除全连接层外的其他参数

from torch import nn, optim
model = torchvision.models.resnet18(pretrained=True)
for param in model.parameters():
    param.requires_grad = False
    
# 替换掉fc层 这样除了fc层以外的所有参数多会被冻结      
model.fc = nn.Linear(512, 10)   
# 计算梯度的唯一参数就是fc的权重和偏差
# optimizer = optim.SGD(model.fc.parameters(), lr=1e-2, momentum=0.9)
optimizer = optim.SGD(model.parameters(), lr=1e-2, momentum=0.9)

冻结除全连接层外的其他参数(区别于DDP):

  • 如果需要冻结模型权重,和单GPU基本没有差别
  • 如果不需要冻结权重,可以选择是否同步BN层。然后再把模型包装成DDP模型,就可以方便进程之间的通信了
    # 是否冻结权重
    if args.freeze_layers:
        for name, para in model.named_parameters():
            # 除最后的全连接层外,其他权重全部冻结
            if "fc" not in name:
                para.requires_grad_(False)
    else:
        # 只有训练带有BN结构的网络时使用SyncBatchNorm采用意义
        if args.syncBN:
            # 使用SyncBatchNorm后训练会更耗时
            model = torch.nn.SyncBatchNorm.convert_sync_batchnorm(model).to(device)
        
    # 转为DDP模型
    model = torch.nn.parallel.DistributedDataParallel(model, device_ids=[args.gpu])
    # optimizer使用SGD+余弦淬火策略
    pg = [p for p in model.parameters() if p.requires_grad]
    optimizer = optim.SGD(pg, lr=args.lr, momentum=0.9, weight_decay=0.005)
    lf = lambda x: ((1 + math.cos(x * math.pi / args.epochs)) / 2) * (1 - args.lrf) + args.lrf  # cosine
    scheduler = lr_scheduler.LambdaLR(optimizer, lr_lambda=lf)

关于DDP

我们所使用的方式仍是统计机器学习的方式,模型不会太大,因此这里的DDP就不涉及一块GPU放不下的大模型,仅仅是为了使用多块GPU并行计算以加速训练,即数据并行;

模型放在一块GPU,再复制到每一块GPU,同时训练(注意训练速度并不是倍增的,并行越多设备通讯会越复杂);

  • DataParallel是单进程多线程的,仅仅能工作在单机中。而DistributedDataParallel是多进程的,可以工作在单机或多机器中。
model = torch.nn.DataParallel(model)
optimizer = torch.optim.Adam(
            model.module.parameters(), lr=lr, weight_decay=opt.weight_decay)
model.module.save(opt.notes)
  • DataParallel通常会慢于DistributedDataParallel。所以目前主流的方法是DistributedDataParallel。

具体实现参考《如何使用torchrun启动单机多卡DDP并行训练》具体使用尚存疑,后续开发详细查阅后搞清楚

BN为何有用

  • (1)使特征值处于相似的区间。
  • (2)使权重比网络更滞后,更深层。(eg:第10层比第1层更能经受住变化)
    BN保证了无论前面参数如何变化,而后面的均值和方差不变,从而减少了隐藏分布的变化的数量。
    减弱了前层参数作用与后层参数作用之间的联系。使网络每层都可以自己学习,有一些独立性。
    前层不会左右移动的太多,因为被一样的均值和方差限制,这会使后层的学习工作更容易。
  • (3)其他:每个Mini-batch通过均值和方差缩放;在每个隐藏单元的激活上增加了一些噪声;有轻微的正则化作用。

学习率衰减

加快学习算法的一个方法:随时间慢慢减少学习率。若α为固定值,则会在最优点附近摆动,不会精确地收敛。在刚开始时能承受较大的步长,但在快收敛时则需小步长。

  • 20
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值