fast.ai 深度学习笔记(二)(4)

简介: fast.ai 深度学习笔记(二)

fast.ai 深度学习笔记(二)(3)https://developer.aliyun.com/article/1482684

使用 Python 进行点积

T是 Torch 中的张量

a = T([[1., 2], [3, 4]])
b = T([[2., 2], [10, 10]])

当我们在 numpy 或 PyTorch 中的张量之间有数学运算符时,它将假定它们具有相同的维度进行逐元素操作。下面是如何计算两个向量的点积的方法(例如(1, 2)⋅(2, 2) = 6 - 矩阵 a 和 b 的第一行):

(a*b).sum(1)
'''
6
70
[torch.FloatTensor of size 2]
'''

构建我们的第一个自定义层(即 PyTorch 模块)[33:55]

我们通过创建一个扩展nn.Module并覆盖forward函数的 Python 类来实现这一点。

class DotProduct (nn.Module):
   def forward(self, u, m): 
        return (u*m).sum(1)

现在我们可以调用它并获得预期结果(请注意,我们不需要说model.forward(a, b)来调用forward函数 - 这是 PyTorch 的魔法。)[40:14]:

model = DotProduct()
model(a,b)
'''
6
70
[torch.FloatTensor of size 2]
'''

构建更复杂的模块[41:31]

这个实现对DotProduct类有两个添加:

  • 两个nn.Embedding矩阵
  • 在上面的嵌入矩阵中查找我们的用户和电影

用户 ID 可能不是连续的,这使得难以用作嵌入矩阵的索引。因此,我们将从零开始创建连续的索引,并用 Panda 的apply函数和匿名函数lambda替换ratings.userId列,并对ratings.movieId执行相同操作。

u_uniq = ratings.userId.unique() 
user2idx = {o:i for i,o in enumerate(u_uniq)} 
ratings.userId = ratings.userId.apply(lambda x: user2idx[x]) 
m_uniq = ratings.movieId.unique() 
movie2idx = {o:i for i,o in enumerate(m_uniq)} 
ratings.movieId = ratings.movieId.apply(lambda x: movie2idx[x]) 
n_users=int(ratings.userId.nunique()) 
n_movies=int(ratings.movieId.nunique())

提示:{o:i for i,o in enumerate(u_uniq)}是一行方便的代码,可以保存在您的工具包中!

class EmbeddingDot(nn.Module):
    def __init__(self, n_users, n_movies):
        super().__init__()
        self.u = nn.Embedding(n_users, n_factors)
        self.m = nn.Embedding(n_movies, n_factors)
        self.u.weight.data.uniform_(0,0.05)
        self.m.weight.data.uniform_(0,0.05)
    def forward(self, cats, conts):
        users,movies = cats[:,0],cats[:,1]
        u,m = self.u(users),self.m(movies)
        return (u*m).sum(1)

请注意,__init__是一个构造函数,现在需要它,因为我们的类需要跟踪“状态”(有多少电影,有多少用户,有多少因素等)。我们将权重初始化为 0 到 0.05 之间的随机数,您可以在这里找到有关权重初始化的标准算法“Kaiming Initialization”的更多信息(PyTorch 具有 He 初始化实用程序函数,但我们正在尝试从头开始做事)[46:58]。

Embedding不是张量,而是变量。变量执行与张量完全相同的操作,但它还执行自动微分。要从变量中提取张量,请调用data属性。所有张量函数都有一个带有下划线的变体(例如uniform_),将在原地执行操作。

x = ratings.drop(['rating', 'timestamp'],axis=1)
y = ratings['rating'].astype(np.float32)
data = ColumnarModelData.from_data_frame(path, val_idxs, x, y, ['userId', 'movieId'], 64)

我们正在重用来自 Rossmann 笔记本的ColumnarModelData(来自 fast.ai 库),这就是为什么在EmbeddingDot类的def forward(self, cats, conts)函数中有分类和连续变量的原因[50:20]。由于在这种情况下我们没有连续变量,我们将忽略conts,并使用cats的第一列和第二列作为usersmovies。请注意,它们是用户和电影的小批量。重要的是不要手动循环遍历小批量,因为这样不会获得 GPU 加速,而是一次处理整个小批量,就像您在上面forward函数的第 3 和第 4 行中看到的那样[51:00–52:05]。

