fast.ai 机器学习笔记(三)(1)https://developer.aliyun.com/article/1482650
损失函数[1:02:08]
所以我们需要一些关于“尽可能好”的定义。那个东西的一般术语叫做损失函数。所以损失函数是一个函数,如果这个函数更低,那么就更好。就像随机森林一样,我们有信息增益的概念,我们得选择用什么函数来定义信息增益,我们主要看的是均方根误差。大多数机器学习算法我们称之为类似于“损失”的东西。所以损失是我们如何评分我们有多好的东西。最终,我们将计算损失对我们正在乘以的权重矩阵的导数,以找出如何更新它。
我们将使用一种称为负对数似然损失(NLLLoss
)的东西。负对数似然损失也被称为交叉熵,它们实际上是一样的。有两个版本,一个称为二元交叉熵或二元负对数似然,另一个称为分类交叉熵。它们是一样的,一个是当你只有一个零或一个依赖时,另一个是如果你有猫、狗、飞机或马,或者 0、1、到 9 等等。所以这里我们有交叉熵的二元版本:
def binary_loss(y, p): return np.mean(-(y * np.log(p) + (1-y)*np.log(1-p)))
所以这里的定义是-(y * np.log(p) + (1-y)*np.log(1-p))
。我认为理解这个定义的最简单方法可能是看一个例子。假设我们试图预测猫和狗。1 代表猫,0 代表狗。所以这里,我们有猫、狗、狗、猫([1, 0, 0, 1]
)。这是我们的预测([0.9, 0.1, 0.2, 0.8]
)。我们说 90%确定是猫,90%确定是狗,80%确定是狗,80%确定是猫。所以我们可以通过调用我们的函数来计算二元交叉熵。
对于第一个,我们有y=1,p=0.9(即(1 * np.log(0.9)
,因为第二项被跳过了)。对于第二个,第一部分被跳过(乘以 0),第二部分将是(1-0)*np.log(0.9)
。换句话说,这个的第一部分和第二部分将给出完全相同的数字,这是有道理的,因为第一个我们说我们对是猫 90%有信心,而实际上是,第二个我们说我们对是狗 90%有信心,而实际上是。所以在每种情况下,损失都来自于我们本可以更有信心。所以如果我们说我们 100%有信心,损失将为零。
acts = np.array([1, 0, 0, 1]) preds = np.array([0.9, 0.1, 0.2, 0.8]) binary_loss(acts, preds) ''' 0.164252033486018 '''
所以让我们在 Excel 中看一下。从顶部行开始:
- 我们的预测
- 实际/目标值
- 1 减去实际/目标值
- 我们的预测的对数
- 我们的预测的对数的 1 减
- 总和
如果你仔细想一想,我希望你在这一周内考虑一下,你可以用一个 if 语句来替换这个(np.mean(-(y * np.log(p) + (1-y)*np.log(1-p)))
),而不是 y,因为 y 总是 1 或 0,所以它只会使用np.log(p)
或(np.log(1-p)
中的一个。所以你可以用一个 if 语句来替换这个。所以我希望你在这一周内尝试用一个 if 语句来重写这个。
然后看看你是否能将其扩展为分类交叉熵。所以分类交叉熵的工作方式是这样的。假设我们试图预测 3、6、7、2。
所以如果我们试图预测 3,而实际上预测了 5,或者试图预测 3,却意外地预测了 9。5 而不是 3 并不比 9 而不是 3 更好。所以我们实际上不会说实际数字有多远。我们会用不同的方式表达它。换句话说,如果我们试图预测猫、狗、马和飞机。猫和马之间有多远?所以我们会稍微不同地表达这些。与其把它看作是一个 3,不如把它看作是一个在第三个位置上有一个 1 的向量:
不要把它看作是一个 6,让我们把它看作是一个零向量,第 6 个位置是 1。换句话说,独热编码。所以让我们对我们的因变量进行独热编码。这样现在,我们不再试图预测一个数字,而是预测十个数字。让我们预测它是 0 的概率,它是 1 的概率,依此类推。
所以让我们假设我们正在预测 2,这里是我们的分类交叉熵[1:07:50]。所以它只是在说,这个预测是否正确,有多大偏差,依此类推,对每一个进行计算,然后将它们全部加起来。分类交叉熵与二元交叉熵是相同的。我们只需要将它们加起来跨越所有的类别。
所以尝试将 Python 中的二元交叉熵函数转换为 Python 中的分类交叉熵。也许创建带有 if 语句的版本和带有求和和乘积的版本。
这就是为什么在我们的 PyTorch 中,我们将这个矩阵的输出维度设置为 10,因为当我们将一个有 10 列的矩阵相乘时,我们将得到一个长度为 10 的结果,这正是我们想要的[1:08:35]。我们想要有 10 个预测。
这就是我们正在使用的损失函数。然后我们可以拟合模型,它会遍历每个图像,这么多次(epochs
)。所以在这种情况下,它只是查看每个图像一次,并且会根据这些梯度稍微更新那个权重矩阵中的值。
所以一旦我们训练好了,我们就可以用这个模型(net
)在验证集(md.val_dl
)上进行predict
。
preds = predict(net, md.val_dl)
现在这会输出一个 10,000 乘以 10 的东西。我们有 10,000 张图像进行验证,实际上每张图像进行 10 次预测。换句话说,每一行都是它是 0 的概率,它是 1 的概率,它是 2 的概率,依此类推。
preds.shape ''' (10000, 10) '''
Argmax [1:10:22]
在数学中,有一个我们经常做的操作叫做argmax
。当我说它很常见时,很有趣的是在高中,我从来没有见过 argmax。大一,我也从来没有见过 argmax。但不知何故,大学毕业后,一切都与 argmax 有关。所以有些事情在学校里似乎并没有真正教,但实际上它非常关键。argmax 既是数学中的一个东西(它只是完整地写出argmax
),它在 numpy 中,在 PyTorch 中,非常重要。它的作用是让我们拿这些预测数组,然后在给定的轴上(axis=1
- 记住,轴 1 是列),就像 Chis 所说的,对于每一行的 10 个预测,让我们找出哪个预测值最高,然后返回不是那个值(如果只是说 max,它会返回值),argmax 返回值的索引。所以通过说argmax(axis=1)
,它将返回实际上是数字本身的索引。所以让我们取前 5 个:
preds.argmax(axis=1)[:5] ''' array([3, 8, 6, 9, 6]) '''
这就是我们如何将我们的概率转换回预测的方法。我们保存下来并称之为preds
。然后我们可以说preds
何时等于真实值。这将返回一个布尔数组,我们可以将其视为 1 和 0,一堆 1 和 0 的平均值就是平均值。这给了我们 91.8%的准确率。
preds = preds.argmax(1) np.mean(preds == y_valid) ''' 0.91820000000000002 '''
所以你想要能够复制你看到的数字,这里就是。这里是我们的 91.8%。
所以当我们训练这个模型时,最后一件事告诉我们的是我们要求的任何指标,我们要求的是准确率。然后在此之前,我们得到了训练集的损失。损失又是我们要求的任何损失(nn.NLLLoss()
),第二件事是验证集的损失。PyTorch 不使用损失这个词,他们使用准则这个词。所以你会在这里看到crit
,这就是准则等于损失。这就是我们想要使用的损失函数,他们称之为准则。同样的事情。所以np.mean(preds == y_valid)
就是我们如何重新创建准确率的方法。
plots(x_imgs[:8], titles=preds[:8])
因此,现在我们可以继续绘制八幅图像以及它们的预测。对于我们预测错误的那些,您可以看到它们为什么错误。数字 4 的图像非常接近数字 9。它只是在顶部少了一个小交叉。数字 3 非常接近数字 5。它在顶部有一点额外的部分。所以我们已经开始了。到目前为止,我们实际上还没有创建一个深度神经网络。我们实际上只有一个层。因此,我们实际上所做的是创建了一个逻辑回归。逻辑回归就是我们刚刚构建的内容,您可以尝试使用 sklearn 的逻辑回归包来复制这个过程。当我这样做时,我得到了类似的准确性,但这个版本运行得更快,因为它在 GPU 上运行,而 sklearn 在 CPU 上运行。因此,即使对于像逻辑回归这样的东西,我们也可以使用 PyTorch 非常快速地实现它。
问题:当我们创建我们的网络时,我们必须执行.cuda()
。如果不这样做会有什么后果?它只是不会快速运行。它将在 CPU 上运行。
问题:为什么我们必须先进行线性操作,然后再进行非线性操作?简短的答案是因为这是通用逼近定理所说的结构,可以为任何函数形式提供任意精确的函数。长答案是通用逼近定理为何有效的细节。另一个简短答案是,这就是神经网络的定义。因此,神经网络的定义是一个线性层,后跟一个激活函数,再后跟一个线性层,再后跟一个激活函数,依此类推。我们在深度学习课程中会更详细地讨论这一点,但就此目的而言,知道它有效就足够了。到目前为止,当然,我们实际上还没有构建一个深度神经网络。我们只是构建了一个逻辑回归。因此,在这一点上,如果你考虑一下,我们所做的就是将每个输入像素乘以每个可能结果的权重。因此,我们基本上是在说,平均而言,数字 1 具有这些像素点亮。数字 2 具有这些像素点亮。这就是为什么它不是非常准确的原因。这不是现实生活中数字识别的工作方式。但到目前为止,这就是我们构建的全部内容。
问题:所以你一直在说这个通用逼近定理。你有定义过吗?是的,但让我们再次讨论一下,因为这值得谈论。因此,Michael Nielsen 有一个名为神经网络与深度学习的优秀网站。他的第四章现在实际上很有名,其中他通过演示神经网络可以以足够大的规模逼近任何其他函数,只要它足够大,来详细介绍这一点。我们在深度学习课程中详细讨论了这一点,但基本的诀窍是,他展示了通过几个不同的数字,您基本上可以使这些事物创建小盒子,您可以将盒子上下移动,您可以将它们移动,您可以将它们连接在一起,最终基本上可以创建像塔一样的连接,您可以用来逼近任何类型的表面。
因此,这基本上就是诀窍。因此,我们所需要做的就是,鉴于此,找到神经网络中每个线性函数的参数。因此,找到每个矩阵中的权重。到目前为止,我们只有一个矩阵,我们只是构建了一个简单的逻辑回归。
问题:我只是想确认一下,当你展示被错误分类的图像的例子时,它们看起来是矩形的,所以只是在渲染时,像素被不同地缩放了吗?它们是 28 乘 28 的。我认为它们看起来是矩形的,因为它们顶部有标题。Matplotlib 经常会调整它认为的黑色与白色以及具有不同大小轴等的东西。因此,有时你必须小心一点。
自己定义逻辑回归
希望现在这会更有意义,因为我们要深入一层,定义逻辑回归,而不使用nn.Sequential
、nn.Linear
或nn.LogSoftmax
。因此,我们将几乎所有的层定义都从头开始做。为了做到这一点,我们将不得不定义一个 PyTorch 模块。PyTorch 模块基本上是一个神经网络或神经网络中的一层,这实际上是一个强大的概念。基本上,任何可以像神经网络一样行为的东西本身可以成为另一个神经网络的一部分。这就是我们如何构建特别强大的架构,结合了许多其他部分。
def get_weights(*dims): return nn.Parameter(torch.randn(dims)/dims[0]) def softmax(x): return torch.exp(x)/(torch.exp(x).sum(dim=1)[:,None]) class LogReg(nn.Module): def __init__(self): super().__init__() self.l1_w = get_weights(28*28, 10) # Layer 1 weights self.l1_b = get_weights(10) # Layer 1 bias def forward(self, x): x = x.view(x.size(0), -1) x = (x @ self.l1_w) + self.l1_b # Linear Layer x = torch.log(softmax(x)) # Non-linear (LogSoftmax) Layer return x
因此,要创建一个 PyTorch 模块,只需创建一个 Python 类,但它必须继承自nn.Module
。因此,除了继承之外,这是我们已经在面向对象中看到的所有概念。基本上,如果你在这里(在类名后面)放入括号中的内容,意味着我们的类会免费获得这个类的所有功能。这被称为子类化。因此,我们将获得 PyTorch 作者提供的神经网络模块的所有功能,然后我们将添加额外的功能。当你创建一个子类时,有一件重要的事情你需要记住,那就是当你初始化你的类时,你首先必须初始化超类。因此,超类是nn.Module
。因此,在你开始添加你的部分之前,必须先构建nn.Module
。这就像你可以复制并粘贴到你的每一个模块中的东西。你只需说super().__init__()
。这意味着首先构造超类。
这样做之后,我们现在可以定义我们的权重和偏差。我们的权重是权重矩阵。这是我们将要用来乘以我们的数据的实际矩阵。正如我们讨论过的,它将有 28 乘 28 行和 10 列。这是因为如果我们取一个我们已经展平成一个 28 乘 28 长度向量的图像,然后我们可以将它乘以这个权重矩阵,得到一个长度为 10 的向量,然后我们可以将其视为一组预测。这就是我们的权重矩阵。现在问题是我们不只是想要y = ax。我们想要y = ax + b。因此,在神经网络中,+ b被称为偏差。因此,除了定义权重,我们还将定义偏差。由于这个get_weights(28*28, 10)
将为每个图像输出长度为 10 的东西。这意味着我们需要创建一个长度为 10 的向量作为我们的偏差。换句话说,对于每个 0、1、2、3 直到 9,我们将有一个不同的加b。因此,我们有我们的数据矩阵,它的长度是 10,000 乘以 28 乘以 28。然后我们有我们的权重矩阵,它是 28 乘以 28 乘以 10。因此,如果我们将它们相乘,我们将得到一个大小为 10,000 乘以 10 的东西。
然后我们想要添加我们的偏差,如下所示:
我们以后会学到更多关于这个的知识,但是当我们像这样添加一个向量时,基本上它会被添加到每一行。因此,那个偏差将被添加到每一行。因此,我们首先定义这些。为了定义它们,我们创建了一个名为get_weights
的小函数,它基本上只是创建一些正态分布的随机数。torch.randn
返回一个填充有正态分布随机数的张量。
然而,我们必须要小心。当我们进行深度学习时,比如以后添加更多的线性层。想象一下,如果我们有一个矩阵,平均倾向于增加我们输入的大小。如果我们将其乘以许多相同大小的矩阵,它会使数字变得越来越大,指数级增长。或者如果我们让它们变小一点呢?它会使它们变得越来越小,指数级减小。因为深度网络应用了许多线性层,如果平均而言它们导致的结果比起始值稍微大一点或稍微小一点,那么它将指数级地放大这种差异。因此,我们需要确保权重矩阵的大小适当,使得输入到它的(更具体地说,输入的均值)不会改变。
事实证明,如果你使用正态分布的随机数并除以权重矩阵中的行数,这种随机初始化可以保持你的数字在大约正确的范围内。因此,这个想法是,如果你做过线性代数,基本上如果第一个特征值大于 1 或小于 1,它会导致梯度变得越来越大或越来越小。这就是梯度爆炸。我们将在深度学习课程中更多地讨论这个问题,但如果你感兴趣,你可以查看Kaiming He 初始化,并阅读有关这个概念的所有内容,但现在,知道如果你使用这种类型的随机数生成(即torch.randn(dims)/dims[0]
),你将得到行为良好的随机数。你将从均值为 0 标准差为 1 的输入开始。一旦你通过这组随机数,你仍然会得到大约均值为 0 标准差为 1 的东西。这基本上就是目标。
PyTorch 的一个好处是你可以玩弄这些东西[1:25:44]。所以试一试。每当你看到一个函数被使用时,运行它并查看一下。所以你会发现,它看起来很像 numpy,但它不返回一个 numpy 数组。它返回一个张量。
事实上,现在我在进行 GPU 编程。
调用.cuda()
,现在它在 GPU 上运行。
我在 GPU 上非常快地将那个矩阵乘以 3!这就是我们如何使用 PyTorch 进行 GPU 编程。
正如我们所说,我们创建了一个 28*28 乘以 10 的权重矩阵,另一个只是 10 的秩 1 偏差[1:26:29]。我们必须将它们设为参数。这基本上告诉 PyTorch 在执行 SGD 时要更新哪些内容。这是一个非常微小的技术细节。
创建了权重矩阵后,我们定义了一个名为forward
的特殊方法。这是一个特殊的方法,而在 PyTorch 中,名称 forward 具有特殊含义。在 PyTorch 中,称为 forward 的方法是在计算层时将被调用的方法名称。因此,如果你创建了一个神经网络或一个层,你必须定义 forward,它将传递前一层的数据。我们的定义是对输入数据和权重进行矩阵乘法,并加上偏差。就是这样。这就是我们之前说的nn.Linear
时发生的事情。它为我们创建了这个东西。
不幸的是,我们并没有得到一个 28 乘以 28 的长向量。我们得到的是一个 28 行乘以 28 列的矩阵,所以我们必须将其展平。不幸的是,在 PyTorch 中,它们倾向于重新命名事物。他们将“resize”拼写为view
。所以view
意味着重塑。因此,你可以看到这里x.view(x.size(0), -1)
,我们最终得到的是一个图像数量(x.size(0)
)不变。然后我们将行替换为列,形成一个单一轴。再次,-1
的意思是尽可能长。这就是我们使用 PyTorch 展平的方法。
所以我们将其展平,进行矩阵乘法,最后进行 softmax。所以 softmax 是我们使用的激活函数。如果您查看深度学习存储库,您会发现一个名为熵示例的内容,您将在其中看到 softmax 的示例。Softmax 简单地获取我们最终层的输出,因此我们从线性层获取输出。我们所做的是对每个输出进行e的(e^)运算。
然后我们取这个数字,除以e的幂的总和。
这就是所谓的 softmax。为什么我们这样做?因为我们正在将这个(exp)除以总和,这意味着这些本身的总和必须加起来为一。这就是我们想要的。我们希望所有可能结果的概率总和为一。此外,因为我们使用e^,这意味着我们知道这些(softmax)中的每一个都在零和一之间。我们知道概率将在零和一之间。最后,因为我们使用e的幂,这意味着输入中稍大的值会变成输出中的更大值。因此,通常情况下,您会看到我的 softmax 中有一个大数和许多小数。这就是我们想要的,因为我们知道输出是一热编码的。换句话说,softmax 激活函数,softmax 非线性,是一种返回类似概率的东西的东西,其中其中一个概率更有可能是高的,其他概率更有可能是低的。我们知道这就是我们想要映射到我们的一热编码的内容,因此 softmax 是一个很好的激活函数,可以帮助神经网络更容易地映射到您想要的输出。这通常是我们想要的。当我们设计神经网络时,我们尝试提出一些小的架构调整,使其尽可能容易地匹配我们想要的输出。
这基本上就是这样。与其使用 Sequential 和nn.Linear
以及nn.LogSoftmax
,我们从头开始定义了它。现在我们可以说,就像以前一样,我们的net2
等于LogReg().cuda()
,我们可以说fit
,我们得到了几乎完全相同的输出,只是有轻微的随机偏差。
net2 = LogReg().cuda() opt=optim.Adam(net2.parameters()) fit(net2, md, n_epochs=1, crit=loss, opt=opt, metrics=metrics) ''' [ 0\. 0.32209 0.28399 0.92088] '''
所以我希望你在这一周里尝试使用torch.randn
生成一些随机张量,使用torch.matmul
开始将它们相乘,相加,尝试确保你可以自己从头开始重写 softmax。尝试玩弄一下重塑、view 等等,这样到下周你回来时就会感觉对 PyTorch 相当舒适。
如果您搜索 PyTorch 教程,您会看到PyTorch 网站上有很多很好的材料可以帮助您,向您展示如何创建张量,修改它们以及对它们进行操作。
问题:我看到前向是在每个线性层之后应用的层。
Jeremy:不完全是。前向只是模块的定义,这是我们实现 Linear 的方式。
继续:这是否意味着在每个线性层之后,您必须应用相同的函数?假设我们不能在第一层之后应用 LogSoftmax,然后在第二层之后应用其他函数,如果我们有一个多层神经网络?
Jeremy:所以通常我们这样定义神经网络:
我们只是说这里是我们想要的层的列表。您不必编写自己的前向。我们刚刚做的是说,与其这样做,不如完全不使用这些,而是自己手写所有内容。因此,您可以按任何顺序编写任意数量的层。重点是在这里,我们没有使用任何这些:
我们已经编写了自己的matmul
加偏置项,自己的 softmax,所以这只是 Python 代码。您可以在 forward 函数内编写任何您喜欢的 Python 代码来定义自己的神经网络。通常情况下,您不会自己这样做。通常您只会使用 PyTorch 提供的层,并使用.Sequential
将它们组合在一起。或者更有可能的是,您会下载一个预定义的架构并使用它。我们只是为了学习它在幕后是如何工作的。
好的,太棒了。谢谢大家!
机器学习 1:第 9 课
原文:
medium.com/@hiromi_suenaga/machine-learning-1-lesson-9-689bbc828fd2
译者:飞龙
来自机器学习课程的个人笔记。随着我继续复习课程以“真正”理解它,这些笔记将继续更新和改进。非常感谢 Jeremy 和 Rachel 给了我这个学习的机会。
学生的作品[0:00]
欢迎回到机器学习!我非常兴奋能够分享一些由旧金山大学学生在这一周内构建或撰写的惊人内容。我将向你展示的许多东西已经在互联网上广泛传播:大量的推文和帖子以及各种各样的事情发生。
用随机森林着色
由Tyler White提供
他开始说,如果我创建一个合成数据集,其中自变量是 x 和 y,因变量是颜色,会怎样。有趣的是,他向我展示了一个早期版本,那时他没有使用颜色。他只是把实际的数字放在这里。
这个东西一开始根本不起作用。一旦他开始使用颜色,它就开始运行得非常好。所以我想提一下,不幸的是我们在 USF 没有教给你的一件事是人类感知的理论,也许我们应该。因为实际上,当涉及到可视化时,最重要的事情是了解人眼或大脑擅长感知的是什么。关于这个有一个整个学术研究领域。我们最擅长感知的事情之一就是颜色的差异。这就是为什么当我们看这张他创建的合成数据的图片时,你可以立刻看到,哦,这里有四个较浅红色的区域。他所做的是,他说好,如果我们尝试创建一个关于这个合成数据集的机器学习模型,具体来说他创建了一棵树。而酷的事情是你实际上可以绘制这棵树。所以在他创建了这棵树之后,他在 matplotlib 中完成了所有这些。Matplotlib 非常灵活。他实际上绘制了树的边界,这已经是一个相当不错的技巧——能够实际绘制这棵树。
然后他做了更聪明的事情,他说好的,那么树做出了什么预测?嗯,这是每个区域的平均值,所以为了做到这一点,我们实际上可以绘制平均颜色。这实际上相当漂亮。这是树所做的预测。现在这里变得非常有趣。你可以,如你所知,通过重新采样随机生成树,所以这里有通过重新采样生成的四棵树。它们都非常相似但略有不同。
现在我们实际上可以可视化装袋,为了可视化装袋,我们简单地取四张图片的平均值。这就是装袋。就是这样:
这是一个随机森林的模糊决策边界,我觉得这很神奇。因为这就像,我真希望在我开始教你们随机森林的时候有这个,我本来可以跳过几节课的。就像“好的,这就是我们做的”。我们创建决策边界,对每个区域进行平均,然后重复几次并对所有结果进行平均。这就是随机森林的做法,我认为这只是一个通过图片将复杂问题变得简单的很好的例子。所以恭喜 Tyler。事实上,他实际上重新发明了别人已经做过的事情。一个叫 Christian Innie(?)的人,他后来成为了世界上最重要的机器学习研究者之一,实际上在他写的一本关于决策森林的书中几乎完全包含了这个技术。所以 Tyler 最终重新发明了一个世界上最重要的决策森林专家创造的东西,这实际上很酷。我觉得这很有趣。很好,因为当我们在 Twitter 上发布这个时,引起了很多关注,最终有人能够说“哦,你知道吗,这实际上已经存在了”。所以 Tyler 已经开始阅读那本书。
Parfit - 快速而强大的超参数优化与可视化 [4:16]
由 Jason Carpenter
另一件很酷的事情是 Jason Carpenter 创建了一个全新的库叫做 Parfit。Parfit 是为了选择超参数而并行拟合多个模型。我真的很喜欢这个。他展示了如何使用它的清晰示例,API 看起来非常类似于其他基于网格搜索的方法,但它使用了 Rachel 写的验证技术,我们几周前学到的使用一个好的验证集。他在介绍它的博客文章中,回顾了什么是超参数,为什么我们必须训练它们,并解释了每一步。然后这个模块本身非常完善。他为其添加了文档,为其添加了一个很好的 README。当你实际看代码时,你会发现它非常简单,这绝对不是坏事,简单是好事。通过编写这段代码并将其打包得如此完美,他使其他人使用这个技术变得非常容易,这很棒。
如何使用 parfit 使 SGD 分类器表现得和逻辑回归一样好 [5:40]
我真的很高兴看到的一件事是 Vinay 继续结合了我们课堂上学到的两件事:一是使用 Parfit,另一是使用我们在上一课中学到的加速 SGD 分类方法,将两者结合起来,说“好的,现在让我们使用 Parfit 来帮助我们找到 SGD 逻辑回归的参数”。我认为这真的是一个很好的主意。
随机森林的直观解释 [6:14]
由 Prince Grover
我认为很棒的另一件事是,Prince 基本上总结了我们在随机森林解释中学到的几乎所有内容。他甚至比那更进一步,因为他描述了随机森林解释的每种不同方法。他描述了如何做到这一点,例如,通过变量置换来计算特征重要性,每个方法都有一个小图片,然后非常酷,这里是从头开始实现它的代码。我认为这是一篇非常好的文章,描述了很多人不理解的东西,并且准确展示了它是如何工作的,既有图片又有实现代码。所以我认为这真的很棒。我在这里真的很喜欢的一件事是,对于树解释器,他实际上展示了如何将树解释器的输出输入到由 USF 学生 Chris 构建的新瀑布图包中,以展示如何实际上在瀑布图中可视化树解释器的贡献。所以再次,这是一种很好的结合我们学习和作为一个团队构建的多种技术。
Keras Model for Beginners (0.210 on LB)+EDA+R&D [7:37]
有一些有趣的内核分享,下周我会分享更多,Davesh 写了这篇非常好的内核,展示了在检测冰山与船只的 Kaggle 竞赛中的挑战。这是一种很难可视化的奇怪的双通道卫星数据,他实际上通过并基本上描述了这些雷达散射的工作原理的公式,然后实际上设法编写了一个代码,使他能够重新创建实际的 3D 冰山或船只。我以前没有见过这样做。如何可视化这些数据是非常具有挑战性的。然后他继续展示如何构建神经网络来尝试解释这一点,这也非常棒。
SGD [9:53]
让我们回到 SGD。所以我们正在回顾这份笔记本,Rachel 基本上带领我们从头开始学习 SGD,目的是进行数字识别。实际上,今天我们看的很多东西都将紧随计算线性代数课程的一部分,你可以在 fast.ai 上找到MOOCs,或者在 USF,它将成为明年的选修课。所以如果你觉得这些东西有趣,我希望你会考虑报名参加选修课或在线观看视频。
所以我们正在构建神经网络。我们假设已经下载了 MNIST 数据,通过减去平均值并除以标准差对其进行了标准化。这些数据略有不同,虽然它们代表图像,但它们被下载为每个图像都是 784 个长的秩为 1 的张量,因此已经被展平。为了绘制它的图片,我们必须将其调整为 28x28。但实际的数据不是 28x28,而是 784 个长的展平数据。
我们要采取的基本步骤是从训练世界上最简单的神经网络开始,基本上是一个逻辑回归。因此没有隐藏层。我们将使用一个名为 Fast AI 的库进行训练,并使用一个名为 PyTorch 的库构建网络。然后我们将逐渐摆脱所有库。首先,我们将摆脱 PyTorch 中的nn
(神经网络)库,并自己编写。然后我们将摆脱 Fast AI 的fit
函数,并自己编写。然后我们将摆脱 PyTorch 的优化器,并自己编写。因此,在本笔记本的最后,我们将自己编写所有部分。我们最终依赖的唯一两个 PyTorch 给我们的关键事物是:
- 具有编写 Python 代码并在 GPU 上运行的能力
- 具有编写 Python 代码并使其自动为我们进行微分的能力。
因此,这两件事我们不打算自己尝试编写,因为这很无聊且毫无意义。但除此之外,我们将尝试在这两件事的基础上自己编写其他所有内容。
我们的起点不是自己做任何事情。基本上所有事情都已经为我们完成。因此,PyTorch 有一个nn
库,其中包含神经网络的内容。您可以通过使用Sequential
函数创建一个多层神经网络,然后传入您想要的层的列表,我们要求一个线性层,然后是一个 softmax 层,这定义了我们的逻辑回归。
from fastai.metrics import * from fastai.model import * from fastai.dataset import * import torch.nn as nnnet = nn.Sequential( nn.Linear(28*28, 10), nn.LogSoftmax() ).cuda()
我们线性层的输入是 28 乘以 28,输出是 10,因为我们希望为我们的图像中的每个数字 0 到 9 之间的每个数字获得一个概率。.cuda()
将其放在 GPU 上,然后fit
拟合模型。
loss=nn.NLLLoss() metrics=[accuracy] opt=optim.Adam(net.parameters()) fit(net, md, n_epochs=5, crit=loss, opt=opt, metrics=metrics)
因此,我们从一组随机权重开始,然后使用梯度下降进行拟合以使其更好。我们必须告诉拟合函数要使用什么标准,换句话说,什么算作更好,我们告诉它使用负对数似然。我们将在下一课中了解这是什么。我们必须告诉它要使用什么优化器,我们说请使用optim.Adam
,这方面的细节我们在本课程中不会涉及。我们将构建一个更简单的称为 SGD 的东西。如果您对 Adam 感兴趣,我们刚刚在深度学习课程中涵盖了这一点。您想要打印出什么指标,我们决定打印出准确率。就是这样。所以在我们拟合后,我们通常会得到大约 91、92%的准确率。
定义模块
接下来我们要做的是,我们将重复这完全相同的事情,所以我们将重建这个模型 4 到 5 次,逐渐减少使用的库。我们上次做的第二件事是尝试开始自己定义模块。所以我们不再使用那个库,而是尝试从头开始自己定义它。为了做到这一点,我们必须使用面向对象,因为这是我们在 PyTorch 中构建所有东西的方式。我们必须创建一个类,该类继承自nn.Module
。所以nn.Module
是一个 PyTorch 类,它接受我们的类并将其转换为神经网络模块,这基本上意味着您从nn.Module
继承的任何东西,您几乎可以将其插入到神经网络中作为一个层,或者您可以将其视为一个神经网络。它将自动获得作为神经网络的一部分或整个神经网络运行所需的所有内容。现在我们将在今天和下一课中详细讨论这意味着什么。
因此我们需要构建这个对象,这意味着我们需要定义构造函数 dunder init。重要的是,这是一个 Python 的东西,如果你继承自其他对象,那么你首先必须创建你继承的东西。因此当你说super().__init__()
时,这意味着首先构建那个nn.Module
部分。如果你不这样做,那么nn.Module
的东西就永远没有机会被实际构建。因此这就像一个标准的 Python 面向对象子类构造函数。如果其中有任何地方让你感到困惑,那么你知道这就是你绝对需要抓住一个 Python 面向对象的入门,因为这是标准的方法。
def get_weights(*dims): return nn.Parameter(torch.randn(dims)/dims[0]) def softmax(x): return torch.exp(x)/(torch.exp(x).sum(dim=1)[:,None]) class LogReg(nn.Module): def __init__(self): super().__init__() self.l1_w = get_weights(28*28, 10) # Layer 1 weights self.l1_b = get_weights(10) # Layer 1 bias def forward(self, x): x = x.view(x.size(0), -1) x = (x @ self.l1_w) + self.l1_b # Linear Layer x = torch.log(softmax(x)) # Non-linear (LogSoftmax) Layer return x
因此,在我们的构造函数中,我们想要做的相当于nn.Linear
。nn.Linear
所做的是,它将我们的 28 乘以 28 的向量,即 784 个元素的向量,作为矩阵乘法的输入。因此,现在我们需要创建一个具有 784 行和 10 列的矩阵。因为这个的输入将是一个大小为 64 乘以 784 的小批量数据。因此我们将进行这个矩阵乘法运算。因此当我们在 PyTorch 中说nn.Linear
时,它将为我们构建一个 784 乘以 10 的矩阵。因此,由于我们没有使用它,我们是从头开始做事情,我们需要自己制作它。为了自己制作它,我们可以说生成具有这种维度的正态随机数torch.randn(dims)
,我们在这里传入了28*28, 10
。这样我们就得到了我们随机初始化的矩阵。
然后我们想要添加到这个。我们不只是想要 y = ax,我们想要 y = ax + b。所以我们需要添加在神经网络中称为偏置向量的东西。因此,我们在这里创建一个长度为 10 的偏置向量 self.l1_b = get_weights(10)
,同样是随机初始化的,所以现在我们有了两个随机初始化的权重张量。
这就是我们的构造函数。现在我们需要定义前向传播。为什么我们需要定义前向传播?这是一个 PyTorch 特有的东西。当你在 PyTorch 中创建一个模块时,你得到的对象会表现得好像它是一个函数。你可以用括号调用它,我们马上就会这样做。因此,你需要以某种方式定义当你调用它时会发生什么,就好像它是一个函数,答案是 PyTorch 调用一个叫做 forward 的方法。这是他们选择的 PyTorch 方法。所以当它调用 forward 时,我们需要做这个模块或层的输出的实际计算。这就是在逻辑回归中实际计算的东西。基本上我们取得我们的输入 x,它被传递给 forward —— 这基本上是 forward 的工作原理,它接收到小批量数据,并且我们将其与构造函数中定义的第一层权重进行矩阵乘法运算。然后我们加上构造函数中也定义的第一层偏置。实际上,现在我们可以更加优雅地使用 Python 3 的矩阵乘法运算符@
来定义这个。
当你使用它时,我认为你最终会得到更接近数学符号的样子,所以我觉得这更好看。
好了,这就是我们逻辑回归中的线性层(即我们的零隐藏层神经网络)。然后我们对其进行 softmax。我们得到这个矩阵乘法的输出,它的维度是 64 乘以 10。我们得到这个输出矩阵,然后我们通过 softmax 函数处理它。为什么我们要通过 softmax 函数处理它?我们要通过 softmax 函数处理它是因为最终,对于每个图像,我们希望得到一个概率,它是 0、1、2、3 或 4。所以我们希望得到一堆概率,它们加起来等于 1,其中每个概率都在 0 到 1 之间。所以 softmax 函数正好为我们做到了这一点。
例如,如果我们不是从零到 10 中挑选数字,而是挑选猫、狗、飞机、鱼或建筑物,那么一个特定图像的矩阵乘积的输出可能看起来像这样(输出列)。这些只是一些随机数。为了将其转换为 softmax,我首先对这些数字中的每一个进行e的幂运算。我将这些e的幂相加。然后我将这些e的幂中的每一个除以总和。这就是 softmax。这就是 softmax 的定义。
因为它是e的幂,这意味着它始终是正的。因为它被总和除以,这意味着它始终在 0 和 1 之间,并且还意味着它们总是加起来等于 1。因此,通过应用这个 softmax 激活函数,每当我们有一层输出,我们称之为激活,然后我们对其应用一些非线性函数,将一个标量映射到一个标量,比如 softmax(我们称之为激活函数)。因此,softmax 激活函数将我们的输出转换为类似概率的东西。严格来说,我们不需要它。我们仍然可以尝试训练直接输出概率的东西。但是通过使用这个函数,它自动使它们始终表现得像概率,这意味着网络需要学习的内容更少,因此它会学得更好。因此,一般来说,每当我们设计一个架构时,我们都会尽可能地设计它,以便它尽可能地创建我们想要的形式。这就是为什么我们使用 softmax 的原因。
这就是基本步骤。我们有我们的输入,它是一堆图像,它被一个权重矩阵相乘,我们还添加一个偏置以获得线性函数的输出。我们将其通过一个非线性激活函数,这种情况下是 softmax,这给我们带来了我们的概率。
所以这就是全部。PyTorch 也倾向于使用 softmax 的对数,原因并不需要现在困扰我们。基本上是为了数值稳定性的便利。因此,为了使这与我们在上面看到的nn.LogSoftmax()
版本相同,我也将在这里使用对数。好的,现在我们可以实例化这个类(即创建这个类的对象)。
问题:我有一个关于之前的概率的问题。如果我们有一张照片上有一只猫和一只狗,那会改变它的工作方式吗?或者它的基本工作方式是一样的。这是一个很好的问题。所以如果你有一张照片上有一只猫和一只狗,你希望它同时输出猫和狗,这将是一个非常糟糕的选择。Softmax 是我们专门用于分类预测的激活函数,我们只想预测其中一种东西。因此,部分原因是因为正如你所看到的,因为我们使用e的幂,稍微更大的e会产生更大的数字。因此,通常我们只有一两个大的东西,其他东西都很小。因此,如果我重新计算这些随机数(在 Excel 表中),你会看到它往往是一堆零和一两个高数字。因此,它真的是设计成试图让预测这一件事变得容易。如果你正在进行多标签预测,所以我只想找到这张图片中的所有东西,而不是使用 softmax,我们将使用 sigmoid。Sigmoid 会导致这些东西中的每一个都在 0 和 1 之间,但它们不再加起来等于 1。
关于最佳实践的许多细节是我们在深度学习课程中涵盖的内容,我们在机器学习课程中不会涵盖大量这些内容。我们更感兴趣的是机制。但是如果它们很快,我们会尝试涵盖它们。
现在我们已经得到了这个,我们可以实例化该类的一个对象[27:30]。当然我们想将其复制到 GPU 上。这样我们可以在那里进行计算。再次,我们需要一个优化器,我们很快会讨论这是什么。但你看到这里,我们在我们的类上调用了一个名为parameters
的函数。但我们从未定义过一个叫做 parameters 的方法,这将会起作用的原因是因为它实际上是在nn.Module
内部为我们定义的。所以nn.Module
会自动遍历我们创建的属性,并找到任何我们说这是一个参数的东西。你说某个东西是参数的方式是将它包装在nn.Parameter
中。这只是告诉 PyTorch 这是我想要优化的东西的方式。所以当我们创建权重矩阵时,我们只是用nn.Parameter
包装它,这与我们很快要学习的常规 PyTorch 变量完全相同。这只是一个小标志,告诉 PyTorch 你应该优化这个。所以当你在我们创建的net2
对象上调用net2.parameters()
时,它会遍历我们在构造函数中创建的所有东西,检查是否有任何一个是Parameter
类型,如果是,它会将所有这些东西设置为我们要用优化器训练的东西。我们稍后将从头开始实现优化器。
net2 = LogReg().cuda() opt=optim.Adam(net2.parameters())
做完这些,我们可以拟合[28:51]。我们应该基本上得到与之前相同的答案(即 91 左右)。看起来不错。
fit(net2, md, n_epochs=1, crit=loss, opt=opt, metrics=metrics)
fast.ai 机器学习笔记(三)(3)https://developer.aliyun.com/article/1482654