这章感觉没什么需要特别记住的东西,感觉忘了回来翻一翻代码就好。
用符号标识的矩阵 \(\boldsymbol{X} \in \mathbb{R}^{n\times d}\) 可以很方便地引用整个数据集中的 \(n\) 个样本。其中 \(\boldsymbol{X}\) 地每一行是一个样本,每一列是一种特征。
对于特征集合 \(\boldsymbol{X}\),预测值 \(\hat{\boldsymbol{y}} \in \mathbb{R}^n\) 可以通过矩阵-向量乘法表示为
然后求和的过程使用广播机制。另外,即使确信特征与标签的潜在关系是线性的,也会加入一个噪声项以考虑观测误差带来的影响。
这里采用的损失函数为平方误差函数。当样本 \(i\) 的预测值为 \(\hat{y}^{(i)}\),其相应的真实标签为 \(y^{(i)}\) 时,平方误差可以定义为:
这里的系数 \(\frac{1}{2}\) 的目的是为了求导后常数为 \(1\)。
因此,整个数据集上的损失均值为:
最后在训练模型时要找一组参数 \((\boldsymbol{w}^*, b^*)\) ,最小化总损失,即如:
这里原书写的不是很清楚。具体合并大概是
是这样合并的,然后问题就转化为最小化 \(||\boldsymbol{y} - \boldsymbol{Xw}||^2\),然后令损失关于 \(\boldsymbol{w}\) 的导数设为 \(0\),那么有解析解:
当然了,一般的深度学习问题也没有解析解能给你求出来(233)。
在每次需要计算更新的时候随机抽取一小批样本,这种变体叫做小批量随机梯度下降(minibatch stochastic gradient descent)。
大致可以写作如下公式:
其中,\(|B|\) 是 batch size,\(\eta\) 是学习率。
由于在统计学中,推断(inference)更多地表示基于数据集估计参数,所以请尽量将给定特征的情况下估计目标的过程称为预测。
先定义一下 Timer 类,这个类可以丢进小本本里。
class Timer:
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()
然后用下面的 python 循环加法和 tensor 的向量加法比较。可以发现,尽量使用 PyTorch 向量化后的 tensor 进行运算。不得不感慨一下 python 是真的慢啊,即使是 tensor 加法也还要比 C++ 慢(tensor 的基础运算应该就是拿 C++ 实现的)。这也侧面证明向量加法在 CPU 环境下应该没有涉及到应用多核。
n = 10000
a = torch.ones(n)
b = torch.ones(n)
c = torch.zeros(n)
timer = Timer()
for i in range(n):
c[i] = a[i] + b[i]
f'{timer.stop():.5f} sec'
# '0.09908 sec'
timer.start()
d = a + b
f'{timer.stop():.5f} sec'
'0.00035 sec'
正态分布概率密度函数如下:
然后本书假设观测中包含的噪声服从正态分布。噪声正态分布如:\(y = \boldsymbol{w}^T\boldsymbol{x} + b + \epsilon\),其中,\(\epsilon \sim N(0, \sigma^2)\)。
通过给定 \(\boldsymbol{x}\) 观测到特定的 \(y\) 的似然为:
那么参数 \(\boldsymbol{w}\) 和 b 的最优值是使整个数据集的似然最大的值:
等价于最小化负对数似然:
要让上式最小,即让最后一项最小。因此在有高斯噪声的假设下,最小化均方误差等价于对线性模型的极大似然估计。
这节就不详细写了,有一些代码里感觉有意思的点写在这里好了。
y = x[torch.tensr([1, 2, 5])] 来获得一个只包含 \(x_1, x_2, x_5\) 的 \(\boldsymbol{y}\)。优化算法代码如下:
def sgd(params, lr, batch_size):
with torch.no_grad():
for param in params:
param -= lr * param.grad / batch_size
param.grad.zero_()
也就是说本质上是让它在不计算梯度的情况下,更新 param,然后让它的梯度更新成零。
训练用的代码为:
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):
l = loss(net(X, w, b), y)
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}')
这里有一大堆没定义的东西在书中前文提到了,但是我这里仅提供训练代码仅供示意。
linreg(X, w, b) 表示用 \(\boldsymbol{X}, \boldsymbol{w}, b\) 计算 \(\hat{\boldsymbol{y}}\)squared_loss(y_hat, y) 为均方损失features 和 labels 是数据。data_iter 是一个生成器,负责迭代 batch_size 大小的数据。练习中问了个很有趣的问题:
如果将权重初始化为零,会发生什么?算法仍然有效吗?
参考文章 谈谈神经网络权重为什么不能初始化为0,来回答这一问题。
如同书中只有一层线性层的时候:
由于
代入,有
求导数,有
那么在第一次求导的时候,得
那么,导数不为 \(0\),就确实对于算法没有影响。
但是如果不是只有一层线性层的话:
不妨假设此时有两层线性层。第一层 \(\boldsymbol{W^{(0)} \in \mathbb{R}^{2 \times 2}}\),第二层 \(\boldsymbol{W}^{(1)} \in \mathbb{R}^2\)。过第一个线性层后输出的值就与输入 \(\boldsymbol{X}\) 无关了,那么再过第二个线性层后得到的结果就仅与第一个线性层的偏置以及第二个线性层有关了。那么,第二个线性层的权重关于第一个线性层权重的 Jacobi 矩阵是什么样子的呢?由于 \(y_{i} = (\sum_{j=1}^n w_{ij} x_j) + b\),因此 \(\frac{\mathrm{d}y_i}{\mathrm{d}x_j} = w_{ij} = 0\)。所以该矩阵是 \(0\) 矩阵,因此无法更新第一个线性层。然后又由于过了第一个线性层就与输入 \(\boldsymbol{X}\) 无关,所以权重不可以初始化为 \(0\)。
仍然不全抄,只写一些有趣的代码放在这里。
torch.utils.data.DataLoader 返回的是一个可迭代对象(Iterable)而不是一个迭代器(Iterator)。
可以用 iter() 函数构造 Python 迭代器,并使用 next() 函数从迭代器中获取第一项。如下所示:
next(iter(torch.utils.data.DataLoader(dataset, batch_size, shuffle=is_train)))
然后用 net = nn.Sequential(nn.Linear(2, 1)) 得到模型,那么,除了新写一个类初始化参数外,怎么初始化第一层的参数呢?通过 net[0] 选择网络中第一层,然后使用 weight.data 和 bias.data 方法来访问参数。
net[0].weight 是 torch.nn.parameter.Parameter 类的实例,这个类很有趣。一种被视为模块参数的张量。Parameter 是 Tensor 的子类,当与 Module 类一起使用时具有非常特殊的属性:当 Parameter 类被分配为 Module 类的属性时,它们会自动添加到其参数列表中,并将出现在例如在 parameters() 迭代器中。但是分配一个 Tensor 就没有这样的效果。调用 net[0].weight 返回:
net[0].weight
# Parameter containing:
# tensor([[-0.3418, -0.5904]], requires_grad=True)
调用 net[0].weight.data 返回 tensor,这说明它们仅仅是张量:
net[0].weight.data
# tensor([[-0.3418, -0.5904]])
还可以使用替换方法 normal_ 和 fill_ 来重写参数值。这两个方法是在 torch.tensor() 里面的方法。
net[0].weight.data.normal_(0, 0.01)
net[0].bias.data.fill_(0)
损失函数 nn.MSELoss() 及优化算法 torch.optim.SGD(net.parameters(), lr=0.03)。这里 net.parameters() 是个生成器,同时也是一个特殊的迭代器。输出一下它:
loss = nn.MSELoss()
trainer = torch.optim.SGD(net.parameters(), lr=0.03)
print(net.parameters())
# <generator object Module.parameters at 0x716c1cf61150>
下面是训练代码,这段代码先把梯度清零再做反向传播,证明把梯度清零不会把 Jacobi 矩阵之类的中间状态清理掉,实践中极其不推荐像书中这么写,最好还是在求 loss 之前就清理梯度:
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()
l = loss(net(features), labels)
print(f'epoch {epoch + 1}, loss {l:f}')
"""
epoch 1, loss 0.000226
epoch 2, loss 0.000103
epoch 3, loss 0.000103
"""
希望在对硬性类别分类的同时使用软性带有概率的模型。
本章介绍了表示分类数据的简单方法:独热编码(one-hot encoding)。独热编码是一个向量,它的分量和类别一样多。类别对应的分量设置为 \(1\),其他所有分量设置为 \(0\)。
本节的网络架构仍为线性层,这里有和输出一样多的仿射函数,向量形式记作 \(\boldsymbol{o} = \boldsymbol{Wx} + \boldsymbol{b}\)。具有 \(d\) 个输入和 \(q\) 个输出的全连接层,参数开销为 \(O(dq)\),但是论文 Beyond Fully-Connected Layers with Quaternions: Parameterization of Hypercomplex Multiplications with \(\frac{1}{n}\) Parameters 提及可以将具有 \(d\) 个输入和 \(q\) 个输出的全连接层的成本减少到 \(O(dq/n)\),其中超参数 \(n\) 可以设定,以在实际应用中在参数节省和模型有效性之间进行平衡。(完全没读这论文说了啥233)
Softmax 函数可以表示为:
其中,
文中说,尽管 softmax 是一个非线性函数,但 softmax 回归的输出仍然由输入特征的仿射变换决定。因此,softmax 回归是一个线性模型(linear model)。
假设数据集 \(\{ \boldsymbol{X}, \boldsymbol{Y}\}\) 具有 \(n\) 个样本,其中索引 \(i\) 的样本由特征向量 \(\boldsymbol{x}^{(i)}\) 和独热标签向量 \(\boldsymbol{y}^{(i)}\) 组成。因此可以将估计值与实际值比较:
最大化似然仍然是等价于熟悉的最小化负对数似然:
其中,对于任何标签 \(\boldsymbol{y}\) 和模型预测 \(\hat{\boldsymbol{y}}\),损失函数为:
上式一般被称为交叉熵损失。
利用 softmax 定义可得:
考虑相对于任何未规范化的预测 \(o_j\) 的导数,可以得到:
不妨设 \(s_i = \text{softmax} (\boldsymbol{o})_i\),再求二阶导:
课后题还要求 \(\text{softmax} (\boldsymbol{o})\) 给出的分布方差,并和二阶导匹配起来,所以有
上面式子里除以 \(q-1\) 是符合统计学中无偏估计的做法。当然和除以 \(q\) 差别也不太大。
信息论的基本想法是一个不太可能的事件居然发生了,要比一个非常可能的事件发生,能提供更多的信息。如果要通过这种基本想法来量化信息,可以遵循以下三个点:
为了满足上述 \(3\) 个性质,因此定义一个事件 \(x\) 的自信息(self-information)为
这里定义的 \(I(x)\) 单位是奈特(nat)。一奈特是以 \(\frac{1}{e}\) 的概率观测到一个事件时获得的信息量。
自信息只处理单个的输出。可以用香农熵对整个概率分布中不确定性总量进行量化:
这个也可以记作 \(H(P)\)。一个分布的香农熵是指遵循这个分布的事件所产生的期望信息总量。
如果对同一个随机变量 \(x\) 有两个单独的概率分布 \(P(x)\) 和 \(Q(x)\),可以使用KL散度(Kullback-Leibler divergence)来衡量这两个分布的差异:
在离散型变量的情况下,KL 散度衡量的是,当使用一种被设计成能够使得概率分布 \(Q\) 产生的消息的长度最小的编码,发送包含由概率分布 \(P\) 产生的符号的消息时,所需要的额外信息量。
KL 散度有一些有用的性质如下:
由于上述两个性质,因此它经常被用作分布之间的某种距离。然而,它并不满足交换性,即 \(D_{KL}(P||Q) \not = D_{KL}(Q||P)\)。
假设此时有一个分布 \(p(x)\),并且希望用另一个分布 \(q(x)\) 来近似它,那么就可以选择最小化 \(D_{KL}(p||q)\) 或者最小化 \(D_{KL}(q||p)\)。这其中选择哪一个 KL 散度是取决于问题的。选择 \(D_{KL}(p||q)\) 的目的是为了让近似分布 \(q\) 在真实分布 \(p\) 放置高概率的所有地方都放置高概率,而选择 \(D_{KL}(q||p)\) 的目的是为了让近似分布 \(q\) 在真实分布 \(p\) 放置低概率的所有地方都很少放置高概率。
一个和 KL 散度密切联系的量是交叉熵,即 \(H(P,Q) = H(P) + D_{KL}(P||Q) = -\mathbb{E}_{x \sim P} \log Q(x)\)。因此针对 \(Q\) 最小化交叉熵等价于最小化 KL 散度。
本章其实没啥亮点,有趣的内容稍微写一下:
一个用来展示图片以及标题的函数,有 num_rows 行 num_cols 列。
def show_images(imgs, num_rows, num_cols, titles=None, scale=1.5):
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:
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
此外,num_workers 这个参数表示了使用子进程读取数据的个数。如果调小 batch_size 的话即使是 CPU 运行的代码速度也会减慢,在 num_workers=4 的时候,测试时间长度如下表:
| batch_size | 时间 |
|---|---|
| 1 | 117.74 |
| 4 | 28 |
| 256 | 3.11 |
仍然是有趣的内容:
torch.normal() 能够返回一个其中所有值都符合正态分布的 tensor。
Accumulator 类对多个变量进行累加。
class Accumulator:
def __init__(self, n):
self.data = [0.0] * n
def add(self, *args):
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]
还有一个可以在动画中绘制图表的实用程序类 Animator。此函数仅能在 notebook 中使用。
import torch
from IPython import display
from d2l import torch as d2l
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, ]
# 使用lambda函数捕获参数
self.config_axes = lambda: d2l.set_axes(
self.axes[0], xlabel, ylabel, xlim, ylim, xscale, yscale, legend)
self.X, self.Y, self.fmts = None, None, fmts
def add(self, x, y):
# 向图表中添加多个数据点
if not hasattr(y, "__len__"):
y = [y]
n = len(y)
if not hasattr(x, "__len__"):
x = [x] * n
if not self.X:
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)):
if a is not None and b is not None:
self.X[i].append(a)
self.Y[i].append(b)
self.axes[0].cla()
for x, y, fmt in zip(self.X, self.Y, self.fmts):
self.axes[0].plot(x, y, fmt)
self.config_axes()
display.display(self.fig)
display.clear_output(wait=True)
这个类应该怎么用呢?见下方代码
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_loss, train_acc)
animator.add(epoch + 1, train_metrics + (test_acc,))
如何在类外给所有线性层初始化?可以使用 nn.Module.apply(fn) 可以做到。它的本来作用是递归地对所有子模块(包括自己)做相同的操作。如:
net = nn.Sequential(nn.Flatten(), nn.Linear(784, 10))
def init_weights(m):
if type(m) == nn.Linear:
nn.init.normal_(m.weight, std=0.01)
net.apply(init_weights)
即对 net 中所有 Linear 层初始化参数。
为防止由于指数函数导致的上溢出,因此再继续 softmax 运算之前,先从所有 \(o_k\) 中减去 \(\max (o_k)\),事实上这样不会改变 softmax 的返回值:
又由于有些 \(\exp(o_j - max(o_k))\) 具有较大的负值,可能导致求完指数函数后直接下溢出归零,并使得 \(\log(\hat{y}_j)\) 的值变为负无穷大。反向传播几步之后,可能会发现满屏幕的 nan。因此将交叉熵和 softmax 操作结合在一起:
这些具体落实到代码上是模型过完最后一个线性层不要做 softmax 操作,直接往 PyTorch 的 CrossEntropyLoss 里面丢就行了,因为它已经结合好了。
目录前言滤波电路科普主要分类实际情况单位的概念常用评价参数函数型滤波器简单分析滤波电路构成低通滤波器RC低通滤波器RL低通滤波器高通滤波器RC高通滤波器RL高通滤波器部分摘自《LC滤波器设计与制作》,侵权删。前言最近需要学习放大电路和滤波电路,但是由于只在之前做音乐频谱分析仪的时候简单了解过一点点运放,所以也是相当从零开始学习了。滤波电路科普主要分类滤波器:主要是从不同频率的成分中提取出特定频率的信号。有源滤波器:由RC元件与运算放大器组成的滤波器。可滤除某一次或多次谐波,最普通易于采用的无源滤波器结构是将电感与电容串联,可对主要次谐波(3、5、7)构成低阻抗旁路。无源滤波器:无源滤波器,又称
最近在学习CAN,记录一下,也供大家参考交流。推荐几个我觉得很好的CAN学习,本文也是在看了他们的好文之后做的笔记首先是瑞萨的CAN入门,真的通透;秀!靠这篇我竟然2天理解了CAN协议!实战STM32F4CAN!原文链接:https://blog.csdn.net/XiaoXiaoPengBo/article/details/116206252CAN详解(小白教程)原文链接:https://blog.csdn.net/xwwwj/article/details/105372234一篇易懂的CAN通讯协议指南1一篇易懂的CAN通讯协议指南1-知乎(zhihu.com)视频推荐CAN总线个人知识总
深度学习部署:Windows安装pycocotools报错解决方法1.pycocotools库的简介2.pycocotools安装的坑3.解决办法更多Ai资讯:公主号AiCharm本系列是作者在跑一些深度学习实例时,遇到的各种各样的问题及解决办法,希望能够帮助到大家。ERROR:Commanderroredoutwithexitstatus1:'D:\Anaconda3\python.exe'-u-c'importsys,setuptools,tokenize;sys.argv[0]='"'"'C:\\Users\\46653\\AppData\\Local\\Temp\\pip-instal
我完全不是程序员,正在学习使用Ruby和Rails框架进行编程。我目前正在使用Ruby1.8.7和Rails3.0.3,但我想知道我是否应该升级到Ruby1.9,因为我真的没有任何升级的“遗留”成本。缺点是什么?我是否会遇到与普通gem的兼容性问题,或者甚至其他我不太了解甚至无法预料的问题? 最佳答案 你应该升级。不要坚持从1.8.7开始。如果您发现不支持1.9.2的gem,请避免使用它们(因为它们很可能不被维护)。如果您对gem是否兼容1.9.2有任何疑问,您可以在以下位置查看:http://www.railsplugins.or
如何学习ruby的正则表达式?(对于假人) 最佳答案 http://www.rubular.com/在Ruby中使用正则表达式时是一个很棒的工具,因为它可以立即将结果可视化。 关于ruby-我如何学习ruby的正则表达式?,我们在StackOverflow上找到一个类似的问题: https://stackoverflow.com/questions/1881231/
深度学习12.CNN经典网络VGG16一、简介1.VGG来源2.VGG分类3.不同模型的参数数量4.3x3卷积核的好处5.关于学习率调度6.批归一化二、VGG16层分析1.层划分2.参数展开过程图解3.参数传递示例4.VGG16各层参数数量三、代码分析1.VGG16模型定义2.训练3.测试一、简介1.VGG来源VGG(VisualGeometryGroup)是一个视觉几何组在2014年提出的深度卷积神经网络架构。VGG在2014年ImageNet图像分类竞赛亚军,定位竞赛冠军;VGG网络采用连续的小卷积核(3x3)和池化层构建深度神经网络,网络深度可以达到16层或19层,其中VGG16和VGG
文章目录1、自相关函数ACF2、偏自相关函数PACF3、ARIMA(p,d,q)的阶数判断4、代码实现1、引入所需依赖2、数据读取与处理3、一阶差分与绘图4、ACF5、PACF1、自相关函数ACF自相关函数反映了同一序列在不同时序的取值之间的相关性。公式:ACF(k)=ρk=Cov(yt,yt−k)Var(yt)ACF(k)=\rho_{k}=\frac{Cov(y_{t},y_{t-k})}{Var(y_{t})}ACF(k)=ρk=Var(yt)Cov(yt,yt−k)其中分子用于求协方差矩阵,分母用于计算样本方差。求出的ACF值为[-1,1]。但对于一个平稳的AR模型,求出其滞
写在之前Shader变体、Shader属性定义技巧、自定义材质面板,这三个知识点任何一个单拿出来都是一套知识体系,不能一概而论,本文章目的在于将学习和实际工作中遇见的问题进行总结,类似于网络笔记之用,方便后续回顾查看,如有以偏概全、不祥不尽之处,还望海涵。1、Shader变体先看一段代码......Properties{ [KeywordEnum(on,off)]USL_USE_COL("IsUseColorMixTex?",int)=0 [Toggle(IS_RED_ON)]_IsRed("IsRed?",int)=0}......//中间省略,后续会有完整代码 #pragmamulti_c
TCL脚本语言简介•TCL(ToolCommandLanguage)是一种解释执行的脚本语言(ScriptingLanguage),它提供了通用的编程能力:支持变量、过程和控制结构;同时TCL还拥有一个功能强大的固有的核心命令集。TCL经常被用于快速原型开发,脚本编程,GUI和测试等方面。•实际上包含了两个部分:一个语言和一个库。首先,Tcl是一种简单的脚本语言,主要使用于发布命令给一些互交程序如文本编辑器、调试器和shell。由于TCL的解释器是用C\C++语言的过程库实现的,因此在某种意义上我们又可以把TCL看作C库,这个库中有丰富的用于扩展TCL命令的C\C++过程和函数,所以,Tcl是
按照目前的情况,这个问题不适合我们的问答形式。我们希望答案得到事实、引用或专业知识的支持,但这个问题可能会引发辩论、争论、投票或扩展讨论。如果您觉得这个问题可以改进并可能重新打开,visitthehelpcenter指导。关闭9年前。我来自C、php和bash背景,很容易学习,因为它们都有相同的C结构,我可以将其与我已经知道的联系起来。然后2年前我学了Python并且学得很好,Python对我来说比Ruby更容易学。然后从去年开始,我一直在尝试学习Ruby,然后是Rails,我承认,直到现在我还是学不会,讽刺的是那些打着简单易学的烙印,但是对于我这样一个老练的程序员来说,我只是无法将它