wd=1e-5
model = EmbeddingDot(n_users, n_movies).cuda()
opt = optim.SGD(model.parameters(), 1e-1, weight_decay=wd, momentum=0.9)

optim是 PyTorch 中提供优化器的东西。model.parameters()是从nn.Modules继承的一个函数,它给出所有需要更新/学习的权重。

fit(model, data, 3, opt, F.mse_loss)

这个函数来自 fast.ai 库[54:40],与我们一直在使用的learner.fit()相比,更接近常规的 PyTorch 方法。它不会为您提供“随机梯度下降重启”或“不同学习率”等功能。

让我们改进我们的模型

偏差 - 调整到普遍受欢迎的电影或普遍热情的用户。

min_rating,max_rating = ratings.rating.min(),ratings.rating.max()
min_rating,max_rating
def get_emb(ni,nf):
    e = nn.Embedding(ni, nf)
    e.weight.data.uniform_(-0.01,0.01)
    return e
class EmbeddingDotBias(nn.Module):
    def __init__(self, n_users, n_movies):
        super().__init__()
        (self.u, self.m, self.ub, self.mb) = [
            get_emb(*o) 
            for o in [
                (n_users, n_factors), 
                (n_movies, n_factors), 
                (n_users,1), 
                (n_movies,1)
            ]
        ]
    def forward(self, cats, conts):
        users,movies = cats[:,0],cats[:,1]
        um = (self.u(users)* self.m(movies)).sum(1)
        res = um + self.ub(users).squeeze() + self.mb(movies).squeeze()
        res = F.sigmoid(res) * (max_rating-min_rating) + min_rating
        return res

squeeze是 PyTorch 版本的广播[1:04:11],有关更多信息,请参阅机器学习课程或numpy 文档

我们可以压缩评分,使其在 1 和 5 之间吗?可以!通过将预测通过 sigmoid 函数,将得到 1 和 0 之间的数字。因此,在我们的情况下,我们可以将其乘以 4 并加 1 - 这将得到 1 和 5 之间的数字。

F是 PyTorch 功能(torch.nn.functional),包含所有张量的函数,并在大多数情况下导入为F

wd=2e-4
model = EmbeddingDotBias(cf.n_users, cf.n_items).cuda()
opt = optim.SGD(model.parameters(), 1e-1, weight_decay=wd, momentum=0.9)
fit(model, data, 3, opt, F.mse_loss)
'''
[ 0\.       0.85056  0.83742]                                     
[ 1\.       0.79628  0.81775]                                     
[ 2\.       0.8012   0.80994]
'''

让我们看看我们在简单的 Python 版本中使用的 fast.ai 代码[1:13:44]。在column_data.py文件中,CollabFilterDataSet.get_leaner调用get_model函数,该函数创建了EmbeddingDotBias类,与我们创建的内容相同。

神经网络版本[1:17:21]

我们回到 Excel 表格来理解直觉。请注意,我们创建了user_idx来查找嵌入,就像我们之前在 Python 代码中所做的那样。如果我们要对user_idx进行独热编码并将其乘以用户嵌入,我们将得到用户的适用行。如果只是矩阵乘法,为什么我们需要嵌入?这是为了计算性能优化的目的。

与计算用户嵌入向量和电影嵌入向量的点积以获得预测不同,我们将连接这两者并将其馈送到神经网络中。

