Linear-regression

具体原理见我同专栏下的另一篇文章

  • 一些pytorch的函数介绍

    • 基础

      y = torch.tensor([0, 2])
      y_hat = torch.tensor([[0.1, 0.3, 0.6], [0.3, 0.2, 0.5]])
      y_hat[[0, 1], y]
      tensor([0.1000, 0.5000])
      

      相当于获取 (0,0) 和 (1,2)

    • 正态分布

      torch.normal(mean, std, *, generator=None, out=None)
      # 从一个标准正态分布N~(0,1),提取一个2x2的矩阵
      torch.normal(mean=0.,std=1.,size=(2,2))
      # 让每一个值服从不同的正态分布
      torch.normal(mean=torch.arange(1., 11.), std=torch.arange(1, 0, -0.1))
      tensor([  1.0425,   3.5672,   2.7969,   4.2925,   4.7229,   6.2134,8.0505,   8.1408,   9.0563,  10.0566])
      
    • 将list中的内容打乱(修改原值)

      random.shuffle(indices)
      
    • torch的矩阵计算

      # 两个张量对应元素相乘
      y = torch.mul(X,w)# 两个张量矩阵相乘,可以用利用广播机制进行不同维度的相乘操作
      y = torch.matmul(X,w) + b
      
    • yield:python生成器,解决使用序列存储大量数据时内存消耗大的问题,用于生成小批量随机样本时

      https://blog.csdn.net/mieleizhi0522/article/details/82142856/

      for i in range(0,num_examples,batch_size):batch_indices = torch.tensor(indices[i:min(i+batch_size,num_examples)])yield features[batch_indices],labels[batch_indices]
      
    • sum:给定一个矩阵X,我们可以对所有元素求和(默认情况下), 也可以只求同一个轴上的元素。 如果X是一个形状为(2, 3)的张量,我们对列进行求和, 则结果将是一个具有形状(3,)的向量。 调用时可以指定保持在原始张量的轴数,而不折叠求和的维度。

      X = torch.tensor([[1.0, 2.0, 3.0], [4.0, 5.0, 6.0]])
      X.sum(0, keepdim=True), X.sum(1, keepdim=True)(tensor([[5., 7., 9.]]),tensor([[ 6.],[15.]]))
      
  • tensor的梯度下降

    • requires_grad:tensor的属性,表示是否要在计算中保留对应的梯度信息,一连串连续的true的节点形成一条求导通路。所有依赖它的节点的requires_grad都为True。

      # false表示叶子变量,true表示非叶子变量
      w = torch.normal(0,0.01,size=(2,1),requires_grad = True)
      b = torch.zeros(1,requires_grad = True)
      
    • detach:截断反向传播的梯度流,当反向传播经过当前node时,梯度就不再向前传。

      也常用于将原数据拷贝出来进行绘图(不影响原数据)

    • - = 和 = - 的区别

      W1 - = learning_rate * W1.grad:在原地修改,之前申请的内存地址不变,仅数据发生了变化。

      W1 = W1 - learning_rate*W1.grad :新申请一块内存,原有的W1和更新后的W1内存地址不变

      = - 适用于任何情况,- = 只有在grad_fn为None且requires_grad = True的时候不适用。

    • with torch.no_grad():即使tensor x的requires_grad = True,由x得到的新tensor w的requires_grad也为False,且grad_fn也为None,即不会对w求导(默认情况下requires_grad应该也为True)

      计算过程中认为等式右边的tensor的require_grad为False,故即使tensor原本不可以执行 -=操作,这里依旧可以执行,但最终属性还是根据最初的设置判断。

      with torch.no_grad(): # 数据不需要计算梯度,也不会进行反向传播for param in params:param -= lr*param.grad / batch_sizeparam.grad.zero_()
      

      先看下面的代码

      a = torch.ones(2,requires_grad=True)
      b = a*2 # b依赖于a,故requires_grad=True
      print(a, a.grad, a.requires_grad )
      # tensor([1., 1.], requires_grad=True) None True
      print(b, b.grad, b.requires_grad )
      # tensor([2., 2.], grad_fn=<MulBackward0>) None Trueb.sum().backward(retain_graph = True) # 回传
      print(a, a.grad, a.requires_grad )
      #tensor([1., 1.], requires_grad=True) tensor([2., 2.]) Truewith torch.no_grad():a = a + a.grad #生成了一个新变量,no_grad模式下无法求梯度print(a, a.grad, a.requires_grad )# a.grad.zero_()
      # tensor([3., 3.]) None Falseb.sum().backward(retain_graph = True )
      print(a, a.grad ,a.requires_grad )
      # tensor([3., 3.]) None Falsewith torch.no_grad():a += a.grad # 可行!逃避了autograd的检测print(a, a.grad, a.requires_grad)# tensor([3., 3.], requires_grad=True) tensor([2., 2.]) Truea.grad.zero_()# tensor([3., 3.], requires_grad=True) tensor([0., 0.]) Trueb.sum().backward(retain_graph = True )
      print(a, a.grad ,a.requires_grad )
      # tensor([3., 3.], requires_grad=True) tensor([2., 2.]) True
      # 没加zero:tensor([3., 3.], requires_grad=True) tensor([4., 4.]) True
      

      原本我们希望第二次反向传播时,最后a的输出不包含上一次的梯度(即全新的一次根据loss的求导)但是这里梯度却会被累加。假定我在做一个梯度的更新操作,这个梯度累计越来越大,更新的步长越来越大,loss直接跑飞。所以得加一个梯度清零的操作。

  • TensorDataset和DataLoader

    • TensorDataset:用来对 tensor 进行打包(类似zip)该类通过每一个 tensor 的第一个维度进行索引,因此该类中的 tensor 第一维度必须相等。
    • DataLoader:用来包装所使用的数据,每次抛出一批数据
    import torch
    from torch.utils.data import TensorDataset
    from torch.utils.data import DataLoadera = torch.tensor([[1, 2, 3], [4, 5, 6], [7, 8, 9], [1, 2, 3], [4, 5, 6], [7, 8, 9], [1, 2, 3], [4, 5, 6], [7, 8, 9], [1, 2, 3], [4, 5, 6], [7, 8, 9]])
    b = torch.tensor([44, 55, 66, 44, 55, 66, 44, 55, 66, 44, 55, 66])
    train_ids = TensorDataset(a, b)
    # 循环取数据
    for x_train, y_label in train_ids:print(x_train, y_label)
    '''
    tensor([1, 2, 3]) tensor(44)
    tensor([4, 5, 6]) tensor(55)
    tensor([7, 8, 9]) tensor(66)
    tensor([1, 2, 3]) tensor(44)
    tensor([4, 5, 6]) tensor(55)
    tensor([7, 8, 9]) tensor(66)
    tensor([1, 2, 3]) tensor(44)
    tensor([4, 5, 6]) tensor(55)
    tensor([7, 8, 9]) tensor(66)
    tensor([1, 2, 3]) tensor(44)
    tensor([4, 5, 6]) tensor(55)
    tensor([7, 8, 9]) tensor(66)
    '''# DataLoader进行数据封装,每次从数据库中每次抽出batch size个样本
    train_loader = DataLoader(dataset=train_ids, batch_size=4, shuffle=True)
    for i, data in enumerate(train_loader, 1):  # 注意enumerate返回值有两个,一个是序号,一个是数据(包含训练数据和标签)x_data, label = dataprint(' batch:{0} x_data:{1}  label: {2}'.format(i, x_data, label))
    '''
    batch:1 x_data:tensor([[7, 8, 9],[4, 5, 6],[1, 2, 3],[4, 5, 6]])  label: tensor([66, 55, 44, 55])
    batch:2 x_data:tensor([[1, 2, 3],[7, 8, 9],[1, 2, 3],[4, 5, 6]])  label: tensor([44, 66, 44, 55])
    batch:3 x_data:tensor([[1, 2, 3],[4, 5, 6],[7, 8, 9],[7, 8, 9]])  label: tensor([44, 55, 66, 66])
    '''
    
  • torch.flatten(x):默认将张量拉成一维向量,torch.flatten(x,1)为从第二维开始平坦化。torch.flatten(x,0,1) 代表在第一维和第二维之间平坦化

    import torch
    x=torch.randn(2,4,2)
    print(x)z=torch.flatten(x)
    print(z)w=torch.flatten(x,1)
    print(w)'''
    tensor([[[-0.9814,  0.8251],[ 0.8197, -1.0426],[-0.8185, -1.3367],[-0.6293,  0.6714]],[[-0.5973, -0.0944],[ 0.3720,  0.0672],[ 0.2681,  1.8025],[-0.0606,  0.4855]]])tensor([-0.9814,  0.8251,  0.8197, -1.0426, -0.8185, -1.3367, -0.6293,  0.6714,-0.5973, -0.0944,  0.3720,  0.0672,  0.2681,  1.8025, -0.0606,  0.4855])tensor([[-0.9814,  0.8251,  0.8197, -1.0426, -0.8185, -1.3367, -0.6293,  0.6714]
    , [-0.5973, -0.0944,  0.3720,  0.0672,  0.2681,  1.8025, -0.0606,  0.4855]])
    '''
    

    torch.nn.Flatten():被用在神经网络中,输入为一批数据,第一维为batch,通常要把一个数据拉成一维,而不是将一批数据拉为一维。所以torch.nn.Flatten()默认从第二维开始平坦化。

  • net. apply():递归地搜索网络内的所有module并把参数表示的函数应用到所有的module上。一般和init函数配套使用。

