机器学习 1:第 11 课
原文:
medium.com/@hiromi_suenaga/machine-learning-1-lesson-11-7564c3c18bbb
译者:飞龙
来自机器学习课程的个人笔记。随着我继续复习课程以“真正”理解它,这些笔记将继续更新和改进。非常感谢 Jeremy 和 Rachel 给了我这个学习的机会。
使用 SGD 优化多层函数的回顾[0:00]
这个想法是我们有一些数据(x),然后我们对这些数据做一些操作,例如,我们用一个权重矩阵乘以它(f(x))。然后我们对这个结果做一些操作,例如,我们通过 softmax 或 sigmoid 函数处理它(g(f(x)))。然后我们对这个结果做一些操作,比如计算交叉熵损失或均方根误差损失(h(g(f(x))))。这将给我们一些标量。这里没有隐藏层。这有一个线性层,一个非线性激活函数是 softmax,一个损失函数是均方根误差或交叉熵。然后我们有我们的输入数据。
例如[1:16],如果非线性激活函数是 sigmoid 或 softmax,损失函数是交叉熵,那就是逻辑回归。那么我们如何计算对权重的导数?
为了做到这一点,基本上我们使用链式法则:
因此,为了对权重求导,我们只需使用那个确切的公式计算对 w 的导数[3:29]。然后如果我们在这里进一步,有另一个带有权重 w2 的线性层,现在计算对所有参数的导数没有区别。我们仍然可以使用完全相同的链式法则。
所以不要把多层网络想象成在不同时间发生的事情。它只是函数的组合。所以我们只需使用链式法则一次计算所有导数。它们只是在函数的不同部分出现的一组参数,但微积分并没有不同。所以计算对 w1 和 w2 的导数,你现在可以称之为 w,说 w1 就是所有这些权重。
那么你将会得到一个参数列表[5:26]。这里是 w1,可能是某种高阶张量。如果是卷积层,它将是一个三阶张量,但我们可以展开它。我们将把它变成一个参数列表。这里是 w2。这只是另一个参数列表。这是我们的损失,是一个单一的数字。因此,我们的导数就是同样长度的向量。改变 w 的值会对损失产生多大影响?你可以把它想象成一个函数,比如y = ax1 + bx2 + c,然后问对 a、b 和 c 的导数是多少?你会得到三个数字:对 a、b 和 c 的导数。就是这样。如果对那个权重求导,那个权重,…
为了到达这一点,在链式法则中,我们必须计算像雅可比这样的导数,当你进行矩阵乘法时,你现在得到的是一个权重矩阵和输入向量,这些是来自前一层的激活,还有一些新的输出激活。所以现在你必须说对于这个特定的权重,改变这个特定的权重如何改变这个特定的输出?改变这个特定的权重如何改变这个特定的输出?等等。所以你最终会得到这些更高维度的张量,显示每个权重如何影响每个输出。然后当你到达损失函数时,损失函数将有一个均值或和,所以它们最终会被加起来。
尝试手动计算或逐步考虑这一点让我有点疯狂,因为你倾向于像…你只需要记住,对于每个权重对于每个输出,你都必须有一个单独的梯度。
一个很好的方法是学习使用 PyTorch 的.grad
属性和.backward
方法,并手动查阅 PyTorch 教程。这样你就可以开始设置一些具有向量输入和向量输出的计算,然后输入.backward
,然后输入grad
并查看它。然后对只有 2 或 3 个项目的输入和输出向量进行一些非常小的操作,比如加 2 或其他操作,看看形状是什么,确保它是有意义的。因为向量矩阵微积分在严格意义上并没有为你在高中学到的任何概念引入新的概念。但是对这些形状如何移动有了一定的感觉需要大量的练习。好消息是,你几乎永远不必担心这个。
NLP 的朴素贝叶斯和逻辑回归回顾[9:53]
我们正在讨论使用这种逻辑回归进行 NLP。在达到这一点之前,我们正在讨论使用朴素贝叶斯进行 NLP。基本思想是我们可以取一个文档(例如电影评论),并将其转换为一个词袋表示,其中包含每个单词出现的次数。我们称单词的唯一列表为词汇表。我们使用 sklearn 的 CountVectorizer 自动生成词汇表,他们称之为“特征”,并创建词袋表示,所有这些袋表示的整体称为术语文档矩阵。
我们有点意识到,我们可以通过简单地平均积极评论中单词“this”出现的次数来计算积极评论包含单词“this”的概率,我们可以对消极评论做同样的事情,然后我们可以取它们的比率,得到一个结果,如果大于一,则表示该单词在积极评论中出现得更频繁,如果小于一,则表示该单词在消极评论中出现得更频繁。
然后我们意识到,使用贝叶斯规则并取对数,我们基本上可以得到一个结果,我们可以将这些(下面突出显示的)的对数加起来,再加上类别 1 与类别 0 的概率比的对数,最终得到一个可以与零进行比较的结果[11:32]。如果结果大于零,我们可以预测文档是积极的,如果结果小于零,我们可以预测文档是消极的。这就是我们的贝叶斯规则。
我们从数学的第一原理开始做了这个,我认为我们都同意“朴素”在朴素贝叶斯中是一个很好的描述,因为它假设了独立性,而这显然是不正确的。但这是一个有趣的起点,当我们实际上到达这一点时,我们计算了概率的比率并取了对数,现在不是将它们相乘,当然,我们必须将它们相加。当我们实际写下这个时,我们意识到哦,这只是一个标准的权重矩阵乘积加上一个偏差:
然后我们意识到,如果这不是很好的准确率(80%),为什么不通过说,嘿,我们知道其他计算一堆系数和一堆偏差的方法,即在逻辑回归中学习它们来改进呢?换句话说,这是我们用于逻辑回归的公式,那么为什么我们不只是创建一个逻辑回归并拟合它呢?它会给我们同样的结果,但与基于独立性假设和贝叶斯规则的理论上正确的系数和偏差不同,它们将是实际上在这些数据中最好的系数和偏差。这就是我们的结论。
这里的关键见解是,几乎所有的机器学习最终都变成了一棵树或一堆矩阵乘积和非线性[13:54]。一切似乎最终都归结为相同的事情,包括贝叶斯规则。然后事实证明,无论该函数中的参数是什么,它们都比基于理论计算更好地学习。事实上,当我们实际尝试学习这些系数时,我们得到了 85%的准确率。
然后我们注意到,与其采用整个术语文档矩阵,我们可以只取单词存在或不存在的一和零。有时这样做同样有效,但后来我们实际尝试了另一种方法,即添加正则化。通过正则化,二值化方法结果略好一些。
然后正则化是我们取损失函数,再次,让我们从 RMSE 开始,然后我们将讨论交叉熵。损失函数是我们的预测减去我们的实际值,将其相加,取平均再加上一个惩罚。
具体来说,这是 L2 惩罚。如果这是 w 的绝对值,那就是 L1 惩罚。我们还注意到,我们实际上并不关心损失函数本身,我们只关心它的导数,这实际上是更新权重的东西,因此因为这是一个总和,我们可以分别对每个部分求导,所以惩罚的导数只是 2aw。因此,我们了解到,尽管这些在数学上是等价的,但它们有不同的名称。这个版本(2aw)被称为权重衰减,这个术语在神经网络文献中使用。
交叉熵[16:34]
另一方面,交叉熵只是另一个损失函数,就像均方根误差一样,但它专门设计用于分类。这是一个二元交叉熵的例子。假设这是我们的“是猫还是狗?”所以说isCat
是 1 还是 0。而Preds
是我们的预测,这是我们神经网络的最终层的输出,一个逻辑回归等等。
然后我们所做的就是说好吧,让我们取实际值乘以预测的对数,然后加上 1 减去实际值乘以 1 减去预测的对数,然后取整个东西的负值。
我建议你们尝试写出这个 if 语句版本,希望你们现在已经做到了,否则我将为你们揭示。所以这是:
我们如何将这写成一个 if 语句?
if y == 1: return -log(ŷ) else: return -log(1-ŷ)
所以关键的洞察是 y 有两种可能性:1 或 0。所以很多时候数学会隐藏关键的洞察,我认为这里发生了,直到你真正思考它可以取什么值。所以这就是它所说的。要么给我:-log(ŷ)
,要么给我:-log(1-ŷ)
好的,那么多类别版本就是同样的事情,但你说的不仅仅是 y == 1
,而是 y == 0, 1, 2, 3, 4, 5 . . .
,例如 [19:26]。所以这个损失函数有一个特别简单的导数,另外一个你可以在家里尝试的东西是想一想在它之前加上一个 sigmoid 或 softmax 之后导数是什么样子。结果会是非常好的导数。
人们使用均方根误差用于回归和交叉熵用于分类的原因有很多,但大部分都可以追溯到最佳线性无偏估计的统计概念,基于可能性函数的结果表明这些函数具有一些良好的统计性质。然而,实际上,尤其是均方根误差的性质可能更多是理论上的而不是实际的,实际上,现在使用绝对偏差而不是平方偏差的和通常效果更好。所以在实践中,机器学习中的一切,我通常都会尝试两种。对于特定的数据集,我会尝试两种损失函数,看哪一个效果更好。当然,如果是 Kaggle 竞赛的话,那么你会被告知 Kaggle 将如何评判,你应该使用与 Kaggle 评估指标相同的损失函数。
所以这真的是关键的洞察 [21:16]。让我们不要使用理论,而是从数据中学习。我们希望我们会得到更好的结果。特别是在正则化方面,我们确实得到了。然后我认为这里的关键正则化洞察是让我们不要试图减少模型中的参数数量,而是使用大量的参数,然后使用正则化来找出哪些实际上是有用的。
更多的 n-grams 特征 [21:41]
然后我们进一步说,鉴于我们可以通过正则化做到这一点,让我们通过添加二元组和三元组来创建更多。例如 by vast
、by vengeance
这样的二元组,以及 by vengeance .
、by vera miles
这样的三元组。为了让事情运行得更快一些,我们将其限制为 800,000 个特征,但即使使用完整的 70 百万个特征,它的效果也一样好,而且速度并没有慢多少。
veczr = CountVectorizer( ngram_range=(1,3), tokenizer=tokenize, max_features=800000 ) trn_term_doc = veczr.fit_transform(trn) val_term_doc = veczr.transform(val) trn_term_doc.shape ''' (25000, 800000) ''' vocab = veczr.get_feature_names() vocab[200000:200005] ''' ['by vast', 'by vengeance', 'by vengeance .', 'by vera', 'by vera miles'] '''
所以我们使用了完整的 n-grams 集合为训练集和验证集创建了一个术语文档矩阵。现在我们可以继续说我们的标签是训练集标签如前所述,我们的自变量是二值化的术语文档矩阵如前所述:
y=trn_y x=trn_term_doc.sign() val_x = val_term_doc.sign() p = x[y==1].sum(0)+1 q = x[y==0].sum(0)+1 r = np.log((p/p.sum())/(q/q.sum())) b = np.log(len(p)/len(q))
然后让我们对其进行逻辑回归拟合,并进行一些预测,我们得到了 90% 的准确率:
m = LogisticRegression(C=0.1, dual=True) m.fit(x, y); preds = m.predict(val_x) (preds.T==val_y).mean() ''' 0.90500000000000003 '''
所以看起来很不错。
回到朴素贝叶斯 [22:54]
让我们回到我们的朴素贝叶斯。在我们的朴素贝叶斯中,我们有这个术语文档矩阵,然后对于每个特征,我们正在计算如果它是类别 1 出现的概率,如果它是类别 0 出现的概率,以及这两者的比率。
在我们实际基于的论文中,他们将 p(f|1)
称为 p,将 p(f|0)
称为 q,将比率称为 r。
然后我们说让我们不要把这些比率作为矩阵乘法中的系数。而是,尝试学习一些系数。也许开始用一些随机数,然后尝试使用随机梯度下降找到稍微更好的系数。
所以你会注意到这里一些重要的特征。r向量是一个秩为 1 的向量,其长度等于特征的数量。当然,我们的逻辑回归系数矩阵也是秩为 1 且长度等于特征数量的。我们说它们是计算相同类型的东西的两种方式:一种基于理论,一种基于数据。所以这里是r中的一些数字:
r.shape, r ''' ((1, 800000), matrix([[-0.05468, -0.161 , -0.24784, ..., 1.09861, -0.69315, -0.69315]])) '''
记住它使用对数,所以这些小于零的数字代表更有可能是负数的东西,而大于零的数字可能是正数。所以这里是 e 的幂次方。所以这些是我们可以与 1 而不是 0 进行比较的数字:
np.exp(r) ''' matrix([[ 0.94678, 0.85129, 0.78049, ..., 3\. , 0.5 , 0.5 ]])\ '''
我要做一些希望看起来很奇怪的事情。首先,我会说我们要做什么,然后我会尝试描述为什么这很奇怪,然后我们会讨论为什么它可能并不像我们最初想的那么奇怪。所以这就是我们要做的事情。我们将取我们的术语文档矩阵,然后将其乘以r。这意味着,我可以在 Excel 中做到这一点,我们将说让我们抓取我们的术语文档矩阵中的所有内容,并将其乘以向量r中的等值。所以这就像是一个广播的逐元素乘法,而不是矩阵乘法。
所以这是术语文档矩阵乘以r的值,换句话说,在术语文档矩阵中出现零的地方,在乘以版本中也出现零。而在术语文档矩阵中每次出现一个的地方,等效的r值出现在底部。所以我们并没有真正改变太多。我们只是将一个变成了其他东西,即来自该特征的r。所以我们现在要做的是,我们将使用这些独立变量,而不是在我们的逻辑回归中。
所以在这里。x_nb
(x 朴素贝叶斯版本)是x
乘以r
。现在让我们使用这些独立变量进行逻辑回归拟合。然后对验证集进行预测,结果我们得到了一个更好的数字:
x_nb = x.multiply(r) m = LogisticRegression(dual=True, C=0.1) m.fit(x_nb, y); val_x_nb = val_x.multiply(r) preds = m.predict(val_x_nb) (preds.T==val_y).mean() ''' 0.91768000000000005 '''
让我解释为什么这可能会令人惊讶。这是我们的独立变量(下面突出显示),然后逻辑回归得出了一些系数集(假设这些是它恰好得出的系数)。
现在我们可以说,好吧,让我们不使用这组独立变量(x_nb
),而是使用原始的二值化特征矩阵。然后将所有系数除以r中的值,数学上我们将得到完全相同的结果。
所以我们有了我们的独立变量的 x 朴素贝叶斯版本(x_nb
),以及一组权重/系数(w1
),它发现这是一个用于进行预测的好系数集。但是 x_nb 简单地等于x
乘以(逐元素)r
。
换句话说,这(xnb·w1
)等于x*r·w1
。所以我们可以只改变权重为r·w1
,得到相同的数字。这应该意味着我们对独立变量所做的更改不应该有任何影响,因为我们可以在不进行这种改变的情况下计算出完全相同的结果。所以问题就在这里。为什么会有所不同呢?为了回答这个问题,你需要考虑哪些数学上不同的事情。为什么它们不完全相同?提出一些假设,也许我们实际上得到了更好的答案的一些原因。要弄清楚这一点,首先我们需要弄清楚为什么会有不同的答案?这是微妙的。
讨论
它们受到正则化的影响是不同的。我们的损失等于基于预测和实际值的交叉熵损失加上我们的惩罚:
所以如果你的权重很大,那么惩罚(aw²
)就会变大,而且会淹没掉交叉熵部分(x.e.(xw, y)
)。但那实际上是我们关心的部分。我们实际上希望它是一个很好的拟合。所以我们希望尽可能少地进行正则化。所以我们希望权重更小(我有点把“更少”和“更小”这两个词用得有点等同,这不太公平,我同意,但想法是接近零的权重实际上是不存在的)。
这就是问题所在。我们的r值,我不是一个贝叶斯怪胎,但我仍然要使用“先验”这个词。它们有点像一个先验 - 我们认为不同级别的重要性和这些不同特征的积极或消极可能是这样的。我们认为“坏”可能与负面更相关,而不是“好”。所以我们之前的隐含假设是我们没有先验,换句话说,当我们说平方权重(w²)时,我们是在说非零权重是我们不想要的。但实际上我想说的是,与朴素贝叶斯的期望不同是我不想做的事情。除非你有充分的理由相信其他情况,否则只有与朴素贝叶斯先验有所不同。
这实际上就是这样做的。我们最终说我们认为这个值可能是 3。所以如果你要把它变得更大或更小,那将会导致权重的变化,从而使平方项增加。所以如果可以的话,就让所有这些值保持与现在大致相似。这就是现在惩罚项正在做的事情。当我们的输入已经乘以r时,它在说惩罚那些与我们的朴素贝叶斯先验有所不同的事物。
问题:为什么只与 r 相乘,而不是像 r²这样,这次方差会高得多呢?因为我们的先验来自一个实际的理论模型。所以我说我不喜欢依赖理论,但如果我有一些理论,那么也许我们应该将其作为我们的起点,而不是假设一切都是相等的。所以我们的先验说,嘿,我们有这个叫做朴素贝叶斯的模型,朴素贝叶斯模型说,如果朴素贝叶斯的假设是正确的,那么r就是这个特定公式中的正确系数。这就是我们选择它的原因,因为我们的先验是基于那个理论的。
这是一个非常有趣的见解,我从未真正看到过。这个想法是我们可以使用这些传统的机器学习技术,通过将我们的理论期望纳入我们给模型的数据中,赋予它们这种贝叶斯感。当我们这样做时,这意味着我们就不必那么经常进行正则化了。这很好,因为我们经常进行正则化…让我们试试吧!
记住,在 sklearn 逻辑回归中,C
是正则化惩罚的倒数。所以我们通过使其变小(1e-5
)来增加大量的正则化。
这真的会影响我们的准确性,因为现在它非常努力地降低这些权重,损失函数被需要减少权重的需求所压倒。而现在使其具有预测性似乎完全不重要。所以通过开始并说不要推动权重降低,以至于最终忽略这些项,而是推动它们降低,以便尝试消除那些忽略了我们基于朴素贝叶斯公式期望的差异的权重。这最终给我们带来了一个非常好的结果
fast.ai 机器学习笔记(四)(2)https://developer.aliyun.com/article/1482644