class EmbeddingNet(nn.Module):
    def __init__(self, n_users, n_movies, nh=10, p1=0.5, p2=0.5):
        super().__init__()
        (self.u, self.m) = [
            get_emb(*o) 
            for o in [
                (n_users, n_factors), 
                (n_movies, n_factors)
            ]
        ]
        self.lin1 = nn.Linear(n_factors*2, nh)
        self.lin2 = nn.Linear(nh, 1)
        self.drop1 = nn.Dropout(p1)
        self.drop2 = nn.Dropout(p2)
    def forward(self, cats, conts):
        users,movies = cats[:,0],cats[:,1]
        x = self.drop1(torch.cat([self.u(users),self.m(movies)], dim=1))
        x = self.drop2(F.relu(self.lin1(x)))
        return F.sigmoid(self.lin2(x)) * (max_rating-min_rating+1) + min_rating-0.5

注意到我们不再有偏差项,因为 PyTorch 中的Linear层已经内置了偏差。nh是线性层创建的激活数量(Jeremy 称之为“num hidden”)。

它只有一个隐藏层,所以可能不是“深度”,但这绝对是一个神经网络。

wd=1e-5
model = EmbeddingNet(n_users, n_movies).cuda()
opt = optim.Adam(model.parameters(), 1e-3, weight_decay=wd)
fit(model, data, 3, opt, F.mse_loss)
'''
A Jupyter Widget
[ 0\.       0.88043  0.82363]                                    
[ 1\.       0.8941   0.81264]                                    
[ 2\.       0.86179  0.80706]
'''

请注意,损失函数也在F中(这里是均方损失)。

现在我们有了神经网络,我们可以尝试很多事情:

  • 添加丢弃
  • 为用户嵌入和电影嵌入使用不同的嵌入大小
  • 不仅用户和电影嵌入,还可以附加电影类型嵌入和/或原始数据中的时间戳。
  • 增加/减少隐藏层和激活数量
  • 增加/减少正则化

训练循环中发生了什么?[1:33:21]

目前,我们将权重的更新交给 PyTorch 的优化器。优化器做什么?动量是什么?

opt = optim.SGD(model.parameters(), 1e-1, weight_decay=wd, momentum=0.9)

我们将在 Excel 表格中实现梯度下降(graddesc.xlsm)- 从右到左查看工作表。首先我们创建一组随机的xy,它们与x线性相关(例如y= ax* + b)。通过使用一组xy,我们将尝试学习ab

要计算误差,我们首先需要一个预测,并计算差的平方:

为了减少误差,我们稍微增加/减少ab,并找出什么会使误差减少。这被称为通过有限差分找到导数。

在高维空间中,有限差分变得复杂[1:41:46],并且变得非常占用内存且需要很长时间。因此,我们希望找到一种更快地完成这项工作的方法。值得查阅雅可比和黑塞(深度学习书籍:第 4.3.1 节第 84 页)。

链式规则和反向传播

更快的方法是通过分析进行[1:45:27]。为此,我们需要一个链式规则:

链式规则概述

这是 Chris Olah 关于反向传播作为链式规则的一篇很棒的文章。

现在我们用实际导数替换有限差分WolframAlpha给我们提供了(请注意,有限差分输出与实际导数非常接近,是计算自己的导数的快速检查的好方法):

  • “在线”训练 - 小批量大小为 1

这就是你在 Excel 表格中使用 SGD 的方法。如果你将预测值更改为 CNN 电子表格的输出,我们可以使用 SGD 训练 CNN。

动量[1:53:47]

来吧,给个提示 - 那是一个好方向。请继续这样做,但更多。

通过这种方法,我们将使用当前小批量导数和上一个小批量之后我们采取的步骤(以及方向)之间的线性插值(单元格 K9):

与随机符号(+/-)的de/db相比,具有动量的方向会保持相同的方向,直到某个点为止。这将减少训练所需的周期数。

Adam[1:59:04]

