《李沐:动手学深度学习v2 pytorch版》第1章和第2章

0.环境配置

这里我们选择windows下配置深度学习环境,详情见沐神发布的这个视频windows下配置深度学习环境
要注意以下几点:
1.建议直接安装anaconda,目前最新版本适配的python解释器为3.11,如果安装了3.12版本的python解释器,会在这个教程中出现安装d2l包时报错。
2.个人感觉jupyter notebook对新手不友好,建议直接用pycharm,详情可以看B站小土堆

1.引言

大部分都是概念,没有很难的地方,重点注意以下概念
监督学习:有标签的学习,在“给定输入特征”的情况下预测标签。监督学习包括了回归、分类、标记问题、搜索、推荐系统、序列学习。注意序列学习指的是输入的数据是连续的。序列学习需要摄取输入序列或预测输出序列,或两者兼而有之。 具体来说,输入和输出都是可变长度的序列,例如机器翻译和从语音中转录文本。
无监督学习:数据没有标签。比如聚类、主成分分析、因果关系和概率图模型问题、GAN。
强化学习:主要用于与环境交互的学习中。在强化学习问题中,智能体(agent)在一系列的时间步骤上与环境交互。 在每个特定时间点,智能体从环境接收一些观察(observation),并且必须选择一个动作(action),然后通过某种机制(有时称为执行器)将其传输回环境,最后智能体从环境中获得奖励(reward)。

2.预备知识

2.1 数据操作

2.1.1.入门数据操作

#导入pytorch包
import torch

#创建一个行向量,有12个元素
x = torch.arange(12)

#访问张量(沿每个轴的长度)的形状
x.shape

#张量中元素的总数
x.numel()

#改变张量的形状,不改变张量中元素的值
x.reshape(3,4)

#创建全0、全1、或随机采样的数字来初始化矩阵
torch.zeros((2,3,4))
torch.ones((2,3,4))
torch.randn(3,4)

2.1.2 运算符

#相同形状张量之间的运算
x = torch.tensor([1.0, 2, 4, 8])
y = torch.tensor([2, 2, 2, 2])
print(x + y, x - y, x * y, x / y, x ** y ) # **运算符是求幂运算

#两个张量连接(类似于字符串的拼接)
X = torch.arange(12, dtype=torch.float32).reshape((3,4))
Y = torch.tensor([[2.0, 1, 4, 3], [1, 2, 3, 4], [4, 3, 2, 1]])
print(torch.cat((X, Y), dim=0), torch.cat((X, Y), dim=1))
#dim=0表示留下第0个维度,dim=1则留下第一个维度

#对所有元素求和,得到一个单元素张量
x.sum()

2.1.3 广播机制

在某些情况下,张量的形状不同,我们仍然可以通过调用广播机制(broadcasting mechanism)来执行按元素操作。 这种机制的工作方式如下:

1.通过适当复制元素来扩展一个或两个数组,以便在转换之后,两个张量具有相同的形状;
2.对生成的数组执行按元素操作。
例如:

a = torch.arange(3).reshape((3, 1))
b = torch.arange(2).reshape((1, 2))
#a是一个3行1列的张量,b是一个1行2列的张量
print(a + b)
#a会复制列,b会复制行,直到二者形状相同后个元素相加

2.1.4 索引和切片

此部分与python语法相同,不做赘述

2.1.5 节省内存

运行一些操作可能会导致为新结果分配内存。 例如,如果我们用Y = X + Y,我们将取消引用Y指向的张量,而是指向新分配的内存处的张量。

在下面的例子中,我们用Python的id()函数演示了这一点, 它给我们提供了内存中引用对象的确切地址。 运行Y = Y + X后,我们会发现id(Y)指向另一个位置。 这是因为Python首先计算Y + X,为结果分配新的内存,然后使Y指向内存中的这个新位置。

before = id(Y)
Y = Y + X
print(id(Y) == before)

这可能是不可取的,原因有两个:
1.首先,我们不想总是不必要地分配内存。在机器学习中,我们可能有数百兆的参数,并且在一秒内多次更新所有参数。通常情况下,我们希望原地执行这些更新;
2.如果我们不原地更新,其他引用仍然会指向旧的内存位置,这样我们的某些代码可能会无意中引用旧的参数。
执行原地操作的代码:

