第一讲 杂谈+学前准备
1. 关于深度学习
本人是一名大学生,很不幸运在大学之前对深度学习一无所知。自从ChatGPT爆火全球,我才逐渐关注到原来“智能”与深度学习息息相关。本书《动手学深度学习》其实有另一套理论版《DeepLearning》(书的封面是一片花海故被称作“花书”),也是一套很经典的教程,但是由于整篇比较注重数学的推算可以说是很难啃;而本书由书名就可以知道案例比较丰富,“动手学”才能让抽象的变形象。本书第一章没有很多内容,大致是介绍一下深度学习到底是什么。个人觉得重要的是知道“监督学习”、“无监督学习”、“与环境互动”以及“强化学习”这些概念。按照我的理解,监督学习是需要为数据打上标签,自发学习就是无监督学习,与环境互动中有反馈是强化学习。
2. 环境配置
本书配备有完整的电子教程 (其实电子教程已经涵盖了实体书的所有内容,所以对于想要学习DL的同志,这些资料都是开源的)。由于我之前有学过Python,对Python的虚拟环境算是比较熟悉,我没有使用书上的教程进行环境配置,书上的Python环境使用的是Miniconda,Miniconda是一个轻量级的Python发行版本,创建和管理Python环境比较方便所以应用于数据科学方面比较广泛。
2.1 安装Jupyter
我个人使用的是Python3.12,可以手动创建venv虚拟环境然后执行pip install jupyter
就能安装jupyter了,至于jupyter是什么 可以自己去搜一下就明白为什么有这个东西了。jupyter notebook有插件可以安装,但是似乎在7.x版本的notebook中不再支持nbextensions
插件,需要手动把notebook的版本降为6.x。插件不是必要的,安装完成后输入jupyter notebook
即可以启动webui界面。
2.2 安装CUDA、cuDNN和Pytorch
深度学习一般是矩阵计算,而GPU比CPU更适合做这些计算,要调用GPU就需要安装驱动,NVIDIA系列的显卡有CUDA工具包方便我们调用,而cuDNN 是 NVIDIA 的 CUDA 深度神经网络库,可以显著提高训练速度和推断速度,需要匹配对应的cuda版本。而安装Pytorch框架也需要匹配对应的cuda版本,等我安装pytorch的时候发现我装的cuda版本是12.6而pytorch匹配的最新版本是12.4,所以在此告诉大家安装的时候一定要看清所有匹配的版本再安装。CPU也不是不能够用…等到算不够的情况下再换cuda吧。
2.3 云端环境
对于没有显卡的小伙伴,网上有很多免费的GPU资源(转载) 可以使用。
第二讲 预备知识
本讲主要介绍的就是一些数学基础以及Python常用操作。
1.数据操作
1 2 3 4 5 6 7 8 9 10 import torch x = torch.arange(12 ) len (x) x.shape x.numel() x.reshape(3 ,4 ) torch.zeros(2 ,3 ,4 ) torch.ones(2 ,3 ,4 ) torch.randn(3 ,4 ) torch.tensor(l:list )
2.数据运算
1 2 3 4 5 6 torch.exp(x) torch.cat(X,Y,dim=0 ) X == Y x.sum () A.cumsum(axis=0 )
广播机制 :形状不同的张量进行操作的时候会进行自动复制扩充以匹配运算。
索引和切片 :张量特性和Python数组特性类似有索引和切片
内存节省 : X = X + Y
与X += Y
的区别,在Python中如果X
是简单数据类型(如整数、浮点数、字符等)那么这两者没有区别,如果是复杂数据类型前者可能会重新创建一个对象并赋给X
后者将在原有内存上进行赋值,所以后者是更加节省内存的(或者写作X[:] = X + Y
)。
对象转换 :将torch的张量转换为numpy的张量(ndarray),这两其实看上去都是Python的数组,转换也就理所当然了。转换后是共享底层内存的,所以对变量的操作将会相互影响。
1 2 3 A = X.numpy() B = torch.tensor(A) B.item()
3.数据预处理
数据预处理是将原始数据处理成张量的过程。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 import pandas as pddata = pd.read_csv(data_file) data.iloc data = { 'NumRooms' : [None , 2.0 , 4.0 , None ], 'Alley' : ['Pave' , None , None , None ], 'Price' : [127500 , 106000 , 178100 , 140000 ] } data = pd.DataFrame(data) inputs, outputs = data.iloc[:, 0 :2 ], data.iloc[:, 2 ] inputs = inputs.fillna(inputs.mean(numeric_only=True )) inputs = pd.get_dummies(inputs, dummy_na=True ) x = torch.tensor(inputs.to_numpy(dtype=float ))
4.数学基础
4.1 线代
1 2 3 4 5 6 7 A.T torch.dot(x,y) torch.mv(A,x) torch.mm(A,B) torch.norm(u) torch.abs (u).sum
4.2 微积分
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 %matplotlib inline import numpy as npfrom matplotlib_inline import backend_inlinefrom matplotlib import pyplot as pltdef f (x ): return 3 * x ** 2 - 4 * x def use_svg_display (): """使用svg格式在Jupyter中显示绘图""" backend_inline.set_matplotlib_formats('svg' ) def set_figsize (figsize=(3.5 , 2.5 ) ): """设置matplotlib的图表大小""" use_svg_display() plt.rcParams['figure.figsize' ] = figsize def set_axes (axes, xlabel, ylabel, xlim, ylim, xscale, yscale, legend ): """设置matplotlib的轴""" axes.set_xlabel(xlabel) axes.set_ylabel(ylabel) axes.set_xscale(xscale) axes.set_yscale(yscale) axes.set_xlim(xlim) axes.set_ylim(ylim) if legend: axes.legend(legend) axes.grid() def set_axes (axes, xlabel, ylabel, xlim, ylim, xscale, yscale, legend ): """设置matplotlib的轴""" axes.set_xlabel(xlabel) axes.set_ylabel(ylabel) axes.set_xscale(xscale) axes.set_yscale(yscale) axes.set_xlim(xlim) axes.set_ylim(ylim) if legend: axes.legend(legend) axes.grid() def plot (X, Y=None , xlabel=None , ylabel=None , legend=None , xlim=None , ylim=None , xscale='linear' , yscale='linear' , fmts=('-' , 'm--' , 'g-.' , 'r:' ), figsize=(3.5 , 2.5 ), axes=None ): """绘制数据点""" if legend is None : legend = [] set_figsize(figsize) axes = axes if axes else plt.gca() def has_one_axis (X ): return (hasattr (X, "ndim" ) and X.ndim == 1 or isinstance (X, list ) and not hasattr (X[0 ], "__len__" )) if has_one_axis(X): X = [X] if Y is None : X, Y = [[]] * len (X), X elif has_one_axis(Y): Y = [Y] if len (X) != len (Y): X = X * len (Y) axes.cla() for x, y, fmt in zip (X, Y, fmts): if len (x): axes.plot(x, y, fmt) else : axes.plot(y, fmt) set_axes(axes, xlabel, ylabel, xlim, ylim, xscale, yscale, legend) x = np.arange(0 , 3 , 0.1 ) plot(x, [f(x), 2 * x - 3 ], 'x' , 'f(x)' , legend=['f(x)' , 'Tangent line (x=1)' ])
非代码部分的偏导数以及梯度等等概念高数书中有详细介绍不在此赘述。
4.3 自动微分
框架提供自动微分来加快求导,自动微分使系统能够随后反向传播梯度。这里,反向传播(backpropagate)意味着跟踪整个计算图,填充关于每个参数的偏导数。
书中这句话说明了自动求导的意义,现在还不是很懂,后续遇到应该会有所了解。
x.requires_grad_(True) # 等价于x=torch.arange(4.0,requires_grad=True)
这意味着在计算图中,x
将被视为一个需要计算梯度的节点。当你对这个张量执行操作并最终计算损失函数时,PyTorch 会自动跟踪所有对 x
的操作,并在反向传播时计算关于 x
的梯度。
1 2 3 4 5 6 x.grad y = 2 * torch.dot(x, x) y.backward() x.grad x.grad.zero_()
==感觉这里需要补一下矩阵微分(向量微分) 、矩阵梯度 以及反向传播的知识。(后续再补吧,头大)==
以上的微分简而言之是下面这个公式:
∇ x f ( x ) = [ ∂ f ( x ) ∂ x 1 , ∂ f ( x ) ∂ x 2 , … , ∂ f ( x ) ∂ x n ] ⊤ \nabla_{\mathbf{x}} f(\mathbf{x})=\left[\frac{\partial f(\mathbf{x})}{\partial x_1}, \frac{\partial f(\mathbf{x})}{\partial x_2}, \ldots, \frac{\partial f(\mathbf{x})}{\partial x_n}\right]^{\top} ∇ x f ( x ) = [ ∂ x 1 ∂ f ( x ) , ∂ x 2 ∂ f ( x ) , … , ∂ x n ∂ f ( x ) ] ⊤
1 2 3 4 5 6 7 x.grad.zero_() y = x * x y.sum ().backward() x.grad
==到这里只是似懂非懂了…:cry:==
4.4 概率
1 2 3 4 5 6 7 8 9 %matplotlib inline import torchfrom torch.distributions import multinomialfair_probs = torch.ones([6 ]) / 6 multinomial.Multinomial(1 , fair_probs).sample() multinomial.Multinomial(10 , fair_probs).sample()
第三讲 线性神经网络
主要包含两个部分:线性回归和softmax回归。
3.1 线性回归
3.1.1 线性模型
即线性拟合,这与高中所学过的概率知识契合,不做过多叙述(其实这里用矩阵描述的)。
3.1.2 损失函数
L ( w , b ) = 1 n ∑ i = 1 n l ( i ) ( w , b ) = 1 n ∑ i = 1 n 1 2 ( w ⊤ x ( i ) + b − y ( i ) ) 2 . L(\mathbf{w}, b)=\frac{1}{n} \sum_{i=1}^n l^{(i)}(\mathbf{w}, b)=\frac{1}{n} \sum_{i=1}^n \frac{1}{2}\left(\mathbf{w}^{\top} \mathbf{x}^{(i)}+b-y^{(i)}\right)^2 .
L ( w , b ) = n 1 i = 1 ∑ n l ( i ) ( w , b ) = n 1 i = 1 ∑ n 2 1 ( w ⊤ x ( i ) + b − y ( i ) ) 2 .
损失函数就是描述估计值和观测值之间的误差的函数。在训练模型时,我们希望寻找一组参数( w ∗ , b ∗ ) \left(\mathbf{w}^*, b^*\right) ( w ∗ , b ∗ ) , 这组参数能最小化在所有训练样本上的总损失。
w ∗ , b ∗ = argmin w , b L ( w , b ) . \mathbf{w}^*, b^*=\underset{\mathbf{w}, b}{\operatorname{argmin}} L(\mathbf{w}, b) .
w ∗ , b ∗ = w , b a r g m i n L ( w , b ) .
3.1.3 解析解
∥ y − X w ∥ 2 \|\mathbf{y}-\mathbf{X} \mathbf{w}\|^2 ∥ y − X w ∥ 2 最小化(也就是让损失关于w \mathbf{w} w 的导数设为0)得到解析解:
==这里是怎么推算的也不是很清楚:anguished:后续补上==
w ∗ = ( X ⊤ X ) − 1 X ⊤ y \mathbf{w}^*=\left(\mathbf{X}^{\top} \mathbf{X}\right)^{-1} \mathbf{X}^{\top} \mathbf{y}
w ∗ = ( X ⊤ X ) − 1 X ⊤ y
线性回归存在解析解,但不是所有问题存在解析解,所以无法广泛应用在DL。
3.1.4 随机梯度下降
无法得到解析解的情况下,可以通过梯度下降 的方法通过不断地在损失函数递减的方向上更新参数来降低误差。对于平方损失和仿射变换就是以下形式:
w ← w − η ∣ B ∣ ∑ i ∈ B ∂ w l ( i ) ( w , b ) = w − η ∣ B ∣ ∑ i ∈ B x ( i ) ( w ⊤ x ( i ) + b − y ( i ) ) , b ← b − η ∣ B ∣ ∑ i ∈ B ∂ b l ( i ) ( w , b ) = b − η ∣ B ∣ ∑ i ∈ B ( w ⊤ x ( i ) + b − y ( i ) ) . \begin{aligned}
\mathbf{w} & \leftarrow \mathbf{w}-\frac{\eta}{|\mathcal{B}|} \sum_{i \in \mathcal{B}} \partial_{\mathbf{w}} l^{(i)}(\mathbf{w}, b)=\mathbf{w}-\frac{\eta}{|\mathcal{B}|} \sum_{i \in \mathcal{B}} \mathbf{x}^{(i)}\left(\mathbf{w}^{\top} \mathbf{x}^{(i)}+b-y^{(i)}\right), \\
b & \leftarrow b-\frac{\eta}{|\mathcal{B}|} \sum_{i \in \mathcal{B}} \partial_b l^{(i)}(\mathbf{w}, b)=b-\frac{\eta}{|\mathcal{B}|} \sum_{i \in \mathcal{B}}\left(\mathbf{w}^{\top} \mathbf{x}^{(i)}+b-y^{(i)}\right) .
\end{aligned}
w b ← w − ∣ B ∣ η i ∈ B ∑ ∂ w l ( i ) ( w , b ) = w − ∣ B ∣ η i ∈ B ∑ x ( i ) ( w ⊤ x ( i ) + b − y ( i ) ) , ← b − ∣ B ∣ η i ∈ B ∑ ∂ b l ( i ) ( w , b ) = b − ∣ B ∣ η i ∈ B ∑ ( w ⊤ x ( i ) + b − y ( i ) ) .
∣ B ∣ |\mathcal{B}| ∣ B ∣ 表示每个小批量中的样本数,η \eta η 表示学习率,批量大小和学习率的值通常是手动预先指定(称为超参数),而不是通过模型训练得到的。 训练集上的损失达到最小并没有太大作用,难的是找到一组参数,这组参数能够在我们从未见过的数据上实现较低的损失(也称为泛化)。
3.1.5 矢量化加速
使用循环遍历向量的效率是不如向量直接相加的,大概是因为这些库对运算已经做好了优化吧。
3.1.6 正态分布与平方损失
p ( x ) = 1 2 π σ 2 e x p ( − 1 2 σ 2 ( x − μ ) 2 ) . p(x)=\frac1{\sqrt{2\pi\sigma^2}}\mathrm{exp}\left(-\frac1{2\sigma^2}(x-\mu)^2\right).
p ( x ) = 2 π σ 2 1 e x p ( − 2 σ 2 1 ( x − μ ) 2 ) .
1 2 3 4 5 6 7 8 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 ) 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])
最小化负对数似然:
− log P ( y ∣ X ) = ∑ i = 1 n 1 2 l o g ( 2 π σ 2 ) + 1 2 σ 2 ( y ( i ) − w ⊤ x ( i ) − b ) 2 . -\log P(\mathbf{y}\mid\mathbf{X})=\sum_{i=1}^n\frac12\mathrm{log}(2\pi\sigma^2)+\frac1{2\sigma^2}\left(y^{(i)}-\mathbf{w}^\top\mathbf{x}^{(i)}-b\right)^2.
− log P ( y ∣ X ) = i = 1 ∑ n 2 1 l o g ( 2 π σ 2 ) + 2 σ 2 1 ( y ( i ) − w ⊤ x ( i ) − b ) 2 .
3.2 线性回归的从零开始实现
3.2.1 生成数据集
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 def synthetic_data (w, b, num_examples ): """生成y=Xw+b+噪声""" 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, 1000 ) d2l.set_figsize() d2l.plt.scatter(features[:, (1 )].detach().numpy(), labels.detach().numpy(), 1 )
后续越来越不知道是在干啥了,感觉还是需要配合《深度学习》(花书)把概念先过一遍才好理解,那就先改变学习计划吧,算是在计划之外了,高估了自己的基础…