个人学习笔记2:动手学深度学习pytorch版-李沐

#深度学习# #人工智能#

3. 线性神经网络

经典统计学习技术中的线性回归和softmax回归可以视为线性神经网络。

3.1线性回归

3.1.1 线性回归的基本元素

线性问题。

例子:房价预测

假设 1:
影响房价的关键因素
卧室数目卫浴数目和房子大小,分别用𝑥1𝑥2𝑥3表示

假设 2:
销售价格是关键因素的加权总和:𝑦 𝑤1𝑥1+𝑤2𝑥2+𝑤3𝑥3+𝑏        (w,b为权重和偏差,后续确定)

损失函数(此处有的形式在括号前有常数1/2,不会有本质区别,有1/2时,当我们对损失函数求导后常数系数为1):

训练数据集:包含真实值x和与y

学习参数:

由于是线性函数,因此具有显式解:(线性回归可以看作是单层神经网络)

梯度下降:

        注意学习率不能太小,效率低,成本高;也不能太大,太大会导致一直震荡,并不是真正的下降:如图所示:

        梯度下降最简单的用法是计算损失函数(数据集中所有样本的损失均值),关于模型参数的导数(梯度)。实际中的执行可能会非常慢:因为在每一次更新参数之前,我们必须遍历整个数据集。      

小批量随机梯度下降:每次需要计算更新的时候随机抽取一小批样本b(batch-size)。

超参数(hyperparameter)名词补充说明:

b:批量大小(batch size); η:表示学习率(learning rate)

调参(hyperparameter tuning)是选择超参数的过程。批量大小和学习率的值通常是手动预先指定,而不是通过模型训练得到的。

批量大小的选择注意事项:

3.1.2 矢量化加速

训练我们的模型时,我能够同时处理整个小批量的样本。矢量化代码通常会带来数量级的加速。

例子:

#导入相关工具包
import math
import time
import numpy as np
import torch


#定义一个计时器
class Timer:  #@save
    """记录多次运行时间"""
    def __init__(self):
        self.times = []
        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])
a ,b

结构如下:

验证阶段:1.使用for循环,每次执行一位的加法。

c = torch.zeros(n)
timer = Timer()
for i in range(n):
    c[i] = a[i] + b[i]
f'{timer.stop():.5f} sec'

结果:

2.使用重载的+运算符来计算按元素的和。

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

结果:

3.1.3 正态分布与平方损失

正态分布(normal distribution),也称为高斯分布(Gaussiandistribution)。随机变量x具有均值µ和方差σ2(标准差σ),其正态分布概率密度函数如下:

def normal(x, mu, sigma):
    p = 1 / math.sqrt(2 * math.pi * sigma**2)
    return p * np.exp((-1 / 2*sigma**2) * (x - mu)**2)

可视化:

from d2l import torch as d2l
# 再次使用numpy进行可视化
x = np.arange(-7, 7, 0.01)

# 均值和标准差对
params = [(0, 1), (0, 2), (3, 1)]
d2l.plot(x, [normal(x, mu, sigma) for mu, sigma in params], xlabel='x',
         ylabel='p(x)', figsize=(4.5, 2.5),
         legend=[f'mean {mu}, std {sigma}' for mu, sigma in params])

得出:改变均值会产生沿x轴的偏移,增加方差将会分散分布、降低其峰值。

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

        从零开始实现整个方法,包括数据流水线、模型、损失函数和小批量随机梯度下降优化器。

导入工具包:

%matplotlib inline
import random
import torch
from d2l import torch as d2l

3.2.1 生成数据集

根据带有噪声的线性模型构造一个人造数据集(好处:知道真实的w和b):

def synthetic_data(w, b, num_examples):  #样本数num_examples
    """生成y=Xw+b+噪声"""
    X = torch.normal(0, 1, (num_examples, len(w))) #均值为0,方差为1,num_examples样本,列数为w的长度。
    y = torch.matmul(X, w) + b  #x*w+b
    y += torch.normal(0, 0.01, y.shape) #随机噪音,均值为0方差为0.01,形状同y
    return X, y.reshape((-1, 1))  #x和y作为列向量返回