#1.使用切片表示法将操作的结果分配给先前分配的数组
Z = torch.zeros_like(Y)
Z[:] = X + Y

#2.原地操作
X += Y

本章练习

1.运行本节中的代码。将本节中的条件语句X == Y更改为X < Y或X > Y,然后看看你可以得到什么样的张量。

X = torch.arange(12, dtype=torch.float32).reshape((3,4))
Y = torch.tensor([[2.0, 1, 4, 3], [1, 2, 3, 4], [4, 3, 2, 1]])
print(X > Y, '\n', X < Y)
#得到答案:
tensor([[False, False, False, False],
        [ True,  True,  True,  True],
        [ True,  True,  True,  True]]) 
 tensor([[ True, False,  True, False],
        [False, False, False, False],
        [False, False, False, False]])

2.用其他形状(例如三维张量)替换广播机制中按元素操作的两个张量。结果是否与预期相同?

a = torch.arange(12).reshape((2, 1, 6))
b = torch.arange(24).reshape((3, 2, 4))
print(a + b)
#报错如下
 print(a + b)
          ~~^~~
RuntimeError: The size of tensor a (6) must match the size of tensor b (4) at non-singleton dimension 2

2.2 数据预处理

本节开始介绍如何读取数据,并对数据做预处理

2.2.1 读取数据集

举一个例子,我们首先创建一个人工数据集,并存储在CSV(逗号分隔值)文件 …/data/house_tiny.csv中。 以其他格式存储的数据也可以通过类似的方式进行处理。 下面我们将数据集按行写入CSV文件中。

import os
os.makedirs(os.path.join('..', 'data'), exist_ok=True)
data_file = os.path.join('..', 'data', 'house_tiny.csv')
with open(data_file, 'w') as f:
    f.write('NumRooms,Alley,Price\n')  # 列名
    f.write('NA,Pave,127500\n')  # 每行表示一个数据样本
    f.write('2,NA,106000\n')
    f.write('4,NA,178100\n')
    f.write('NA,NA,140000\n')

要从创建的CSV文件中加载原始数据集,我们导入pandas包并调用read_csv函数。该数据集有四行三列。其中每行描述了房间数量(“NumRooms”)、巷子类型(“Alley”)和房屋价格(“Price”)。

# 如果没有安装pandas,只需取消对以下行的注释来安装pandas
# !pip install pandas
import pandas as pd
data = pd.read_csv(data_file)
print(data)

2.2.2 处理缺失值

数据中的“NaN”项代表缺失值。处理缺失值的方法包括插值法和删除法, 其中插值法用一个替代值弥补缺失值,而删除法则直接忽略缺失值。这里考虑插值法:
通过位置索引iloc,我们将data分成inputs和outputs, 其中前者为data的前两列,而后者为data的最后一列。 对于inputs中缺少的数值,我们用同一列的均值替换“NaN”项。

inputs, outputs = data.iloc[:, 0:2], data.iloc[:, 2]
inputs = inputs.fillna(inputs.mean())
print(inputs)

对于inputs中的类别值或离散值,我们将“NaN”视为一个类别。 由于“巷子类型”(“Alley”)列只接受两种类型的类别值“Pave”和“NaN”, pandas可以自动将此列转换为两列“Alley_Pave”和“Alley_nan”。 巷子类型为“Pave”的行会将“Alley_Pave”的值设置为1,“Alley_nan”的值设置为0。 缺少巷子类型的行会将“Alley_Pave”和“Alley_nan”分别设置为0和1。

inputs = pd.get_dummies(inputs, dummy_na=True)
print(inputs)

2.2.3 转换为张量格式

现在inputs和outputs中的所有条目都是数值类型,它们可以转换为张量格式。

import torch
X = torch.tensor(inputs.to_numpy(dtype=float))
y = torch.tensor(outputs.to_numpy(dtype=float))
print(X, y)

本章练习

创建包含更多行和列的原始数据集:1.删除缺失值最多的列。2.将预处理后的数据集转换为张量格式。

import os
import pandas as pd