Adam 速度更快,但问题在于最终预测不如使用动量的 SGD 那么好。似乎是由于 Adam 和权重衰减的结合使用。修复此问题的新版本称为AdamW

  • 单元格 J8:导数和上一个方向的线性插值(与动量中的相同)
  • 单元格 L8:导数平方和上一步的导数平方的线性插值(单元格 L7
  • 这个想法被称为“指数加权移动平均”(换句话说,平均值随着先前值的乘法递减)

学习率比以前高得多,因为我们将其除以L8的平方根。

如果你看一下 fast.ai 库(model.py),你会注意到在fit函数中,它不仅计算平均损失,而且计算损失的指数加权移动平均

avg_loss = avg_loss * avg_mom + loss * (1-avg_mom)

另一个有用的概念是每当你看到α(…) + (1-α)(…)时,立即想到线性插值

一些直觉

  • 我们计算了梯度平方的指数加权移动平均值,对其取平方根,并将学习率除以它。
  • 梯度的平方始终为正。
  • 当梯度变化很大时,梯度的平方会很大。
  • 当梯度恒定时,梯度的平方会很小。
  • 如果梯度变化很大,我们希望小心谨慎,并通过一个大数来除以学习率(减慢速度)
  • 如果梯度变化不大,我们将通过一个小数来除以学习率,从而迈出更大的一步
  • 自适应学习率 ——跟踪梯度平方的平均值,并使用它来调整学习率。因此只有一个学习率,但如果梯度恒定,则每个参数在每个时期都会跳得更远;否则跳得更小。
  • 有两种动量 —— 一个用于梯度,另一个用于梯度的平方(在 PyTorch 中,它被称为 beta,是两个数字的元组)

AdamW[2:11:18]

当参数比数据点多得多时,正则化变得重要。我们之前见过 dropout,权重衰减是另一种正则化方法。权重衰减(L2 正则化)通过将平方权重(乘以权重衰减系数)添加到损失中来惩罚大权重。现在损失函数希望保持权重较小,因为增加权重会增加损失;因此只有在损失提高超过惩罚时才会这样做。

问题在于,由于我们将平方权重添加到损失函数中,这会影响 Adam 的梯度移动平均和梯度平方移动平均。这会导致在梯度变化很大时减少权重衰减的量,在变化很小时增加权重衰减的量。换句话说,“惩罚大权重,除非梯度变化很大”,这不是我们的初衷。AdamW 将权重衰减从损失函数中移除,并在更新权重时直接添加。

相关文章
|
14天前
|
机器学习/深度学习 自然语言处理 PyTorch
fast.ai 深度学习笔记(三)(4)
fast.ai 深度学习笔记(三)(4)
22 0
|
14天前
|
机器学习/深度学习 算法 PyTorch
fast.ai 深度学习笔记(三)(3)
fast.ai 深度学习笔记(三)(3)
30 0
|
14天前
|
机器学习/深度学习 编解码 自然语言处理
fast.ai 深度学习笔记(三)(2)
fast.ai 深度学习笔记(三)(2)
31 0
|
14天前
|
机器学习/深度学习 PyTorch 算法框架/工具
fast.ai 深度学习笔记(三)(1)
fast.ai 深度学习笔记(三)(1)
34 0
|
索引 机器学习/深度学习 计算机视觉
fast.ai 深度学习笔记(四)(3)
fast.ai 深度学习笔记(四)
40 0
|
15天前
|
机器学习/深度学习 固态存储 Python
fast.ai 深度学习笔记(四)(2)
fast.ai 深度学习笔记(四)
48 3
fast.ai 深度学习笔记(四)(2)
|
15天前
|
API 机器学习/深度学习 Python
fast.ai 深度学习笔记(四)(1)
fast.ai 深度学习笔记(四)
62 3
fast.ai 深度学习笔记(四)(1)
|
15天前
|
机器学习/深度学习 算法框架/工具 PyTorch
fast.ai 深度学习笔记(五)(4)
fast.ai 深度学习笔记(五)
71 3
fast.ai 深度学习笔记(五)(4)
|
机器学习/深度学习 自然语言处理 Web App开发
fast.ai 深度学习笔记(五)(3)
fast.ai 深度学习笔记(五)
119 2
fast.ai 深度学习笔记(五)(3)
|
15天前
|
机器学习/深度学习 自然语言处理 Python
fast.ai 深度学习笔记(五)(2)
fast.ai 深度学习笔记(五)
105 4
fast.ai 深度学习笔记(五)(2)