【lzy学习笔记-dive into deep learning】3.1-3.3 线性回归原理与实现

第三章 线性神经网络

介绍神经⽹络的整个训练过程,包括:定义简单的神经⽹络架构、数据处理、指定损失函数和如何训练模型。

3.1 线性回归

回归regression是能为一个或多个自变量与因变量之间关系建模的一类方法。回归经常用来表示输入和输出之间的关系,与预测任务有关

3.1.1 线性回归linear regression的基本元素

线性回归在回归的各种标准⼯具中最简单而且最流⾏。
线性回归基于的简单假设:⾸先,假设⾃变量x和因变量y之间的关系是线性的,即y可以表⽰为x中元素的加权和,这⾥通常允许包含观测值的⼀些噪声;其次,我们假设任何噪声都⽐较正常,如噪声遵循正态分布。
专有名词:
为了开发⼀个能预测房价的模型,我们需要收集⼀个真实的数据集。这个数据集包括了房屋的销售价格、⾯积和房龄。在机器学习的术语中,该数据集称为训练数据集(training data set)或训练集(training set)。
每⾏数据(⽐如⼀次房屋交易相对应的数据)称为样本(sample),也可以称为数据点(data
point)或数据样本(data instance)。
我们把试图预测的⽬标(⽐如预测房屋价格)称为标签(label)或⽬标(target)。
预测所依据的⾃变量(⾯积和房龄)称为特征(feature)或协变量(covariate)
在这里插入图片描述

线性模型

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述在这里插入图片描述
在这里插入图片描述
在开始寻找最好的模型参数(model parameters)w和b之前,我们还需要两个东西:(1)⼀种模型质量的度量⽅式;(2)⼀种能够更新模型以提⾼模型预测质量的⽅法。

损失函数

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

解析解

在这里插入图片描述

随机梯度下降

即使在我们⽆法得到解析解的情况下,我们仍然可以有效地训练模型。弄清楚如何训练这些难以优化的模型。

梯度下降(gradient descent)这种⽅法⼏乎可以优化所有深度学习模型。它通过不断地在损失函数递减的⽅向上更新参数来降低误差。

梯度下降最简单的⽤法是计算损失函数(数据集中所有样本的损失均值)关于模型参数的导数(在这⾥也可以称为梯度)。但实际中的执⾏可能会⾮常慢:因为在每⼀次更新参数之前,我们必须遍历整个数据集。因此,我们通常会在每次需要计算更新的时候随机抽取⼀小批样本,这种变体叫做小批量随机梯度下降(minibatch stochastic gradient descent)。
在这里插入图片描述
在这里插入图片描述

用模型进行预测在这里插入图片描述
3.1.2 矢量化加速

在训练我们的模型时,我们经常希望能够同时处理整个小批量的样本。为了实现这⼀点,需要我们对计算进⾏⽮量化,从而利⽤线性代数库,而不是在Python中编写开销⾼昂的for循环。

import math
import time
import numpy as np
import torch

# 进⾏运行时间的基准测试,定义⼀个计时器
class Timer:
    def __init__(self): # 记录多次运行时间
        self.times = []
        self.tik = None
        self.start()

    def start(self): # 启动计时器
        self.tik = time.time()

    def stop(self): # 停止计时器并将时间记录在列表中
        self.times.append(time.time() - self.tik)
        return self.times[-1] # 返回最后一个

    def avg(self): # 返回平均时间
        return sum(self.times) / len(self.times)

    def sum(self): # 返回时间总和
        return sum(self.times)

    def cumsum(self): # 返回累计时间
        return np.array(self.times).cumsum().tolist()

# 对工作负载进行基准测试
# 对向量相加的两种方法的负载进行测试
n = 10000
a = torch.ones(n)
b = torch.ones(n)
c = torch.zeros(n)
timer = Timer()
for i in range(n):
    c[i] = a[i] + b[i]
print(f'{timer.stop(): .5f} sec')

timer.start()
d = a + b
print(f'{timer.stop(): .5f} sec')