os.makedirs(os.path.join('..', 'data'), exist_ok=True)
data_file = os.path.join('..', 'data', 'house_tiny.csv')
with open(data_file, 'w') as f:
    f.write('NumRooms,Alley,Price,ConstruTime\n')  # 列名
    f.write('NA,Pave,127500,10_2000\n')  # 每行表示一个数据样本
    f.write('2,NA,106000,3_1998\n')
    f.write('4,NA,178100,5_2020\n')
    f.write('NA,NA,140000,8_1979\n')
    f.write('6,NA,NA,9_1996\n')

data = pd.read_csv(data_file)

# 1.删除缺失值最多的列
null_counts = data.isnull().sum()
# 找到含有最多缺失值的列名
most_null_column = null_counts.idxmax()
df = data.drop(most_null_column, axis=1)
print(df)
# 2.将预处理后的数据集转换为张量格式
df = torch.tensor(df.to_numpy(dtype=float))
print(df)

2.3 线性代数

第五小节以及之前的都不再赘述了,直接开始看第六小结

2.3.6 降维

张量降维求和,默认情况下,调用求和函数会沿所有的轴降低张量的维度,使它变为一个标量

x = torch.arange(4, dtype=torch.float32)
print(x, x.sum())

指定张量沿哪一个轴来通过求和降低维度,这里很重要,别搞混了
为了通过求和所有行的元素来降维(轴0),可以在调用函数时指定axis=0。 由于输入矩阵沿0轴降维以生成输出向量,因此输入轴0的维数在输出形状中消失。

A_sum_axis0 = A.sum(axis=0)
A_sum_axis0, A_sum_axis0.shape

指定axis=1将通过汇总所有列的元素降维(轴1)。因此,输入轴1的维数在输出形状中消失。

A_sum_axis1 = A.sum(axis=1)
A_sum_axis1, A_sum_axis1.shape

请注意:axis的值为几,就会按照第几维求和,例如其值为0,就会把所有行都加起来,最后留下一个行向量;若为1,把每一列加起来,最后留下一个列向量。

求平均值

A.mean(), A.sum() / A.numel()

非降维求和

sum_A = A.sum(axis=1, keepdims=True)

如果我们想沿某个轴计算A元素的累积总和, 比如axis=0(按行计算),可以调用cumsum函数。 此函数不会沿任何轴降低输入张量的维度。

A.cumsum(axis=0)

2.3.7 点积

点积是指相同位置的按元素乘积的和

y = torch.ones(4, dtype = torch.float32)
print(x, y, torch.dot(x, y))

注意,我们可以通过执行按元素乘法,然后进行求和来表示两个向量的点积

torch.sum(x * y)

2.3.8 矩阵-向量积

在代码中使用张量表示矩阵-向量积,我们使用mv函数。 当我们为矩阵A和向量x调用torch.mv(A, x)时,会执行矩阵-向量积。 注意,A的列维数(沿轴1的长度)必须与x的维数(其长度)相同。

print(A.shape, x.shape, torch.mv(A, x))

2.3.9 矩阵-矩阵乘法

我们在A和B上执行矩阵乘法。 这里的A是一个5行4列的矩阵,B是一个4行3列的矩阵。 两者相乘后,我们得到了一个5行3列的矩阵。

B = torch.ones(4, 3)
print(torch.mm(A, B))

2.3.10 范数

非正式地说,向量的范数是表示一个向量有多大。 这里考虑的大小(size)概念不涉及维度,而是分量的大小。
L2范数: 假设n维向量x中的元素是x1,x2,…,xn,其L2范数是向量元素平方和的平方根:
L2范数

u = torch.tensor([3.0, -4.0])
torch.norm(u) == 5

本章练习

4.本节中定义了形状(2,3,4)的张量X。len(X)的输出结果是什么?

X = torch.arange(24).reshape(2, 3, 4)
print(len(X))

#输出
2

5.对于任意形状的张量X,len(X)是否总是对应于X特定轴的长度?这个轴是什么?

是的,对应第0轴的大小/len(axis = 0)

6.运行A/A.sum(axis=1),看看会发生什么。请分析一下原因?

X = torch.arange(24).reshape(2, 3, 4)
print(X/X.sum(axis = 1))

#输出
 print(X/X.sum(axis = 1))
          ~^~~~~~~~~~~~~~~~
RuntimeError: The size of tensor a (3) must match the size of tensor b (2) at non-singleton dimension 1

