赞
踩
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主要包含三个属性:
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()
主要步骤如下:
使用的数据集是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的接口设计的与numpy类似,以便用户使用。
从接口的角度讲,对tensor的操作可分为两类:
为方便使用,对tensor的大部分操作同时支持这两类接口。
从存储的角度讲,对tensor的操作可分为两类:
函数名以_结尾的都是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.]])
维度扩展:
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.]])
数据类型:
数据类型 | CPU T | GPU T |
---|---|---|
32bit float | torch.FloatTensor | torch.cuda.FloatTensor |
64bit float | torch.DoubleTensor | torch.cuda.DoubleTensor |
16bit float | — | torch.cuda.HalfTensor |
8bit UInt(0~255) | torch.ByteTensor | torch.cuda.ByteTensor |
16bit Int | torch.ShortTensor | torch.cuda.ShortTensor |
32bit Int | torch.IntTensor | torch.cuda.IntTensor |
64bit Int | torch.LongTensor | torch.cuda.LongTensor |
默认的tensor是FloatTensor类型的;HalfTensor是专门为GPU设计的,同样的元素个数显存占用仅为float的一半,极大缓解GPU显存不足的问题;
注意HalfTensor的精度有限,有可能溢出;
类型转换:
type(new_type)
的方式,同时还有float
、long
、half
等快捷方式。CPU tensor与GPU tensor之间的转换通过tensor.cuda
和tensor.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)。
归并操作:
函数 | 功能 |
---|---|
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.]])
线性代数:
函数 | 功能 |
---|---|
trace | 矩阵的迹(对角线元素之和) |
diag | 对角线元素 |
triu/tril | 上三角/下三角阵,可指定偏移量 |
mm/bmm | 矩阵登发,batch的矩阵乘法 |
addmm/addbmm/addmv | 矩阵运算 |
t | 转置 |
dot/cross | 内积 /外积 |
inverse | 求逆矩阵 |
svd | 奇异值分解 |
沿维度复制扩充张量:
注意 repead 会复制数据,会额外占用空间;
持久化:
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上
向量化:
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)
其他:
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也就是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, 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]]))
范数是一个标量,它是对向量(或者矩阵)的度量:
np.linalg.norm
经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)
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):
# 是否冻结权重
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就不涉及一块GPU放不下的大模型,仅仅是为了使用多块GPU并行计算以加速训练,即数据并行;
模型放在一块GPU,再复制到每一块GPU,同时训练(注意训练速度并不是倍增的,并行越多设备通讯会越复杂);
model = torch.nn.DataParallel(model)
optimizer = torch.optim.Adam(
model.module.parameters(), lr=lr, weight_decay=opt.weight_decay)
model.module.save(opt.notes)
具体实现参考《如何使用torchrun启动单机多卡DDP并行训练》 ,具体使用尚存疑,后续开发详细查阅后搞清楚
;
加快学习算法的一个方法:随时间慢慢减少学习率。若α为固定值,则会在最优点附近摆动,不会精确地收敛。在刚开始时能承受较大的步长,但在快收敛时则需小步长。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。