# d2l-study-recoder **Repository Path**: bllose/d2l-study-recoder ## Basic Information - **Project Name**: d2l-study-recoder - **Description**: No description available - **Primary Language**: Unknown - **License**: Not specified - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 0 - **Created**: 2025-08-18 - **Last Updated**: 2025-09-01 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # 特別记录 ## OMP(Jupyter Lab 重启原因之一) ``` OMP: Error #15: Initializing libiomp5md.dll, but found libiomp5md.dll already initialized. OMP: Hint This means that multiple copies of the OpenMP runtime have been linked into the program. That is dangerous, since it can degrade performance or cance or cause incorrect results. The best thing to do is to ensure that only a single OpenMP runtime is linked into the process, e.g. by avoiding ic lg of ause incorrect results. The best thing to do static linking of the OpenMP runtime in any library. As an unsafe, unsupported, undocumented workaround you can set the environment variable KMP_D_LIBo allfe, unsupported, undocumented workaround you UPLICATE_LIB_OK=TRUE to allow the program to continue to execute, but that may cause crashes or silently produce incorrect results. For more inforse sntel.rrect results. For more information, please smation, please see http://www.intel.com/software/products/support/. ``` > 报错的直接原因是重复初始化了OpenMP运行时库`libiomp5md.dll` > 可能原因是多个库(比如PyTorch, NumPy等)都自带了`libiomp5md.dll`,运行时冲突了。 临时解决方案 ``` Python import os os.environ["KMP_DUPLICATE_LIB_OK"]="TRUE" ``` ## d2l下包含的多个版本 比如在下载 `d2l-zh-2.0.0`后,项目下包含`mxnet`, `paddle`, `pytorch`, `tensorflow` 实际上是指使用的主要包。 其中包所依赖的python版本不同需要注意。 比如 `mxnet` 只能在3.6~3.7版本下运行。 典型的`mxnet`>`chapter_linear-networks`>`image-classification-dataset.ipynb` ``` Python %matplotlib inline import sys from mxnet import gluon from d2l import mxnet as d2l d2l.use_svg_display() ``` 这里就需要导入这个包,如果python是3.12, 那么这个包都装不了,就没法运行了。 ## d2l包导入 在有的代码样例中,并没有附带`d2l`包的内容。 [一个可使用的d2l包源码](https://github.com/d2l-ai/d2l-en/tree/master/d2l) 将d2l文件夹加入到 PYTHONPATH中可以正常执行`d2l`包导入操作。 # 符号 ![输入图片说明](resources\符号-数字.png) ![输入图片说明](resources/%E7%AC%A6%E5%8F%B7-%E9%9B%86%E5%90%88%E8%AE%BA.png) ![输入图片说明](resources/%E7%AC%A6%E5%8F%B7-%E5%87%BD%E6%95%B0%E5%92%8C%E8%BF%90%E7%AE%97%E7%AC%A6.png) ![输入图片说明](resources/%E7%AC%A6%E5%8F%B7-%E5%BE%AE%E7%A7%AF%E5%88%86.png) ![输入图片说明](resources/%E7%AC%A6%E5%8F%B7-%E6%A6%82%E7%8E%87%E4%B8%8E%E4%BF%A1%E6%81%AF%E8%AE%BA.png) ![输入图片说明](resources/%E7%AC%A6%E5%8F%B7-%E5%A4%8D%E6%9D%82%E5%BA%A6.png) # 第二章 预备知识 ## 2.1 数据操作 ### 2.1.2 运算符 我们的兴趣不仅限于读取数据和写入数据。我们想在这些数据上执行数学运算,其中最简单且最有用的操作是按元素(elementwise)运算。它们将标准标量运算符应用于数组的每个元素。对于将两个数组作为输入的函数,按元素运算将二元运算符应用于两个数组中的每对位置对应的元素。我们可以`基于任何从标量到标量的函数来创建按元素函数`。 ![输入图片说明](resources/运算符说明.png) ### 2.1.5 节省内存 使用切片表示法将操作的结果分配给先前分配的数组。 ``` Python Z = torch.zeros_like(Y) print('id(Z):', id(Z)) Z[:] = X + Y print('id(Z):', id(Z)) ``` > id(Z): 140327634811696 > id(Z): 140327634811696 如果在后续计算中没有重复使用X,我们也可以使用X[:] = X + Y或X += Y来减少操作的内存开销。 ``` Python before = id(X) X += Y id(X) == before ``` > True ### 本章重点 #### 广播机制 ## 2.2 数据预处理 ### 2.2.2 处理缺失值 重点关注`Pandas.get_dummies()` 将分类变量转换为数值变量。 分类变量: Categorical Variables 独热编码:One-Hot Encoding 重点关注`pandas.core.frame.DataFrame.fillna()`,按列进行填充。 材料中: ``` Python import pandas as pd data = pd.read_csv(data_file) inputs, outputs = data.iloc[:, 0:2], data.iloc[:, 2] inputs = inputs.fillna(inputs.mean()) # 计算第二列均值时报错 ``` 这段代码是会报错的。 因为直接读取到的文件中, 第二列为数值型: | | NumRooms | Alley | Price | | --- | --- | --- | --- | |0| NaN |Pave |127500| |1 |2.0 |NaN |106000| |2 |4.0 |NaN |178100| |3 |NaN |NaN |140000| ``` Python TypeError: can only concatenate str (not "int") to str ``` 将`dummies`函数提前执行可以正常运行。 这里有个细节,dummies之后,第二列其实变成了由0和1组成的列,也会执行均值计算,但是因为没有NaN元素,所以没有任何效果。 但是如果该列真的有NaN元素,那么填充是有问题的,可能出现歧义甚至是错误信息。 ``` Python data = pd.read_csv(data_file) inputs, outputs = data.iloc[:, 0:2], data.iloc[:, 2] inputs = pd.get_dummies(inputs, dummy_na=True) # 在计算 mean 之前,先调用dummies,将类型列转化为 0,1列 inputs = inputs.fillna(inputs.mean()) ``` 验证代码: ``` Python import numpy as np inputs, outputs = data.iloc[:, 0:2], data.iloc[:, 2] inputs = pd.get_dummies(inputs, dummy_na=True) inputs.loc[3, 'Alley_Pave'] = np.nan inputs = inputs.fillna(inputs.mean()) print(inputs) ``` 输出: | | NumRooms | Alley_Pave | Alley_nan | | --- | --- | --- | --- | | 0 | 3.0 | True | False | | 1 | 2.0 | True | False | | 2 | 4.0 | True | False | | 3 | 3.0 | 1.0 | True | ``` Python FutureWarning: Setting an item of incompatible dtype is deprecated and will raise an error in a future version of pandas. Value 'nan' has dtype incompatible with bool, please explicitly cast to a compatible dtype first. inputs.loc[3, 'Alley_Pave'] = np.nan ``` ## 2.3 线性代数 ### 2.3.5 张量算法的基本性质 两个矩阵的按元素乘法称为Hadamard积(Hadamard product)(数学符号⊙) ![输入图片说明](resources/image.png) Python代码直接就是相乘 -> `A * B` ### 2.3.6 降为 ``` Python import torch A = torch.arange(20).reshape(5,4) print(A, A.sum(), A.sum(axis=0), A.sum(axis=1)) ``` ``` tensor([[ 0, 1, 2, 3], [ 4, 5, 6, 7], [ 8, 9, 10, 11], [12, 13, 14, 15], [16, 17, 18, 19]]) tensor(190) tensor([40, 45, 50, 55]) tensor([ 6, 22, 38, 54, 70]) ``` > axis = 0 时,按横轴降维 > axis = 1 时,按纵轴降维 > 最后打印出来看起来都是横向量,数学属性上来说axis=1应该得到一个列向量,这是打印处理上的妥协,后面学习向量相乘可能有更明确的解释。 #### 非降维求和 ### 2.3.7 点积 Dot Product <**x**,**y**> 可以通过执行**按元素乘法**, 然后进行求和来标识两个向量的点积。 若向量**x**与权重**w**点积,且当权重为**非负**且和为**1**时,点积标识**加权平均**。 ***将两个向量规范化得到单位长度后,点积标识他们夹角的余弦***。 ### 2.3.8 矩阵-向量积 (matrix-vector product) ![输入图片说明](resources/mv_product_as_transpose.png.png) > **这些转换是非常有用的,例如可以用方阵的乘法来表示旋转。后续章节将讲到,我们也可以使用矩阵‐向量积来描述在给定前一层的值时,求解神经网络每一层所需的复杂计算。** `A.shape, x.shape, torch.mv(A, x)` ### 2.3.10 范数(待理解) ``` Python u = torch.tensor([3.0, -4.0]) torch.norm(u) ``` > 非正式地说,向量的范数是表示一个向量有多大。这里考虑的大小(size)概念不涉及维度,而是分量的大小。 **范数和目标** 在深度学习中,我们经常试图解决优化问题:最大化分配给观测数据的概率; 最小化预测和真实观测之间的距离。用向量表示物品(如单词、产品或新闻文章),以便最小化相似项目之间的距离,最大化不同项目之间的距离。目标,或许是深度学习算法最重要的组成部分(除了数据),通常被表达为范数。 ### 线性代数小结 - 标量、向量、矩阵和张量是线性代数中的基本数学对象 - 向量泛化自标量,矩阵泛化自向量 - 标量、向量、矩阵和张量分别具有零、一、二和任意数量的轴。 - 一个张量可以通过sum和mean沿指定的轴降低维度 - 两个矩阵的按元素乘法被称作Hadamard积。它与矩阵乘法不同。 - **在深度学习中,我们经常使用范数,如L1范数、L2范数和Frobenius范数。** - 我们可以对标量、向量、矩阵和张量执行各种操作。 ## 2.4 微积分 逼近法: method of exhaustion 积分 : integral calculus 微分 : differential calculus 损失函数 : loss function 优化 : optimization 泛化 : generalization ### 2.4.3 梯度 函数f(**x**)相对于**x**的梯度是一个包含n个偏导数的向量。 倒数f'(**x**)解释为f(x)相对于x的瞬时变化率。 多元函数(多个方向)的倒数,即为偏导。 梯度就是对n维向量对应的n维偏导向量的称呼。 ![输入图片说明](resources/gradient.png) 👆 **待后续深入理解** ### 2.4.4 链式法则(**待理解**) 然而,上面方法可能很难找到梯度。这是因为在深度学习中,多元函数通常是复合(composite)的,所以难以应用上述任何规则来微分这些函数。幸运的是,链式法则可以被用来微分复合函数。 让我们先考虑单变量函数。假设函数y = f (u)和u = g(x)都是可微的,根据链式法则: ![输入图片说明](resources/%E9%93%BE%E5%BC%8F%E6%B3%95%E5%88%99.png) ![输入图片说明](resources/%E6%9B%B4%E4%B8%80%E8%88%AC%E5%9C%BA%E6%99%AF%E9%93%BE%E5%BC%8F%E6%B3%95%E5%88%99.png) 👆 **待后续深入理解** ### 微积分 小结 - 微分和积分是微积分的两个分支,前者可以应用于深度学习中的优化问题。 - 导数可以被解释为函数相对于其变量的瞬时变化率,它也是函数曲线的切线斜率。 - 梯度是一个向量,其分量是多个变量函数相对于其所在变量的偏导数。 - 链式法则可以用来微分复核函数。 ## 2.5 自动微分 深度学习框架通过自动计算导数,即自动微分(automatic differentiation)来加快求导。实际中,根据设计好的模型,系统会构建一个计算图(computational graph),来跟踪计算是哪些数据通过哪些操作组合起来产生输出。自动微分使系统能够随后反向传播梯度。这里,反向传播(backpropagate)意味着跟踪整个计算图,填充关于每个参数的偏导数。 ### 2.5.1 一个简单的例子 ``` Python import torch # 开启requires_grad=True后,PyTorch会追踪对x的操作,用于后续自动求导 # 且初始化x.grad=None x = torch.arange(4.0, requires_grad=True) print(x, x.grad) # dot(x, x) 向量内积(与点积是什么关系) # y是0维张量 - 标量,为反向传播做准备(PyTorch自动求导需基于标量展开) # tensor(28., grad_fn=) # 这个结果中,grad_fn=就是记录创建y的操作,用于反向推到梯度 y = 2 * torch.dot(x, x) print(y) # 对y执行反向传播,PyTorch依据链式法则自动计算y对x每个元素的梯度 y.backward() print(x, x.grad) """ 作为比较的第二个案例 """ x.grad.zero_() y=x.sum() y.backward() print(x, x.grad) ``` > 对向量 x 而言,`y=2 * torch.dot(x, x)`的数学展开为 > ![输入图片说明](latexs/%E4%B8%80%E4%B8%AA%E7%AE%80%E5%8D%95%E6%A1%88%E4%BE%8B_%E6%95%B0%E5%AD%A6%E5%B1%95%E5%BC%80.png) > 对 x 求 **梯度(逐元素偏导数组成的向量)** 。其中梯度标识如下: > ![输入图片说明](latexs/%E4%B8%80%E4%B8%AA%E7%AE%80%E5%8D%95%E6%A1%88%E4%BE%8B_%E6%A2%AF%E5%BA%A6%E5%81%8F%E5%AF%BC.png) > 计算得: > ![输入图片说明](latexs/%E4%B8%80%E4%B8%AA%E7%AE%80%E5%8D%95%E6%A1%88%E4%BE%8B_%E5%81%8F%E5%AF%BC%E5%8D%95%E9%A1%B9.png) > 所以梯度就是: ![输入图片说明](latexs/%E4%B8%80%E4%B8%AA%E7%AE%80%E5%8D%95%E6%A1%88%E4%BE%8B_%E6%9C%80%E7%BB%88%E6%A2%AF%E5%BA%A6%E8%A1%A8%E7%A4%BA.png) > 将创建值`[0., 1., 2., 3.]`代入计算得`[0., 4., 8., 12.]` ### 2.5.2 非标量变量的反向传播 (待重新理解) ### 2.5.3 分离计算 (待重新理解) ### 2.5.4 Python控制流的梯度计算 (待重新理解) ## 2.6 概率 ### 2.6.1 基本概率 大数定律 - law of large numbers 从概率分布中抽取样本的过程称为 **抽样** (sampling)。 **分布** 笼统来说可以看作对事件的概率分配。 **多项分布** Multinomial Distribution。 主要用于描述在n次独立重复实验中,每次实验有k个互斥结果时,各结果出现次数的概率分布。 #### 掷骰子 案例 案例基础代码知识 ``` Python import torch from torch.distributions import multinomial # 骰子有六面,每次掷骰子翻滚到某一面的概率是相同的 1/6 # 这里利用 torch.ones([6]) 快速定义一个全是1的有6个参数的向量 # 通过按元素除法(Hadamard积)得到平均的1/6概率向量 fair_probs = torch.ones([6]) / 6 # 单次投掷,输出形状[6],但只有一个1(表示哪一面朝上) print(multinomial.Multinomial(1, fair_probs).sample()) # 1000次投掷后归一化。模拟多次投掷后,各面概率趋近于 1/6 print(multinomial.Multinomial(1000, fair_probs).sample()/1000) ``` 案例具体代码 ``` Python import torch import matplotlib.pyplot as plt from torch.distributions import multinomial # 生成500组实验,每组投掷10次 counts = multinomial.Multinomial(10, fair_probs).sample((500,)) # 以横轴(dim=0)方向进行累计统计 # cum_counts[i] 前i组实验的总次数 # 其中每一列是对应面朝上侧总次数 cum_counts = counts.cumsum(dim=0) # 以纵轴(dim=1)方向进行降维求和,计算累计总数 # 由于cum_counts已经累计过一次,故此处统计的是累计的投掷总次数 # 最终 estimates 就是归一化之后的概率分布 estimates = cum_counts / cum_counts.sum(dim=1, keepdims=True) # 下面是具体画图逻辑 plt.figure(figsize=(6, 4.5)) for i in range(6): # estimates[:, i] 获取第i列的所有数据, 每一列就是对应面的出现频率 plt.plot(estimates[:, i].numpy(), label=("P(die=" + str(i + 1) + ")")) plt.axhline(y=0.167, color='black', linestyle='dashed') plt.gca().set_xlabel('Groups of experiments') plt.gca().set_ylabel('Estimated probability') plt.legend() plt.show() ``` ![输入图片说明](resources/%E6%8E%B7%E9%AA%B0%E5%AD%90.png) ### 2.6.2 处理多个随机变量 #### 2.6.2.3 贝叶斯定理 (待进一步理解) #### 2.6.2.4 边际化 (待进一步理解) ### 2.6.3 期望和方差 # 第三章 线性神经网络 关键点 - 定义神经网络架构 - 数据处理 - 定义存世幻术 - 训练模型 ## 3.1 线性回归 回归 regression 是能为一个或多个自变量与因变量之间关系 **建模** 的一类 **方法** 。 ### 3.1.1 线性回归的基本元素 线性回归的几个基本假设 - 假设自变量`x`和应变量`y`之间的关系是线性的,即`y`可以表示为`x`中元素的加权和,这里 **通常允许包含观测值的一些噪声**; - 假设 **任何噪声都比较正常**, 如噪声遵循正太分布。 术语 - 数据集称为 **训练数据集** training data set, **训练集** training set - 训练集中的每一行数据称为 **样本** sample, 或数据点 data point 或数据样本 data instance - 试图预测的目标称为 **标签** label 或 **目标** target - 预测所依据的自变量称为 **特征** feature 或协变量 covariate - 通常使用`n`来表示数据集中的样本数。 对索引为`i`的样本,其输入表示为 ![输入图片说明](resources/%E6%95%B0%E6%8D%AE%E6%A0%B7%E6%9C%AC.png),其对应的标签是![输入图片说明](resources/%E6%A0%87%E7%AD%BE.png) ***线性模型*** 指目标可以表示为特征的加权和。 ![输入图片说明](resources/%E7%BA%BF%E6%80%A7%E6%A8%A1%E5%9E%8B%E6%A1%88%E4%BE%8B%E5%85%AC%E5%BC%8F.png) `w`称为权重,`b`称为 **偏置** bias、 偏移量 offset、 截距 intercept。 严格来说,上面等式是输入特征的一个 仿射变换 affine transformation。 仿射变换的特点是通过加权和对特征进行线性变换 linear transformation,并通过偏置项来进行平移 translation。 ## 3.2 线性回归的从零开始实现 ### 3.2.7 训练 ``` Python for epoch in range(num_epochs): for X, y in data_iter(batch_size, features, labels): l = loss(net(X, w, b), y) # X和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}') ``` `l.sum().backward()` 执行的基本原理。 - tensor对象每次计算时都会记录操作,用以后续反向传播的计算 - `backward`方法就是触发反向传播的逻辑。 - l被计算出来时,得到的是损失值的向量。 - 若直接l.backward()就会由于缺少“统一的标准目标”而不知道如何计算。 - 这里的l.sum()在数学上就是把这几个样本损失求和,得到一个标量L。这时候Torch就知道求导目标就是这个总和L了。 ## 3.5 图像分类数据集 加载`Fashion-MNIST`提供的图像数据集。 ``` Python import torchvision from torch.utils import data from torchvision import transforms def load_data_fashion_mnist(batch_size, resize=None): #@save """下载Fashion-MNIST数据集,然后将其加载到内存中""" # 初始化变换列表, ToTensor变换用于将PIL图像转换为张量 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())) def get_dataloader_workers(): #@save """使用4个进程来读取数据""" return 4 if __name__ == '__main__': 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 ```