7.考虑一个具有形状(2,3,4)的张量,在轴0、1、2上的求和输出是什么形状?

X = torch.arange(24).reshape(2, 3, 4)
print(X.sum(axis = 0).shape, ' ', X.sum(axis = 1).shape, ' ', X.sum(axis = 2).shape)

#输出
torch.Size([3, 4])   torch.Size([2, 4])   torch.Size([2, 3])

8.为linalg.norm函数提供3个或更多轴的张量,并观察其输出。对于任意形状的张量这个函数计算得到什么?

X = torch.arange(24).reshape(2, 3, 4)
torch.norm(X)

#####输出报错了,我的原因?

2.4 微积分

主要复习了求导、求偏导、链式法则之类的。主要注意向量、矩阵求导后的形式。
关于如何实现求导的详情看视频自动求导实现_李沐

2.5 自动微分

深度学习框架通过自动计算导数,即自动微分(automatic differentiation)来加快求导。 实际中,根据设计好的模型,系统会构建一个计算图(computational graph), 来跟踪计算是哪些数据通过哪些操作组合起来产生输出。 自动微分使系统能够随后反向传播梯度。 这里,反向传播(backpropagate)意味着跟踪整个计算图,填充关于每个参数的偏导数。

2.5.1 一个简单的例子

作为一个演示例子,假设我们想对函数y=2*xTx关于列向量x求导。 首先,我们创建变量x并为其分配一个初始值。

import torch
x = torch.arange(4.0)

在我们计算y关于X的梯度之前,需要一个地方来存储梯度。重要的是,我们不会在每次对一个参数求导时都分配新的内存。
因为我们经常会成千上万次地更新相同的参数,每次都分配新的内存可能很快就会将内存耗尽。注意,一个标量函数关于向量X的梯度是向量,并且与X具有相同的形状。

x.requires_grad_(True)
#上面两句等价于x=torch.arange(4.0,requires_grad=True)

现在计算y

y = 2 * torch.dot(x, x)
print('y关于x的点积:', ' ', y)

接下来,通过调用反向传播函数来自动计算y关于x每个分量的梯度,并打印这些梯度。

y.backward()
print('y关于x每个分量的梯度:', ' ', x.grad)

如果要接着计算关于X的另一个函数的导数y=x.sum(),首先要清除之前的值(因为pytorch会默认累积梯度)

x.grad.zero_()  # 把原来的梯度置为0
y = x.sum()
y.backward()
print('函数y=x.sum()的梯度:', ' ', x.grad)

2.5.2 非标量变量的反向传播

当y不是标量时,向量y关于向量x的导数的最自然解释是一个矩阵。 对于高阶和高维的y和x,求导的结果可以是一个高阶张量。这个性质在深度学习中不常用,重点理解计算图。

# 对非标量调用backward需要传入一个gradient参数,该参数指定微分函数关于self的梯度。
# 本例只想求偏导数的和,所以传递一个1的梯度是合适的
x.grad.zero_()
y = x * x
# 等价于y.backward(torch.ones(len(x)))
y.sum().backward()
x.grad

2.5.3 分离计算

有时,我们希望将某些计算移动到记录的计算图之外。 例如,假设y是作为x的函数计算的,而z则是作为y和x的函数计算的。 想象一下,我们想计算z关于x的梯度,但由于某种原因,希望将y视为一个常数, 并且只考虑到x在y被计算后发挥的作用。

这里可以分离y来返回一个新变量u,该变量与y具有相同的值, 但丢弃计算图中如何计算y的任何信息。 换句话说,梯度不会向后流经u到x。 因此,下面的反向传播函数计算z=ux关于x的偏导数,同时将u作为常数处理, 而不是z=x‘x*x关于x的偏导数。

x.grad.zero_()
y = x * x
u = y.detach()
z = u * x
z.sum().backward()
x.grad == u

由于记录了y的计算结果,我们可以随后在y上调用反向传播, 得到y=xx关于的x的导数,即2x。

x.grad.zero_()
y.sum().backward()
x.grad == 2 * x

2.5.4 Python控制流的梯度计算

这个看完了不太理解,回头再看。

2.6 概率

基本上是概率论知识,回头用到了再看

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值