dive into deep learning 笔记(pytorch版本)(一)

前言

只记录感兴趣的东西,详细参考如下。

参考:
http://tangshusen.me/Dive-into-DL-PyTorch/#/
《dive into deep learning》
pytorch文档:https://pytorch.org/docs/stable/torch.html

算数操作

pytorch算数运算+、-、*、/,除了最常见的表示方法,还有in-place(原地)形式

y.add_(x)
# 注:PyTorch操作in-place版本都有后缀_, 例如x.copy_(y), x.t_(), x.cos_

除此之外,但有torch.add(x, y)虽然保存输出,但是可以添加out参数保存输出torch.add(x, y, out=result)

索引

之前在python中的numpy包提到过,如果使用数组子集选择(数组切片和数组下标索引,都是一回事)都是原数组的视图,所有的变化会返回到原数组上。

x = np.arange(24).reshape((2, 3, 4))
# 数组下标索引
a = x[0]
b = x[0]
a[:] = 12
print(x, a, b)	# a[12,1,2,3] b[12,1,2,3],x也变化
# 数组的切片
c = x[0:1]
c[:1] = 13
print(x, a, b, c)	# 都变了

上述这种赋值,相当于开辟一个内存空间a存放着指向x的地址,a只是一个指向,没有实际自己的数据空间。
Tensor和numpy一样,索引出来的结果和原数据共内存,一边改,另一边也会跟着改。

x = torch.rand(5, 3)
y = x[0, :]
y += 1
print(y)
print(x[0, :]) # 源tensor也被改了

还有view()也会共享内存,view()是用来改变Tensor形状的,但其实只是改变观察角度,内部数据并没有变化。

y = x.view(15)
z = x.view(-1, 5)  # -1所指的维度可以根据其他维度的值推出来
print(x.size(), y.size(), z.size())
x += 1
print(x)
print(y) # 也加了1

如果不希望是视图,而是副本,可以使用clone(),或者先开辟空间再赋值

x_cp = x.clone().view(15)
# 使用clone还有一个好处是会被记录在计算图中,即梯度回传到副本时也会传到源Tensor

选择函数

index_select(input, dim, index)
# 在指定维度dim上选取,比如选取某些行、某些列

Numpy、PIL、pytorch常用的类型转换

# PIL图片<->tensor
transforms.ToTensor()
transforms.ToPILImage()
# tensor<->numpy
x.numpy()	# x开始是tensor,内存共享
torch.from_numpy(x)	# x开始是array,内存共享
torch.tensor(x)	# x开始是array,内存不共享
# PIL图片<->numpy
numpy.array(x)	# x开始是pil图片
Image.fromarray(x.astype('uint8')).convert('RGB')	# x开始是array

补充一点,
PIL读取图片是(H x W x C)数据在[0, 255];
Numpy数组最好是np.unit8;
tensor是(C x H x W)数据在[0.0, 1.0]。

广播机制

如果两个形状不同的tensor按照元素运算,会自己复制自己扩充到同样的大小,再做运算。

x = torch.arange(1, 3).view(1, 2)
print(x)
y = torch.arange(1, 4).view(3, 1)
print(y)
print(x + y)

自动求梯度-解释

自动求梯度看这里
这里解释一下为什么y.backward()时,如果y是标量,则不需要为backward()传入任何参数;否则,需要传入一个与y同形的Tensor。
因为是为了避免向量对张量求导,转为标量对张量求导。为啥?因为向量对张量求导太复杂了。如果把向量的每一维的乘以权重再相加成标量,那相当于是把向量变成标量,对标量求backward()

x = torch.tensor([1.0, 2.0, 3.0, 4.0], requires_grad=True)
y = 2 * x
z = y.view(2, 2)
print(z)
v = torch.tensor([[1.0, 0.1], [0.01, 0.001]], dtype=torch.float)
z.backward(v)
print(x.grad)

参考:http://tangshusen.me/Dive-into-DL-PyTorch/#/chapter02_prerequisite/2.3_autograd

tensor自动求梯度中的叶子节点

在pytorch的运算过程中会建立一个计算过程图像。也就是说Tensor和Function互相结合就可以构建一个记录有整个计算过程的有向无环图(DAG)
在反向传播的时候我们就需要这个图的帮助,图的一切都是为了反向求导。在这个图里面我们只会记录数据(tensor)和可求导运算(grad_fn),而tensor可细分为两类:叶子节点(leaf node)和非叶子节点。
叶子节点对应的grad_fn是None。
在这里插入图片描述

a = torch.tensor([1.0, 2.0])    
# 默认requires_grad=false
b = a+1
print(b.requires_grad)
print(b.is_leaf)	# false ture

a = torch.tensor([1.0, 2.0], requires_grad=True)     
a.data += 1
print(a.is_leaf)	# ture
# 我们可以改写模型参数同时不影响梯度,因为叶子结点的grad_fn为none。

detach()

在这里插入图片描述

线性回归的原始版本

读取数据的方式

带yield的函数是一个生成器,有yield的那一条语句是一个return,它也可以看作一个位置标记,当每次用生成器的时候到yield(yield那句执行完成)会停止。 可以使用next()方法接着执行,也可以把生成器放入for循环当作可迭代对象。

import random
import numpy as np
import torch

# 本函数已保存在d2lzh包中方便以后使用
def data_iter(batch_size, features, labels):
    num_examples = len(features)
    indices = list(range(num_examples))
    random.shuffle(indices)  # 样本的读取顺序是随机的
    for i in range(0, num_examples, batch_size):
        j = torch.LongTensor(indices[i: min(i + batch_size, num_examples)])  # 最后一次可能不足一个batch
        yield features.index_select(0, j), labels.index_select(0, j)
        
if __name__ == '__main__':

    batch_size = 10
    # 生成数据
    num_inputs = 2
    num_examples = 100
    true_w = [2, -3.4]
    true_b = 4.2
    features = torch.randn(num_examples, num_inputs, dtype=torch.float32)
    labels = true_w[0] * features[:, 0] + true_w[1] * features[:, 1] + true_b
    labels += torch.tensor(np.random.normal(0, 0.01, size=labels.size()), dtype=torch.float32)
    # 读取数据
    # next 方法一步一步用
    g = data_iter(batch_size, features, labels)
    print(next(g))
	# 可迭代对象使用
    for X, y in data_iter(batch_size, features, labels):
        print(X, y)
训练流程

上面已经生成数据啦,训练吧。模型+策略+算法

# 定义模型
def linreg(X, w, b):  # 本函数已保存在d2lzh_pytorch包中方便以后使用
    return torch.mm(X, w) + b
# 定义模型参数
w = torch.tensor(np.random.normal(0, 0.01, (num_inputs, 1)), dtype=torch.float32)
b = torch.zeros(1, dtype=torch.float32)
w.requires_grad_(requires_grad=True)
b.requires_grad_(requires_grad=True) 
# 定义损失函数
def squared_loss(y_hat, y):  # 本函数已保存在d2lzh_pytorch包中方便以后使用
    # 注意这里返回的是向量, 另外, pytorch里的MSELoss并没有除以 2
    return (y_hat - y.view(y_hat.size())) ** 2 / 2
# 定义优化算法
def sgd(params, lr, batch_size):  # 本函数已保存在d2lzh_pytorch包中方便以后使用
    for param in params:
        param.data -= lr * param.grad / batch_size # 注意这里更改param时用的param.data
# 开始训练
lr = 0.03
num_epochs = 3
net = linreg
loss = squared_loss