Logistics

#矢量化后利用线性代数库可以显著提高效率(for循环效率低)
%matplotlib inline
import random
import numpy as np
import torch
from d2l import torch as d2l
# 根据带有噪声的线性模型构造一个人造数据集
def synthetic_data(w,b,num_examples):# 生成size = 1000 * 2 的服从标准正态分布的样本x,即1000个[x1,x2]X = torch.normal(0,1,(num_examples,len(w)))y = torch.matmul(X, w) + b # 一维张量y += torch.normal(0, 0.01, y.shape) # 加噪声return X, y.reshape((-1,1)) # 1000 * 1,不加reshape也可以
# 真实参数,我们希望最后拟合出来的数尽量接近
true_w = torch.tensor([2,-3.4])
true_b = 4.2
features, labels = synthetic_data(true_w,true_b,1000) # 1000个样本,采集两个特征
# featrues中每一行都包含一个二维数据样本,la|bles中每一行都包含一维标签值
print('features:',features[0],'\nlabel:',labels[0])
features: tensor([ 1.4140, -1.3649])
label: tensor([11.6682])
# 生成散点图
d2l.set_figsize()
d2l.plt.scatter(features[:,(1)].detach().numpy(),labels.detach().numpy(),1)
# 随机梯度下降:生成大小为batch_size的小批量
def data_iter(batch_size,features,label):num_examples = len(features)indices = list(range(num_examples))# 样本随机读取,没有特定顺序random.shuffle(indices)# 共生成num_examples/batch_size组,每组个数都是batch_size的随机样本集# 显然可能有样本集可能有重叠,yield局部return,节约内存for i in range(0,num_examples,batch_size):batch_indices = torch.tensor(indices[i:min(i+batch_size,num_examples)])yield features[batch_indices],labels[batch_indices]
# 适合GPU并行运算,连续获得不同的小批量直到遍历完整个数据集
batch_size = 10
for X,y in data_iter(batch_size,features,labels):print(X,'\n',y)break # 没有break会生成100组
tensor([[ 0.1772, -1.1024],[-1.2558, -1.2517],[-0.4269, -0.6371],[-0.1146,  0.6953],[ 0.2708,  1.0730],[ 1.7291, -0.6887],[-0.2078, -0.2044],[-0.3865, -0.8920],[ 2.1464, -0.0057],[-2.1388, -0.6156]]) tensor([[8.2917],[5.9461],[5.5162],[1.6031],[1.0960],[9.9993],[4.4836],[6.4549],[8.5148],[2.0127]])
# 初始化模型参数,之后使用自动微分模块进行梯度下降
w = torch.normal(0,0.01,size=(2,1),requires_grad = True) # 形成求导通路
b = torch.zeros(1,requires_grad = True)
# 根据broadcasting机制,用向量加上一个标量b会加到每个分量上
def linreg(X, w, b):return torch.matmul(X,w)+b# 定义损失函数,注意要转换成维度相同
def squared_loss(y_hat,y):return (y_hat - y.reshape(y_hat.shape))**2/2
# 定义优化算法(小批量)
def sgd(params, lr, batch_size):with torch.no_grad():for param in params:param -= lr*param.grad / batch_size #grad是梯度param.grad.zero_()#清空梯度
# 正式开始训练,very important!
# 每次迭代中读取一小批量训练样本,并得到一组预测,之后开始反向传播
lr = 0.03
num_epochs = 3
net = linreg # 网络类型
loss = squared_lossfor epoch in range(num_epochs):for X,y in data_iter(batch_size,features,labels):l = loss(net(X,w,b),y) # 小批量损失# l的形状是(batch_size,1),故l中所有元素被加到一起以计算梯度l.sum().backward() #梯度回传sgd([w,b],lr,batch_size) # 使用参数的梯度更新参数with torch.no_grad(): train_l = loss(net(features,w,b),labels)print(f'epoch{epoch+1},loss{float(train_l.mean()):f}')
epoch1,loss0.038780
epoch2,loss0.000147
epoch3,loss0.000047
print(w,b)
tensor([[ 2.0002],[-3.3984]], requires_grad=True) tensor([4.1997], requires_grad=True)