true_w = torch.tensor([2, -3.4]) #定义真实的w
true_b = 4.2 #定义真实的b
features, labels = synthetic_data(true_w, true_b, 1000) #生成特征和标注
print('features:', features[0],'\nlabel:', labels[0]) #打印第0个样本的情况,输出结果显示为长为2的向量;标签为一个标量

d2l.set_figsize()
d2l.plt.scatter(features[:, 1].detach().numpy(), labels.detach().numpy(), 1) #将第一列和标签作图

结果:

3.2.2 读取数据集

        定义一个data_iter函数,该函数接收批量大小、特征矩阵和标签向量作为输入,生成大小为batch_size的小批量。每个小批量包含一组特征和标签。

def data_iter(batch_size, features, labels):
    num_examples = len(features) #样本数量等于特征矩阵长度
    indices = list(range(num_examples))  #生成索引list,0到num_examples
    # 这些样本是随机读取的,没有特定的顺序
    random.shuffle(indices) #随机打乱下标,利用随机数据去访问样本
    for i in range(0, num_examples, batch_size):  #从0开始,一直到num_examples,每次条batch_size个大小。
        batch_indices = torch.tensor(
            indices[i: min(i + batch_size, num_examples)])  #从i开始,到i+batch_size,到num_examples,如果超出num_examples,则取min值
        yield features[batch_indices], labels[batch_indices]  #yield相当于返回函数,一直调用,一直返回值,直至结束。

        利用上述定义寒素,读取第一个小批量数据样本并打印。每个批量的特征维度显示批量大小和输入特征数。同样的,批量的标签形状与batch_size相等。

batch_size = 10

for X, y in data_iter(batch_size, features, labels):
    print(X, '\n', y)
    break

结果:

3.2.3 初始化模型参数

        生成均值为0、标准差为0.01的正态分布中采样随机数来初始化权重,并将偏置初始化为0。

w = torch.normal(0, 0.01, size=(2,1), requires_grad=True) #长为2的一个向量,requires_grad=True表示需要计算梯度进行更新
b = torch.zeros(1, requires_grad=True) #偏差为一个标量
w,b

结果:

3.2.4 定义模型

        计算线性模型的输出,我们只需计算输入特征X和模型权重w的矩阵‐向量乘法后加上偏置b。注意,上面的Xw是一个向量,而b是一个标量。基于广播机制:一个向量加一个标量时,标量会被加到向量的每个分量上。

def linreg(X, w, b):  #@save
    """线性回归模型"""
    return torch.matmul(X, w) + b

3.2.5 定义损失函数

        计算损失函数的梯度,所以我们应该先定义损失函数。在实现中,需要将真实值y的形状转换为和预测值y_hat的形状相同。

def squared_loss(y_hat, y):  #@save
    """均方损失"""
    return (y_hat - y.reshape(y_hat.shape)) ** 2 / 2

3.2.6 定义优化算法

def sgd(params, lr, batch_size):   #params参数是一个list 包含我w,b;lr学习率,批大学batch_size
    """小批量随机梯度下降"""
    with torch.no_grad():#在优化期间不计算梯度
        for param in params:
            param -= lr * param.grad / batch_size
            param.grad.zero_()#手动对梯度至0

3.2.7 训练

lr = 0.03
num_epochs = 6
net = linreg
loss = squared_loss


for epoch in range(num_epochs):
    for X, y in data_iter(batch_size, features, labels):#取出一个批量大小的x和y
        l = loss(net(X, w, b), y)  # X和y的小批量损失,#将x,w,b做预测,然后与真实y作损失
        # 因为l形状是(batch_size,1),而不是一个标量。l中的所有元素被加到一起,
        # 并以此计算关于[w,b]的梯度
        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}')

结果:

比较真实参数和通过训练学到的参数来评估训练的成功程度。事实上,真实参数和通过训练学到的参数确实非常接近。

3.3 线性回归的简洁实现

3.3.1 生成数据集

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 = d2l.synthetic_data(true_w, true_b, 1000)

3.3.2 读取数据集

        通过调用框架中现有的API来读取数据。我们将features和labels作为API的参数传递,并通过数据迭代器指定batch_size。

def load_array(data_arrays, batch_size, is_train=True):  #@save
    """构造一个PyTorch数据迭代器"""
    dataset = data.TensorDataset(*data_arrays) #将features, labels做为list,传入TensorDataset,得到dataset。
    return data.DataLoader(dataset, batch_size, shuffle=is_train) #调用DataLoader函数,每次从dataset中随机提取batch_size个样本,shuffle=is_train随机打乱顺序

batch_size = 10
data_iter = load_array((features, labels), batch_size)

#使用iter构造Python迭代器,并使用next从迭代器中获取第一项。
next(iter(data_iter))

结果:

3.3.3 定义模型

        使用框架的预定义好的层。首先定义一个模型变量net,它是一个Sequential类的实例(一个容器)。Sequential类将多个层串联在一起。当给定输入数据时,Sequential实例将数据传入到第一层,然后将第一层的输出作为第二层的输入,以此类推。

# nn是神经网络的缩写
from torch import nn

net = nn.Sequential(nn.Linear(2, 1))#输入特征形状2,输出特征形状为1

3.3.4 初始化模型参数

        指定每个权重参数应该从均值为0、标准差为0.01的正态分布中随机采样,偏置参数将初始化为零。

通过0访问linear,weight访问w,normal使用正态分布来替换data的值。

net[0].weight.data.normal_(0, 0.01)#均值为0,方差为0.01
net[0].bias.data.fill_(0)#bias表偏差,fill填充为0

结果:

3.3.5 定义损失函数

        均方误差使用的是MSELoss类,也称为平方L2范数。默认情况下,它返回所有样本损失的平均值。

loss = nn.MSELoss()

3.3.6 定义优化算法

小批量随机梯度下降算法:

trainer = torch.optim.SGD(net.parameters(), lr=0.03) #net.parameters()包括了w,b等参数

3.3.7 训练

num_epochs = 3 #迭代轮数
for epoch in range(num_epochs):
    for X, y in data_iter:#在data_iter里取出x,y
        l = loss(net(X) ,y)
        trainer.zero_grad()#优化器梯度清零
        l.backward()#pytorch已经做了sum,所以不用sum,直接计算梯度
        trainer.step()#调用step函数,进行模型更新
    l = loss(net(features), labels)
    print(f'epoch {epoch + 1}, loss {l:f}')

结果:

比较生成数据集的真实参数和通过有限数据训练获得的模型参数:

w = net[0].weight.data
print('w的估计误差:', true_w - w.reshape(true_w.shape))
b = net[0].bias.data
print('b的估计误差:', true_b - b)

3.4 softmax回归

回归估计连续值,分类预测离散类别

1.softmax运算获取一个向量并将其映射为概率。
2.softmax回归适用于分类问题,它使用了softmax运算中输出类别的概率分布。
3.交叉熵是一个衡量两个概率分布之间差异的很好的度量,它测量给定模型编码数据所需的比特数

从回归到多类分类问题-均方损失:

从回归到多类分类问题-无校验比例:

需要使得正确类的置信度Oy远远大于非正确类的置信度Oi,\bigtriangleup为一个阈值。

 

3.5 图像分类数据集

将使用类似但更复杂的Fashion‐MNIST数据集。Fashion‐MNIST由10个类别的图像组成,每个类别由训练数据集(train dataset)中的6000张图像和测试数据集(test dataset)中的1000张图像组成。因此,训练集和测试集分别包含60000和10000张图像。

3.5.1 读取数据集

        

#导入相关库
%matplotlib inline
import torch
import torchvision
from torch.utils import data
from torchvision import transforms
from d2l import torch as d2l

d2l.use_svg_display()

#通过框架内置函数下载读取数据集
# 通过ToTensor实例将图像数据从PIL类型变换成32位浮点数格式,
# 并除以255使得所有像素的数值均在0~1之间
trans = transforms.ToTensor()
mnist_train = torchvision.datasets.FashionMNIST(
    root="../data", train=True, transform=trans, download=True)
mnist_test = torchvision.datasets.FashionMNIST(
    root="../data", train=False, transform=trans, download=True)

检测数据集长度:

len(mnist_train), len(mnist_test)

结果:

查询数据集图像的尺寸像素(通道数,高度h像素、宽度w像素):

mnist_train[0][0].shape

结果:显示结果为灰度图像,1个通道,高、宽像素均为28.

        Fashion‐MNIST中包含的10个类别,分别为t‐shirt(T恤)、trouser(裤子)、pullover(套衫)、dress(连衣裙)、coat(外套)、sandal(凉鞋)、shirt(衬衫)、sneaker(运动鞋)、bag(包)和ankle boot(短靴)。以下函数用于在数字标签索引及其文本名称之间进行转换。

def get_fashion_mnist_labels(labels):  #@save
    """返回Fashion-MNIST数据集的文本标签"""
    text_labels = ['t-shirt', 'trouser', 'pullover', 'dress', 'coat',
                   'sandal', 'shirt', 'sneaker', 'bag', 'ankle boot']
    return [text_labels[int(i)] for i in labels]

可视化样本:

def show_images(imgs, num_rows, num_cols, titles=None, scale=1.5):  #@save
    """绘制图像列表"""
    figsize = (num_cols * scale, num_rows * scale)
    _, axes = d2l.plt.subplots(num_rows, num_cols, figsize=figsize)
    axes = axes.flatten()
    for i, (ax, img) in enumerate(zip(axes, imgs)):
        if torch.is_tensor(img):
            # 图片张量
            ax.imshow(img.numpy())
        else:
            # PIL图片
            ax.imshow(img)
        ax.axes.get_xaxis().set_visible(False)
        ax.axes.get_yaxis().set_visible(False)
        if titles:
            ax.set_title(titles[i])
    return axes


X, y = next(iter(data.DataLoader(mnist_train, batch_size=18)))
show_images(X.reshape(18, 28, 28), 2, 9, titles=get_fashion_mnist_labels(y));

结果:

3.5.2 读取小批量

batch_size = 256

def get_dataloader_workers():  #@save
    """使用4个进程来读取数据"""
    return 4

train_iter = data.DataLoader(mnist_train, batch_size, shuffle=True,
                             num_workers=get_dataloader_workers())#shuffle=True表示打乱顺序

读取训练数据所需的时间:

timer = d2l.Timer()
for X, y in train_iter:
    continue
f'{timer.stop():.2f} sec'

结果:

3.5.3 整合所有组件

定义load_data_fashion_mnist函数,用于获取和读取Fashion‐MNIST数据集。这个函数返回训练集和验证集的数据迭代器。此外,这个函数还接受一个可选参数resize,用来将图像大小调整为另一种形状。

def load_data_fashion_mnist(batch_size, resize=None):  #@save
    """下载Fashion-MNIST数据集,然后将其加载到内存中"""
    trans = [transforms.ToTensor()]
    if resize:
        trans.insert(0, transforms.Resize(resize))
    trans = transforms.Compose(trans)
    mnist_train = torchvision.datasets.FashionMNIST(
        root="../data", train=True, transform=trans, download=True)
    mnist_test = torchvision.datasets.FashionMNIST(
        root="../data", train=False, transform=trans, download=True)
    return (data.DataLoader(mnist_train, batch_size, shuffle=True,
                            num_workers=get_dataloader_workers()),
            data.DataLoader(mnist_test, batch_size, shuffle=False,
                            num_workers=get_dataloader_workers()))

通过指定resize参数来测试load_data_fashion_mnist函数的图像大小调整功能:

train_iter, test_iter = load_data_fashion_mnist(32, resize=64)
for X, y in train_iter:
    print(X.shape, X.dtype, y.shape, y.dtype)
    break

结果:

3.6 softmax回归的从零开始实现

后续再补充

3.7 softmax回归的简洁实现

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值