⽮量化代码通常会带来数量级的加速
将更多的数学运算放到库中,而⽆须⾃⼰编写那么多的计算,从而减少了出错的可能性

3.1.3 正态分布与平方损失

正态分布
在这里插入图片描述
改变均值会产⽣沿x轴的偏移,增加⽅差将会分散分布、降低其峰值。

import math
import numpy as np
import matplotlib.pyplot as plt

# 计算正态分布
def normal(x, mu, sigma):
    p = 1 / math.sqrt(2 * math.pi * sigma ** 2)
    return p * np.exp(-0.5 / sigma ** 2 * (x - mu) ** 2)

# 可视化正态分布
x = np.arange(-7, 7, 0.01)
params = [(0, 1), (0, 2), (3, 1)] # 均值和标准差对
for mu, sigma in params:
    plt.plot(x, normal(x, mu, sigma), label=f'mean {mu}, std {sigma}')

plt.xlabel("x")
plt.ylabel("p(x)")
plt.legend()
plt.show()

均方误差损失函数(均方损失)
解释均方误差可以用于线性回归的原因:
假设观测中包含噪声,噪声服从正态分布
在高斯噪声的假设下,最小化均方误差等价于对线性模型的极大似然估计
在这里插入图片描述

3.1.4 从线性回归到深度网络
神经网络图

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

生物学

当今⼤多数深度学习的研究⼏乎没有直接从神经科学中获得灵感。如今在深度学习中的灵感同样或更多地来⾃数学、统计学和计算机科学。
小结
• 机器学习模型中的关键要素是训练数据、损失函数、优化算法,还有模型本⾝。
• ⽮量化使数学表达上更简洁,同时运⾏的更快。
• 最小化⽬标函数和执⾏极⼤似然估计等价。
• 线性回归模型也是⼀个简单的神经⽹络。

3.2 线性回归的从零开始实现

3.2.1 生成数据集

⽣成⼀个包含1000个样本的数据集,每个样本包含从标准正态分布中采样的2个特征。
合成数据集是⼀个矩阵X ∈ R1000×2。
detach() detach_() data区别

3.2.2 读取数据集

训练模型时要对数据集进⾏遍历,每次抽取⼀小批量样本,并使⽤它们来更新我们的模型。由于这个过程是训练机器学习算法的基础,所以有必要定义⼀个函数,该函数能打乱数据集中的样本并以小批量⽅式获取数据。
random.shuffle
python for range 循环
python yield

3.2.3 初始化模型参数

在初始化参数之后,我们的任务是更新这些参数,直到这些参数⾜够拟合我们的数据。每次更新都需要计算损失函数关于模型参数的梯度。有了这个梯度,我们就可以向减小损失的⽅向更新每个参数。
因为⼿动计算梯度很枯燥而且容易出错,所以没有⼈会⼿动计算梯度。我们使⽤ 2.5节中引⼊的⾃动微分来计算梯度。

3.2.4 定义模型

定义模型,将模型的输⼊和参数同模型的输出关联起来

3.2.5 定义损失函数
3.2.6 优化算法:小批量随机梯度下降方法

在每⼀步中,使⽤从数据集中随机抽取的⼀个小批量,然后根据参数计算损失的梯度。接下来,朝着减少损失的⽅向更新我们的参数。
torch.no_grad()

3.2.7 训练过程

训练过程是具有共性的。深度学习几乎相同的训练过程
.backward
.sum 梯度为1不影响结果 只有标量才能backward
在每次迭代中,读取一小批量训练样本,并通过模型来获得一组预测。计算完损失后,开始反向传播,存储每个参数的梯度。最后调用优化算法来更新模型参数。
在这里插入图片描述
我们不应该想当然地认为我们能够完美地求解参数。在机器学习中,我们通常不太关⼼恢复真正的参数,而更关⼼如何⾼度准确预测参数。幸运的是,即使是在复杂的优化问题上,随机梯度下降通常也能找到⾮常好的解。其中⼀个原因是,在深度⽹络中存在许多参数组合能够实现⾼度精确的预测。

import random
import torch
import matplotlib.pyplot as plt

# 3.2.1 生成数据集

# ease to understand
# X = torch.normal(0, 1, (5, 2)) # 正态分布 均值,标准差,size
# print(X)
# w = torch.tensor([2, -3.4])
# print(w)
# y = torch.matmul(X, w) # matrix multiple 矩阵乘法
# print(y)
# print(y.shape)
# print(y.reshape(-1, 1)) # -1 自动填充 由给出的列决定这里


# 生成数据集 y=Xw+b+噪声
def synthetic_data(w, b, num_examples):
    # 对X: 有num_examples个样本 每个样本有len(w)个特征
    X = torch.normal(0, 1, (num_examples, len(w))) # len(w) 是因为 matmul(X, w)
    y = torch.matmul(X, w) + b
    # 随机噪声 此处设置是服从均值为0的正态分布,标准差设置为0.01
    y += torch.normal(0, 0.01, y.shape) # += 不会再分配新的内存
    return X, y.reshape((-1, 1))

# step 1 生成包含1000个样本的数据集,每个样本包含从标准正态分布中采样的2个特征
true_w = torch.tensor([2, -3.4]) # 2个特征的加权求和,所以需要[w1,w2]
true_b = 4.2
features, labels = synthetic_data(true_w, true_b, 1000)

# 见函数synthetic_data 其中features: num_examples * len(w) label: -1 * 1
print('features:', features[0], '\nlabel:', labels[0]) # 输出[0]第一行

# step 2 可视化
# 画出第2个特征值 和 labels 的散点图 y=w1x1+w2x2+b+噪声 应该是线性关系
# detach()创建和原相同的数据,与原来的共享数据,二者变化一致,但是detach()后的不可求导且求导会报错
# numpy()转化为数组
plt.scatter(features[:, 1].detach().numpy(), labels.detach().numpy(), 1)    # point_size = 1
plt.show()


# 3.2.2 读取数据集
# 训练模型时要对数据集进⾏遍历,每次抽取⼀小批量样本,并使用它们来更新我们的模型。

# 该函数接收批量大小、特征矩阵和标签向量作为输入
# 生成大小为batch_size的小批量,每个小批量包含⼀组特征和标签
def data_iter(batch_size, features, labels):
    num_examples = len(features)
    # list(range(5)) = list(range(0,5)) = [0,1,2,3,4]
    indices = list(range(num_examples))
    # 将序列的所有元素随机排序
    random.shuffle(indices)
    for i in range(0, num_examples, batch_size): # start end+1 step
        batch_indices = torch.tensor(indices[i: min(i + batch_size, num_examples)])
        yield features[batch_indices], labels[batch_indices]
    # 迭代执行效率低,可能会在实际问题中遇到麻烦

batch_size = 10
for X, y in data_iter(batch_size, features, labels):
    print(X, '\n', y)
    break # 此处只返回一组数据 不break的话会展示所有划分

# 3.2.3 初始化模型参数

# 通过从均值为0、标准差为0.01的正态分布中采样随机数来初始化权重,并将偏置初始化为0
w = torch.normal(0, 0.01, size=(2, 1), requires_grad=True)
# print(f'w: {w}')
b = torch.zeros(1, requires_grad=True)

# 3.2.4 定义模型

def linreg(X, w, b):
    return torch.matmul(X, w) + b
    # b标量 矩阵相乘向量 相加是广播机制
    # ⽤⼀个向量加⼀个标量时,标量会被加到向量的每个分量上。

# 3.2.5 定义损失函数

def squared_loss(y_hat, y):
    return (y_hat - y.reshape(y_hat.shape)) ** 2 / 2 # 平方损失函数

# 3.2.6 定义优化算法:小批量随机梯度下降

def sgd(params, lr, batch_size): # 模型参数集合、学习速率和批量大小作为输⼊
    # 每⼀步更新的⼤小由学习速率lr决定
    with torch.no_grad(): # 不进行计算图的构建
        for param in params:
            param -= lr * param.grad / batch_size # 朝着减少损失的方向更新我们的参数
            param.grad.zero_()

# 3.2.7 训练

lr = 0.03 # 设置超参数学习速率
num_epochs = 5 # 设置超参数周期
net = linreg
loss = squared_loss

for epoch in range(num_epochs):
    for X, y in data_iter(batch_size, features, labels): # 不断迭代更新w b
        l = loss(net(X, w, b), y)
        l.sum().backward() # .sum标量
        sgd([w, b], lr, batch_size) # 求导更新w b
    with torch.no_grad():
        train_l = loss(net(features, w, b), labels)
        print(f'epoch {epoch + 1}, loss {float(train_l.mean()): f}')

print(f'error in estimating w: {true_w - w.reshape(true_w.shape)}')
print(f'error in estimating b: {true_b - b}')

小结
• 我们学习了深度⽹络是如何实现和优化的。在这⼀过程中只使⽤张量和自动微分,不需要定义层或复杂的优化器。
• 这⼀节只触及到了表⾯知识。在下面的部分中,我们将基于刚刚介绍的概念描述其他模型,并学习如何更简洁地实现其他模型。

3.3 线性回归的简洁实现(依靠API)

从零开始的实现

import numpy as np
import torch
from torch.utils import data
from torch import nn

# step 1 生成数据集
def synthetic_data(w, b, num_examples):
    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))

true_w = torch.tensor([2, -3.4])
true_b = 4.2
features, labels = synthetic_data(true_w, true_b, 100)

# step 2 读取数据集
def load_array(data_arrays, batch_size, is_train=True): # 构造数据迭代器
    dataset = data.TensorDataset(*data_arrays)
    return data.DataLoader(dataset, batch_size, shuffle=is_train)
    # shuffle set to ``True`` to have the data reshuffled at every epoch

batch_size = 10
data_iter = load_array((features, labels), batch_size)
# print(next(iter(data_iter))) # https://www.runoob.com/python3/python3-iterator-generator.html

# step 3 定义模型
# note 1: 定义⼀个模型变量net,它是⼀个Sequential类的实例。Sequential类标准流水线
# Sequential类将多个层串联在⼀起。当给定输⼊数据时,Sequential实例将数据传⼊到第⼀层,然后将第⼀层的输出作为第⼆层的输⼊,以此类推。
# note 2: 单层网络架构,这⼀单层被称为全连接层(fully-connected layer)
# 因为它的每⼀个输⼊都通过矩阵-向量乘法得到它的每个输出。
# 在PyTorch中,全连接层在Linear类中定义。两个参数(指定输⼊特征形状,指定输出形状)
net = nn.Sequential(nn.Linear(2, 1))

# step 4 初始化模型参数
net[0].weight.data.normal_(0, 0.01) # 网络的第一图层/访问数据/设置参数
net[0].bias.data.fill_(0)

# step 5 定义损失函数
loss = nn.MSELoss() # 默认情况下,它返回所有样本损失的平均值

# step 6 定义优化算法
# 指定优化的参数(可通过net.parameters()从我们的模型中获得)以及优化算法所需的超参数字典
trainer = torch.optim.SGD(net.parameters(), lr=0.03)

# step 7 训练
num_epochs = 5
for epoch in range(num_epochs):
    for X, y in data_iter:
        l = loss(net(X), y)
        trainer.zero_grad()
        l.backward()
        trainer.step() # Performs a single optimization step (parameter update).
    l = loss(net(features), labels)
    print(f'epoch {epoch + 1}, loss {l: f}')

w = net[0].weight.data
b = net[0].bias.data
print(f'error in estimating w: {true_w - w.reshape(true_w.shape)}')
print(f'error in estimating b: {true_b - b}')

小结
• 我们可以使⽤PyTorch的⾼级API更简洁地实现模型。
• 在PyTorch中,data模块提供了数据处理⼯具,nn模块定义了⼤量的神经⽹络层和常⻅损失函数。
• 我们可以通过_结尾的⽅法将参数替换,从而初始化参数。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值