标准化实现:

import numpy as np
import torch
from torch.utils import data
from d2l import torch as d2l
true_w = torch.tensor([2,-3.4])
true_b = 4.2
features, labels = synthetic_data(true_w,true_b,1000) # 1000个样本,采集两个特征
# 构造一个PyTorch数据迭代器
# is_train:是否在每个迭代周期内打乱数据
def load_array(data_arrays, batch_size, is_train=True):dataset = data.TensorDataset(*data_arrays) #压缩数据,相当于增广return data.DataLoader(dataset, batch_size, shuffle = is_train) #压缩
batch_size = 10
data_iter = load_array((features, labels),batch_size)
# 通过iter()函数获取这些可迭代对象的迭代器
# 对获取到的迭代器不断使⽤next()函数来获取下⼀条数据
next(iter(data_iter))
[tensor([[ 0.5023,  0.3514],[-0.3454, -0.5636],[-0.7212,  0.3732],[ 1.0469,  0.5950],[ 0.4763,  0.2186],[-0.0729, -0.5102],[ 0.9644, -0.0799],[ 0.2899,  0.2610],[ 0.4827, -0.1519],[ 1.0350,  0.1100]]),tensor([[4.0126],[5.4384],[1.4939],[4.2627],[4.4142],[5.7968],[6.3916],[3.8835],[5.6688],[5.9160]])]
# Sequential类将多个层串联在一起,当给定输入数据时,Sequential实例将数据传到第一层
# 然后将第一层输出作为第二层输入,以此类推
from torch import nn# 此处只有一层,输入特征2(x1,x2),输出特征1(y^)
net = nn.Sequential(nn.Linear(2,1))
# 直接访问参数并进行设定
# 通过_结尾的方法进行参数替换,从而初始化参数
net[0].weight.data.normal_(0,0.01)
net[0].bias.data.fill_(0)# 计算均方误差用MSELoss类
loss = nn.MSELoss()
# 定义优化算法,实例化一个SGD
# 指定待优化的参数net.parameters()
trainer = torch.optim.SGD(net.parameters(),lr=0.03)
'''
通过调用net(X)生成预测并计算损失l(前向传播)
通过进行反向传播来计算梯度
通过调用优化器来更新模型参数
'''
num_epochs = 3 for epoch in range(num_epochs):for X,y in data_iter:l = loss(net(X),y) # 小批量损失trainer.zero_grad()l.backward() #梯度回传trainer.step() # 使用参数的梯度更新参数l = loss(net(features),labels)print(f'epoch{epoch+1},loss{l:f}')
epoch1,loss0.000109
epoch2,loss0.000107
epoch3,loss0.000108
w = net[0].weight.data
b = net[0].bias.data
print(w,b)
tensor([[ 2.0004, -3.3993]]) tensor([4.1999])

softmax

FashionMNIST数据集: