动手学深度学习(第二版)注释后代码【持续更新】

前言

动手学深度学习的代码中用到一些python的特性,但是并没有解释,而且一些torch库函数也并没有做解释,自己也在各大库的官方文档里都查了,做出注释,让人可以看懂每一步。

预备知识

线性神经网络

线性回归从零实现

# 线性回归从零实现
import random
import torch
from d2l import torch as d2l

# 合成数据 features,labels
def synthetic_data(w,b,num_examples):
    X=torch.normal(0,1,(num_examples,len(w))) # 生成随机数据点
    y=torch.matmul(X,w)+b # 计算y
    y+=torch.normal(0,0.01,y.shape) # 对y添加正态分布随机误差,对应MSE极大似然估计
    return X,y.reshape((-1,1))

true_w=torch.tensor([2,-3.4]).reshape(-1,1) # 实际上不规定形状也行,matmul会自动匹配维度
true_b=4.2
features,labels=synthetic_data(true_w,true_b,1000)
d2l.set_figsize()
d2l.plt.scatter(features[:,1].detach().numpy(), # 这里只画第一个特征,因为画不下了
                labels.detach().numpy(),1) # detach是因为转numpy不需要梯度

# 读取数据
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):
        batch_indices=torch.tensor(indices[i:min(i+batch_size,num_examples)]) # 生成目标行列表
        #print(batch_indices)
        yield features[batch_indices],labels[batch_indices] # 通过列表索引
        # 生成generator类,相当于规定了提取方式
        # 每次外部的迭代,都会从函数中寻找原来的循环,从里面提取features[batch_indices]

batch_size=10
#for X,y in data_iter(batch_size,features,labels):
#    print(X,'\n',y)
    
# 初始化模型参数
w=torch.normal(0,0.001,size=(2,1),requires_grad=True)
b=torch.zeros(1,requires_grad=True)

# 定义模型,实现输入到输出,模型和参数分离
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
            param.grad.zero_() # 清空梯度

# 训练
lr=0.03
num_epochs=3
net=linreg # 正向传播
loss=squared_loss # 损失函数

for epoch in range(num_epochs):
    for X,y in data_iter(batch_size,features,labels): # batch_gradient
        l=loss(net(X,w,b),y) # 正向传播
        l.sum().backward() # 反向传播,此时相关requeres_grad变量的梯度都更新了
        sgd([w,b],lr,batch_size) # 使用反向传播后储存在参数中的梯度,
        # 更新参数,同时清空梯度
    with torch.no_grad(): # 输出当前代loss
        train_l=loss(net(features,w,b),labels)
        print(f'epoch {epoch+1}, loss {float(train_l.mean()):f}')

print(f'w的估计误差: {true_w - w.reshape(true_w.shape)}')
print(f'b的估计误差: {true_b - b}')

线性回归简洁实现

# 线性回归简洁实现

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

# 生成数据
true_w=torch.tensor([2,-3.4])
true_b=4.2
features,labels=d2l.synthetic_data(true_w,true_b,1000)
# 读取数据——TensorDateset类与DataLoader类,将数据集与读取分离
def load_array(data_arrays,batch_size,is_train=True):
    dataset=data.TensorDataset(*data_arrays)
    print(dataset)
    return data.DataLoader(dataset,batch_size,shuffle=is_train) # 生成一个generator
batch_size=10
data_iter=load_array((features,labels),batch_size)
# 定义模型——nn.Sequential是一个网络,里面可以加各种层
net=nn.Sequential(nn.Linear(2,1)) # 定义一个网络,具有一个2输入1输出的线性全连接层
# 初始化参数——对Sequential类用下标索引可以对一层操作
net[0].weight.data.normal_(0,0.01) # 正态分布初始化,weight
net[0].bias.data.fill_(0) # 直接填充,bias
# 定义损失函数——损失函数类
loss=nn.MSELoss()# MSE类,但是后面用的时候却像一个函数一样
# 定义优化器——SGD,绑定net,minibatch上的梯度是original的无偏估计,所以可以近似,而且引入随机,不会陷入局部最优
trainer=torch.optim.SGD(net.parameters(),lr=0.03)
# 训练流程
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() # 优化器自动根据lost的梯度,优化一步
    l=loss(net(features),labels)
    print(f'epoch {epoch+1},loss {l:f}')
print(f'w:{net[0].weight.data},b:{net[0].bias.data}')

softmax从零实现

这里你就会发现从零实现的难度逐渐提升,当层数变多,需要处理的参数之类的就越来越多,对应的函数也越来越大。一个尤其大的问题就是,程序隐式地使用了全局变量,因为参数太多了,不能全部传入,比如W和b,这两个参数都是默认全局使用的,这就让程序有隐患且不易读。

# softmax从零实现
import torch 
from IPython import display
from d2l import torch as d2l

# 读取数据
batch_size=256
train_iter,test_iter=d2l.load_data_fashion_mnist(batch_size)
# 初始化参数
num_inputs=28*28
num_outputs=10
W=torch.normal(0,0.01,size=(num_inputs,num_outputs),requires_grad=True)
b=torch.zeros(num_outputs,requires_grad=True)
# 定义softmax
def softmax(X):
    X_exp=torch.exp(X)
    partition=X_exp.sum(1,keepdim=True)
    return X_exp/partition # 广播归一化
# 定义模型
def net(X):# 先将图片变成一维向量,然后再操作,最后softmax
    return softmax(torch.matmul(X.reshape(-1,W.shape[0]),W)+b)
# 定义损失函数,实际上y标签并不是单热点编码,所以要进行一点处理
def cross_entropy(y_hat,y):# 这里是花式索引,给两个列表进行交点选择,回归交叉熵本质
    return -torch.log(y_hat[range(len(y_hat)),y])

# 定义训练精度(但是这个仅仅计算正确个数)
def accuracy(y_hat,y):
    if len(y_hat.shape)>1 and y_hat.shape[1]>1: # y_hat是矩阵,就降维
        y_hat=y_hat.argmax(axis=1) # 降维原则:在axis=1方向上找到最大的下标
    cmp=y_hat.type(y.dtype)==y # 先转换类型,再比较是否预测正确,得到bool列表
    return float(cmp.type(y.dtype).sum()) # True算1,计数

class Accumulator:  #@save
    """在n个变量上累加"""
    def __init__(self, n):
        self.data = [0.0] * n

    def add(self, *args):# 因为是list,所以不能用‘+’,而是用zip实现并行处理两个list
        self.data = [a + float(b) for a, b in zip(self.data, args)]

    def reset(self):
        self.data = [0.0] * len(self.data)

    def __getitem__(self, idx):
        return self.data[idx]
    
def evaluate_accuracy(net,data_iter):
    """计算在指定数据集上模型的精度,实际上不用这个"""
    if isinstance(net,torch.nn.Module): # 如果是自定义函数,就跳过
        net.eval() # 设置模型为评估模式
    metric=Accumulator(2) # 累积,第一个是正确个数,第二个是总个数
    with torch.no_grad():
        for X,y in data_iter: # 对每个batch产生的y_hat,y,都把个数累积到Accumulator里
            metric.add(accuracy(net(X),y),y.numel())
    return metric[0]/metric[1] # 最后得出所有batch的结果

# 定义优化器
lr=0.1
def updater(batch_size):
    return d2l.sgd([W,b],lr,batch_size)

# 训练一代,返回训练损失和训练精度
def train_epoch_ch3(net, train_iter, loss, updater):  #@save
    """训练模型一个迭代周期(定义见第3章)"""
    # 将模型设置为训练模式,如果是个函数,就跳过
    if isinstance(net, torch.nn.Module):
        net.train()
    # 训练损失总和、训练准确度总和、样本数
    metric = Accumulator(3)
    for X, y in train_iter:
        # 计算梯度并更新参数
        y_hat = net(X) # 前向
        l = loss(y_hat, y) # 损失
        if isinstance(updater, torch.optim.Optimizer): # 反向传播
            # 使用PyTorch内置的优化器和损失函数
            updater.zero_grad() 
            l.mean().backward() # 这里感觉用mean和sum没啥区别,反正会自动求导
            updater.step()
        else:
            # 使用定制的优化器和损失函数
            # 清零梯度在updater里面
            l.sum().backward()
            updater(X.shape[0]) # 调用一个updater函数,传入的是batchsize
            # 而updater内部会生成一个sgd优化器,调用全局变量W,b,实际不会这么实现。
        metric.add(float(l.sum()), accuracy(y_hat, y), y.numel())
    # 返回训练损失和训练精度
    return metric[0] / metric[2], metric[1] / metric[2]

# 动画绘图
class Animator:  #@save
    """在动画中绘制数据"""
    def __init__(self, xlabel=None, ylabel=None, legend=None, xlim=None,
                 ylim=None, xscale='linear', yscale='linear',
                 fmts=('-', 'm--', 'g-.', 'r:'), nrows=1, ncols=1,
                 figsize=(3.5, 2.5)):
        # 增量地绘制多条线
        if legend is None:
            legend = []
        d2l.use_svg_display()
        self.fig, self.axes = d2l.plt.subplots(nrows, ncols, figsize=figsize)
        if nrows * ncols == 1:
            self.axes = [self.axes, ] # 即使只有一个,也要变成list,适应d2l绘图
        # 使用lambda函数捕获参数,理解为将参数以预先写好的函数形式存到config_axes函数里
        # 实际上就是把后面一大串表述用一个config_axes()代替
        # 好处就是可以在另一个函数里间接使用这一大堆参数了,
        self.config_axes = lambda: d2l.set_axes(
            self.axes[0], xlabel, ylabel, xlim, ylim, xscale, yscale, legend)
        # 这几个参数因为不是set_axes的参数,所以不能放在那个lambda里,就单独用self过渡
        self.X, self.Y, self.fmts = None, None, fmts

    def add(self, x, y):
        # 向图表中添加多个数据点
        if not hasattr(y, "__len__"): # 保证y是iterable
            y = [y]
        n = len(y)
        if not hasattr(x, "__len__"): # 保证x和y的数量对齐,适应d2l的二维列表绘图
            x = [x] * n
        if not self.X: # 创建shape=(n,0)的空列表X,Y,用于承接逐渐加入的x,y数据
            self.X = [[] for _ in range(n)]
        if not self.Y:
            self.Y = [[] for _ in range(n)]
        for i, (a, b) in enumerate(zip(x, y)): # zip并行处理x,y,将一列数据加入X,Y
            if a is not None and b is not None:
                self.X[i].append(a)
                self.Y[i].append(b)
        self.axes[0].cla() # 清空图像0,实际上只有这一个图
        #画出三条线
        for x, y, fmt in zip(self.X, self.Y, self.fmts): # zip并行处理x,y,fmt,
            self.axes[0].plot(x, y, fmt)
        self.config_axes()
        display.display(self.fig)
        display.clear_output(wait=True)  # 清楚输出

# 训练
def train_ch3(net, train_iter, test_iter, loss, num_epochs, updater):  #@save
    """训练模型(定义见第3章)"""
    animator = Animator(xlabel='epoch', xlim=[1, num_epochs], ylim=[0.3, 0.9],
                        legend=['train loss', 'train acc', 'test acc'])
    for epoch in range(num_epochs):
        train_metrics = train_epoch_ch3(net, train_iter, loss, updater) # 训练一次
        test_acc = evaluate_accuracy(net, test_iter) # 测试一次
        animator.add(epoch + 1, train_metrics + (test_acc,)) # 动态绘图,x是标量,y是三元组
        # print(hasattr( train_metrics + (test_acc,),'__len__'))
    # 如果脱离正常范围,说明没训练好,可能是数值计算错误出现了
    train_loss, train_acc = train_metrics
    assert train_acc <= 1 and train_acc > 0.7, train_acc # 这个,后面的东西没啥用
    assert test_acc <= 1 and test_acc > 0.7, test_acc
    assert train_loss < 0.5, train_loss
    
num_epochs=10
train_ch3(net,train_iter,test_iter,cross_entropy,num_epochs,updater)

# 预测
def predict_ch3(net,test_iter,n=6):
    for X,y in test_iter: # 获取一个batch
        break
    trues=d2l.get_fashion_mnist_labels(y)
    preds=d2l.get_fashion_mnist_labels(net(X).argmax(axis=1))
    titles=[true+'\n'+pred for true,pred in zip(trues,preds)]
    d2l.show_images( # 从batch中取n个样本绘制
            X[0:n].reshape(n,28,28),1,n,titles=titles[0:n])
    
predict_ch3(net,test_iter,20) # 取20个大概有一两个错的。

softmax简洁实现

层数变多+动态绘图,一下子将代码变多。

这个时候,一个顺理成章的思路应该是将这些参数封装到类中,pytorch框架也确实是这么做的,使用Sequential类将网络直接封装为一个类,调整参数之类的操作可以直接通过访问net的某一层实现,也可以将net和优化器绑定,通过优化器简洁地实现。

使用了pytorch库的神经网络,所有模块都变成了类。
请添加图片描述

# softmax简洁实现
import torch
from torch import nn
from d2l import torch as d2l

# 加载数据
batch_size=256
train_iter,test_iter=d2l.load_data_fashion_mnist(batch_size)
# 定义模型+初始化模型参数
def init_weights(model):
    if type(model)==nn.Linear:
        nn.init.normal_(model.weight,std=0.01) # 默认mean=0