for epoch in range(num_epochs):  # 训练模型一共需要num_epochs个迭代周期
    # 在每一个迭代周期中,会使用训练数据集中所有样本一次(假设样本数能够被批量大小整除)。X
    # 和y分别是小批量样本的特征和标签
    for X, y in data_iter(batch_size, features, labels):
        l = loss(net(X, w, b), y).sum()  # l是有关小批量X和y的损失
        l.backward()  # 小批量的损失对模型参数求梯度
        sgd([w, b], lr, batch_size)  # 使用小批量随机梯度下降迭代模型参数

        # 不要忘了梯度清零
        w.grad.data.zero_()
        b.grad.data.zero_()
    train_l = loss(net(features, w, b), labels)
    print('epoch %d, loss %f' % (epoch + 1, train_l.mean().item()))
# 得到具体参数的值
print(true_w, '\n', w)
print(true_b, '\n', b)

线性回归的简洁版本

训练过程

生成数据代码相同,假设还是同样的数据

import torch.utils.data as Data	# 数据处理工具
from torch.nn import init	# 初始化方法
import torch.nn	# 神经网络层
import torch.optim as optim	# 优化方法

# 读取数据
batch_size = 10
# 将训练数据的特征和标签组合
dataset = Data.TensorDataset(features, labels)
# 随机读取小批量,下面data_iter就是一个可迭代对象了
data_iter = Data.DataLoader(dataset, batch_size, shuffle=True)

# 定义网络模型,nn.Linear()封装好了,就是y=wx+b
class LinearNet(nn.Module):
    def __init__(self, n_feature):
        super(LinearNet, self).__init__()
        self.linear = nn.Linear(n_feature, 1)
    # forward 定义前向传播
    def forward(self, x):
        y = self.linear(x)
        return y
        
net = LinearNet(num_inputs) 
# 初始化模型参数
init.normal_(net.linear.weight, mean=0, std=0.01)	# 随机
init.constant_(net.linear.bias, val=0)  # 定值 也可以直接修改bias的data: net[0].bias.data.fill_(0)
# 损失函数
loss = nn.MSELoss()
# 优化方法
optimizer = optim.SGD(net.parameters(), lr=0.03)

# 训练模型
num_epochs = 3
for epoch in range(1, num_epochs + 1):
    for X, y in data_iter:
        output = net(X)
        l = loss(output, y.view(-1, 1))
        optimizer.zero_grad() # 梯度清零,等价于net.zero_grad()
        l.backward()
        optimizer.step()
    print('epoch %d, loss: %f' % (epoch, l.item()))

注意:torch.nn仅支持输入一个batch的样本不支持单个样本输入,如果只有单个样本,可使用input.unsqueeze(0)来添加一维。

其他细节的改变写法

使用nn.Sequential()搭建网络结构,使用.parameters()查看结构

# 写法一
net = nn.Sequential(
    nn.Linear(num_inputs, 1)
    # 此处还可以传入其他层
    )

# 写法二
net = nn.Sequential()
net.add_module('linear', nn.Linear(num_inputs, 1))
# net.add_module ......

# 写法三
from collections import OrderedDict
net = nn.Sequential(OrderedDict([
          ('linear', nn.Linear(num_inputs, 1))
          # ......
        ]))

print(net)
print(net[0])

用上面方法定义的net,模型参数初始化写法不同。

# net为ModuleList或者Sequential实例才行
init.normal_(net[0].weight, mean=0, std=0.01)
init.constant_(net[0].bias, val=0)  # 也可以直接修改bias的data: net[0].bias.data.fill_(0)

# 查看网络参数
for param in net.parameters():
    print(param)

为不同子网络设置不同的学习率,这在finetune时经常用。

optimizer =optim.SGD([
                # 如果对某个参数不指定学习率,就使用最外层的默认学习率
                {'params': net.subnet1.parameters()}, # lr=0.03
                {'params': net.subnet2.parameters(), 'lr': 0.01}
            ], lr=0.03)

调整学习率

for param_group in optimizer.param_groups:
    param_group['lr'] *= 0.1 # 学习率为之前的0.1倍
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值