net=nn.Sequential(nn.Flatten(),nn.Linear(28*28,10)) # 添加flatten将图片展成1维
net.apply(init_weights) # 将函数应用到每一个子模块(但是Flatten没有weight)
# softmax+交叉熵
loss=nn.CrossEntropyLoss(reduction='none')
# 定义优化算法
trainer=torch.optim.SGD(net.parameters(),lr=0.1)
# 训练,同样的d2l训练函数,函数通过if来适应net为自定义函数与nn.Module两种情况
num_epochs=10 # 自定义net中,softmax在net里,而这里的softmax在loss里,但是效果一样
d2l.train_ch3(net,train_iter,test_iter,loss,num_epochs,trainer)
  • 2
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
### 回答1: 动手深度第二版(D2L包)是一套用于深度学习Python开源软件包。它的目标是帮助用户轻松地习和实践深度学习算法。 D2L包中包含了各种深度学习的概念和实现,并提供了大量的代码示例和实践项目,使习者能够深入理解深度学习的原理和应用。 使用D2L包,我们可以习和实践深度学习的各个组成部分,包括神经网络、卷积神经网络、循环神经网络等。它提供了丰富的文档和教程,帮助用户了解每个概念的背后原理,并通过实际的代码实现来加深理解。 D2L包还提供了许多实际应用的示例代码,如图像分类、目标检测、自然语言处理等,这些示例代码可以帮助用户将深度学习应用于具体的问题,并通过实践项目来提高自己的实际能力。 此外,D2L包还提供了一些实用工具,如数据加载、模型保存和加载等,方便用户进行数据处理和模型管理。 总之,动手深度第二版(D2L包)是一套功能丰富的深度学习软件包,它提供了大量的习资源和实践项目,帮助用户深入习和实践深度学习算法,是深度学习的优秀工具。 ### 回答2: 动手深度第二版(D2L)是一本深度学习的教材,是由斯坦福大深度学习专家李沐(Mu Li)和阿隆·坎贝尔(Aston Campbell)合著的。它是深度学习爱好者、生和初者的理想教材,可以帮助读者系统地深度学习的基本概念、原理和实践。 D2L使用了MXNet深度学习框架来进行实践。这本书通过清晰的解释、丰富的实例和实践,引导读者逐步了解深度学习的主要概念和技巧。读者可以通过书中的案例习如何构建和训练深度神经网络,掌握数据预处理、模型评估和调优的方法。 D2L还提供了丰富的在线资源,包括代码、演示和实验环境。读者可以通过云平台Colab在线运行代码,不需要在本地安装任何深度学习框架。这使得习更加便捷,降低了入门的门槛。 该书的章节结构清晰,逻辑性强。每个章节都以一个特定的主题开始,然后通过逐步解释相关概念和实践来帮助读者理解。此外,书中还涵盖了深度学习的前沿研究和应用,如计算机视觉、自然语言处理和强化习等领域。 总之,动手深度第二版是一本理论与实践相结合的深度学习教材,可以帮助读者快速入门深度学习,并掌握实际应用的技巧。无论是初者还是已有一定基础的深度学习爱好者,都可以从中受益匪浅。 ### 回答3: 动手深度第二版的d2l包是指由李沐(Mu Li)等人编写的,用于配套教材《动手深度学习》的Python开源包。d2l包中包含了丰富的深度学习模型的实现代码习资源,可以帮助我们更好地理解和深度学习。 首先,d2l包中的代码实现了深度学习中常用的模型,如多层感知机(MLP)、卷积神经网络(CNN)和循环神经网络(RNN)等。这些模型的实现代码非常详细,包括了网络结构的定义、前向传播和反向传播的实现,以及参数的初始化和更新等。通过阅读和运行这些代码,我们可以深入了解不同模型的原理和实现细节。 其次,d2l包还提供了丰富的习资源,如数据集的加载和预处理代码、作业的实现代码和参考答案等。通过使用这些资源,我们可以实际操作和训练深度学习模型,加深对模型训练过程的理解。同时,d2l包中还包含了一些小项目和练习题,可以帮助我们巩固所内容并进行实践。 最后,d2l包的代码风格简洁明了,注释详细清晰,非常适合初者阅读和习。同时,d2l包还使用了Jupyter Notebook的形式组织,可以直接在浏览器中运行代码,并结合文档进行习和实操。这种交互式的习方式使得习过程更加生动和易于理解。 总而言之,动手深度第二版的d2l包是一个非常有价值的习工具,通过阅读和实践其中的代码,我们可以更好地掌握深度学习的核心概念和实现方法。无论是初者还是有一定经验的人士,都可以通过使用d2l包来加深对深度学习的理解和应用。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

亦梦亦醒乐逍遥

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值