深度学习 2:第 1 部分第 4 课
原文:
medium.com/@hiromi_suenaga/deep-learning-2-part-1-lesson-4-2048a26d58aa
译者:飞龙
来自 fast.ai 课程的个人笔记。随着我继续复习课程以“真正”理解它,这些笔记将继续更新和改进。非常感谢 Jeremy 和Rachel 给了我这个学习的机会。
第 4 课
学生的文章:
- 改进我们使用学习率的方式
- 循环学习率技术
- 探索带有重启的随机梯度下降(SGDR)
- 使用不同学习率的迁移学习
- 让计算机看得比人类更好
Dropout [04:59]
learn = ConvLearner.pretrained(arch, data, ps=0.5, precompute=True)
precompute=True
:预先计算出最后一个卷积层的激活。请记住,激活是根据一些权重/参数计算出来的数字,这些权重/参数构成了卷积核/滤波器,并且它们被应用于前一层的激活或输入。
learn ''' Sequential( (0): BatchNorm1d(1024, eps=1e-05, momentum=0.1, affine=True) (1): Dropout(p=0.5) (2): Linear(in_features=1024, out_features=512) (3): ReLU() (4): BatchNorm1d(512, eps=1e-05, momentum=0.1, affine=True) (5): Dropout(p=0.5) (6): Linear(in_features=512, out_features=120) (7): LogSoftmax() ) '''
learn
— 这将显示我们在末尾添加的层。这些是我们在precompute=True
时训练的层
(0), (4): BatchNorm
将在最后一课中讨论
(1), (5): Dropout
(2):Linear
层简单地意味着矩阵相乘。这是一个具有 1024 行和 512 列的矩阵,因此它将接收 1024 个激活并输出 512 个激活。
(3):ReLU
— 只是用零替换负数
(6): Linear
— 第二个线性层,将前一个线性层的 512 个激活通过一个新的矩阵相乘 512 乘以 120,并输出 120 个激活
(7): Softmax
— 返回总和为 1 的数字的激活函数,每个数字都在 0 到 1 之间:
出于微小的数值精度原因,事实证明取 softmax 的对数比直接取 softmax 要好[15:03]。这就是为什么当我们从模型中获取预测时,我们必须执行np.exp(log_preds)
。
什么是Dropout
和什么是p
?[08:17]
Dropout(p=0.5)
如果我们对Conv2
层应用了p=0.5
的 dropout,它看起来像上面那样。我们遍历,选择一个激活,并以 50%的概率删除它。因此,p=0.5
是删除该单元的概率。输出实际上并没有太大变化,只是有点。
在一个层中随机丢弃一半的激活具有有趣的效果。需要注意的一点是,对于每个小批量,我们在该层中随机丢弃不同的一半激活。这迫使它不会过拟合。换句话说,当一个特定的激活被删除时,模型必须尝试找到一个表示,即使在每次随机丢弃一半激活时,它仍然有效。
这对于使现代深度学习工作并几乎解决泛化问题至关重要。Geoffrey Hinton 和他的同事们提出了这个想法,受到了大脑工作方式的启发。
p=0.01
将丢弃 1%的激活。这几乎不会改变事情,也不会防止过拟合(不具有泛化性)。p=0.99
将丢弃 99%的激活。不会过拟合,对泛化很好,但会降低准确性。- 默认情况下,第一层为
0.25
,第二层为0.5
。如果发现过拟合,开始逐步增加-尝试将所有设置为0.5
,仍然过拟合,尝试0.7
等。如果欠拟合,可以尝试降低,但不太可能需要降低太多。 - ResNet34 的参数较少,因此不会过度拟合,但对于更大的架构如 ResNet50,通常需要增加辍学率。
您是否想知道为什么验证损失在训练早期特别好?这是因为我们在对验证集进行推断(即进行预测)时关闭了辍学。我们希望使用我们能够使用的最佳模型。
问题:您是否需要做任何事情来适应丢弃激活的事实?我们不需要,但当您说p=0.5
时,PyTorch 会执行两件事。它会丢弃一半的激活,并将所有已经存在的激活加倍,以使平均激活不变。
在 Fast.ai 中,您可以传递ps
,这是所有添加层的p
值。它不会改变预训练网络中的辍学,因为它应该已经使用了适当水平的辍学进行训练:
learn = ConvLearner.pretrained(arch, data, ps=0.5, precompute=True)
您可以通过设置ps=0.
来删除辍学,但即使经过几个时期,我们开始严重过拟合(训练损失≪验证损失):
[2\. 0.3521 0.55247 0.84189]
当ps=0.
时,辍学层甚至不会添加到模型中:
Sequential( (0): BatchNorm1d(4096, eps=1e-05, momentum=0.1, affine=True) (1): Linear(in_features=4096, out_features=512) (2): ReLU() (3): BatchNorm1d(512, eps=1e-05, momentum=0.1, affine=True) (4): Linear(in_features=512, out_features=120) (5): LogSoftmax() )
您可能已经注意到,它已经添加了两个Linear
层。我们不必这样做。您可以设置xtra_fc
参数。注意:您至少需要一个,它接受卷积层的输出(在此示例中为 4096)并将其转换为类别数(120 种狗品种):
learn = ConvLearner.pretrained( arch, data, ps=0., precompute=True, xtra_fc=[] ); learn ''' Sequential( (0): BatchNorm1d(1024, eps=1e-05, momentum=0.1, affine=True) (1): Linear(in_features=1024, out_features=120) (2): LogSoftmax() ) ''' learn = ConvLearner.pretrained( arch, data, ps=0., precompute=True, xtra_fc=[700, 300] ); learn ''' Sequential( (0): BatchNorm1d(1024, eps=1e-05, momentum=0.1, affine=True) (1): Linear(in_features=1024, out_features=**700**) (2): ReLU() (3): BatchNorm1d(700, eps=1e-05, momentum=0.1, affine=True) (4): Linear(in_features=700, out_features=**300**) (5): ReLU() (6): BatchNorm1d(300, eps=1e-05, momentum=0.1, affine=True) (7): Linear(in_features=300, out_features=120) (8): LogSoftmax() ) '''
问题:有没有一种特定的方法可以确定是否过拟合?是的,您可以看到训练损失远低于验证损失。您无法确定是否过度拟合。零过拟合通常不是最佳选择。您唯一要做的就是使验证损失降低,因此您需要尝试一些方法,看看什么可以使验证损失降低。随着时间的推移,您会对您的特定问题过度拟合的程度有所了解。
问题:为什么平均激活很重要?如果我们只删除了一半的激活,那么接下来以它们作为输入的激活也会减半,以及之后的所有激活。例如,如果这大于 0.6,则毛茸茸的耳朵是毛茸茸的,现在只有大于 0.3 才是毛茸茸的-这改变了含义。这里的目标是删除激活而不改变含义。
问题:我们可以通过层设置不同的辍学率吗?是的,这就是为什么称为ps
:
learn = ConvLearner.pretrained( arch, data, ps=[0., 0.2], precompute=True, xtra_fc=[512] ); learn ''' Sequential( (0): BatchNorm1d(4096, eps=1e-05, momentum=0.1, affine=True) (1): Linear(in_features=4096, out_features=512) (2): ReLU() (3): BatchNorm1d(512, eps=1e-05, momentum=0.1, affine=True) (4): Dropout(p=0.2) (5): Linear(in_features=512, out_features=120) (6): LogSoftmax() ) '''
- 目前还没有关于早期或后期层应该具有不同辍学率的经验法则。
- 如果有疑问,对每个全连接层使用相同的辍学率。
- 通常人们只在最后一个线性层上放置辍学。
问题:为什么监控损失而不是准确率?损失是我们可以看到验证集和训练集的唯一东西。正如我们后来了解的那样,损失是我们实际优化的东西,因此更容易监控和理解其含义。
问题:在添加辍学率后,我们需要调整学习率吗?似乎不会对学习率产生足够的影响以引起注意。理论上可能会,但不足以影响我们。
结构化和时间序列数据
有两种类型的列:
- 分类——它有一定数量的“级别”,例如 StoreType、Assortment
- 连续型——它有一个数字,该数字的差异或比率具有某种含义,例如 CompetitionDistance
cat_vars = ['Store', 'DayOfWeek', 'Year', 'Month', 'Day', 'StateHoliday', 'CompetitionMonthsOpen', 'Promo2Weeks', 'StoreType', 'Assortment', 'PromoInterval', 'CompetitionOpenSinceYear', 'Promo2SinceYear', 'State', 'Week', 'Events', 'Promo_fw', 'Promo_bw', 'StateHoliday_fw', 'StateHoliday_bw', 'SchoolHoliday_fw', 'SchoolHoliday_bw'] contin_vars = ['CompetitionDistance', 'Max_TemperatureC', 'Mean_TemperatureC', 'Min_TemperatureC', 'Max_Humidity', 'Mean_Humidity', 'Min_Humidity', 'Max_Wind_SpeedKm_h', 'Mean_Wind_SpeedKm_h', 'CloudCover', 'trend', 'trend_DE', 'AfterStateHoliday', 'BeforeStateHoliday', 'Promo', 'SchoolHoliday'] n = len(joined); n
- 像
Year
,Month
这样的数字,尽管我们可以将它们视为连续的,但我们不必这样做。如果我们决定将Year
作为分类变量,我们告诉我们的神经网络,对于每个不同的Year
“级别”(2000、2001、2002),你可以完全不同地对待它;而如果我们说它是连续的,它必须提出某种平滑函数来拟合它们。因此,通常实际上是连续的但没有许多不同级别的事物(例如Year
,DayOfWeek
),通常最好将它们视为分类变量。 - 选择分类变量还是连续变量是您要做的建模决策。总之,如果数据中是分类的,那么它必须是分类的。如果数据中是连续的,您可以选择在模型中将其视为连续或分类。
- 通常,浮点数很难转换为分类变量,因为有许多级别(我们称级别数为“基数”——例如,星期几变量的基数为 7)。
问题:您是否对连续变量进行分箱?Jeremy 不对变量进行分箱,但我们可以对最高温度等进行分组,例如 0-10,10-20,20-30,并将其视为分类变量。有趣的是,上周刚刚发表了一篇论文,其中一组研究人员发现有时分箱可能有所帮助。
问题:如果将年份用作类别,当模型遇到以前从未见过的年份时会发生什么?我们会解决这个问题,但简短的答案是它将被视为未知类别。Pandas 有一个特殊的未知类别,如果它看到以前未见过的类别,它将被视为未知。
for v in cat_vars: joined[v] = joined[v].astype('category').cat.as_ordered() for v in contin_vars: joined[v] = joined[v].astype('float32') dep = 'Sales' joined = joined[cat_vars+contin_vars+[dep, 'Date']].copy()
- 循环遍历
cat_vars
并将适用的数据框列转换为分类列。 - 循环遍历
contin_vars
并将它们设置为float32
(32 位浮点数),因为这是 PyTorch 所期望的。
从一个小样本开始
idxs = get_cv_idxs(n, val_pct=150000/n) joined_samp = joined.iloc[idxs].set_index("Date") samp_size = len(joined_samp); samp_size
这是我们的数据样子。尽管我们将一些列设置为“category”(例如‘StoreType’,‘Year’),但 Pandas 在笔记本中仍然显示为字符串。
df, y, nas, mapper = proc_df(joined_samp, 'Sales', do_scale=True) yl = np.log(y)
proc_df
(处理数据框)——Fast.ai 中的一个函数,执行以下几项操作:
- 将因变量提取出来,放入一个单独的变量中,并从原始数据框中删除它。换句话说,
df
没有Sales
列,而y
只包含Sales
列。 do_scale
:神经网络非常喜欢输入数据的均值大约为零,标准差大约为 1。因此,我们取出数据,减去均值,除以标准差以实现这一点。它返回一个特殊对象,用于跟踪用于归一化的均值和标准差,以便稍后对测试集执行相同操作(mapper
)。- 它还处理缺失值——对于分类变量,它变为 ID:0,其他类别变为 1、2、3 等。对于连续变量,它用中位数替换缺失值,并创建一个新的布尔列,指示是否缺失。
处理后,例如 2014 年变成 2,因为分类变量已被替换为从零开始的连续整数。原因是,我们稍后要将它们放入矩阵中,我们不希望矩阵在可以只有两行时却有 2014 行。
现在我们有一个不包含因变量且所有内容都是数字的数据框。这就是我们需要进行深度学习的地方。查看机器学习课程以获取更多详细信息。机器学习课程中涵盖的另一件事是验证集。在这种情况下,我们需要预测接下来两周的销售额,因此我们应该创建一个验证集,即我们训练集的最后两周:
val_idx = np.flatnonzero((df.index<=datetime.datetime(2014,9,17)) & (df.index>=datetime.datetime(2014,8,1)))
让我们直接进入深度学习行动[39:48]
对于任何 Kaggle 竞赛,重要的是您要对您的指标有一个很好的理解 - 您将如何被评判。在这个比赛中,我们将根据均方根百分比误差(RMSPE)进行评判。
def inv_y(a): return np.exp(a) def exp_rmspe(y_pred, targ): targ = inv_y(targ) pct_var = (targ - inv_y(y_pred))/targ return math.sqrt((pct_var**2).mean()) max_log_y = np.max(yl) y_range = (0, max_log_y*1.2)
- 当您对数据取对数时,得到均方根误差将实际上得到均方根百分比误差。
md = ColumnarModelData.from_data_frame( PATH, val_idx, df, yl.astype(np.float32), cat_flds=cat_vars, bs=128, test_df=df_test )
- 像往常一样,我们将首先创建一个模型数据对象,其中包含验证集、训练集和可选的测试集。从中,我们将得到一个学习器,然后我们可以选择调用
lr_find
,然后调用learn.fit
等等。 - 这里的区别是我们不是使用
ImageClassifierData.from_csv
或.from_paths
,我们需要一种称为ColumnarModelData
的不同类型的模型数据,并调用from_data_frame
。 PATH
:指定存储模型文件等的位置val_idx
:我们要放入验证集的行的索引列表df
:包含自变量的数据框yl
:我们取proc_df
返回的因变量y
,并取其对数(即np.log(y)
)cat_flds
:要作为分类变量处理的列。请记住,到这个时候,一切都是数字,所以除非我们指定,否则它会将它们全部视为连续的。
现在我们有一个标准模型数据对象,我们熟悉并包含train_dl
,val_dl
,train_ds
,val_ds
等。
m = md.get_learner( emb_szs, len(df.columns)-len(cat_vars), 0.04, 1, [1000,500], [0.001,0.01], y_range=y_range )
- 在这里,我们要求它创建一个适合我们模型数据的学习器。
0.04
:要使用多少 dropout[1000,500]
:每个层中要有多少激活[0.001,0.01]
:在后续层中要使用多少 dropout
关键新概念:嵌入[45:39]
让我们暂时忘记分类变量:
请记住,您永远不要在最后一层中放置 ReLU,因为 softmax 需要负数来创建低概率。
全连接神经网络的简单视图[49:13]:
对于回归问题(而非分类),您甚至可以跳过 softmax 层。
分类变量[50:49]
我们创建一个新的矩阵,有 7 行,以及我们选择的列数(例如 4),并用浮点数填充它。要将“Sunday”添加到我们的连续变量的秩 1 张量中,我们查找这个矩阵,它将返回 4 个浮点数,我们将它们用作“Sunday”。
最初,这些数字是随机的。但是我们可以将它们通过神经网络,并以减少损失的方式更新它们。换句话说,这个矩阵只是我们神经网络中的另一组权重。这种类型的矩阵被称为“嵌入矩阵”。嵌入矩阵是一种我们从该类别的零到最大级别数之间开始的整数。我们索引到矩阵中找到特定行,并将其附加到所有连续变量,之后的一切就和以前一样(线性→ReLU→等等)。
问题:这四个数字代表什么?我们将在查看协同过滤时了解更多,但目前,它们只是我们正在学习的参数,最终给我们带来了良好的损失。我们将在后面发现,这些特定的参数通常是人类可解释的,而且相当有趣,但这是一个副作用。
问题:对于嵌入矩阵的维度有好的启发吗?我有!让我们看一看。
cat_sz = [ (c, len(joined_samp[c].cat.categories)+1) for c in cat_vars ] cat_sz ''' [('Store', 1116), ('DayOfWeek', 8), ('Year', 4), ('Month', 13), ('Day', 32), ('StateHoliday', 3), ('CompetitionMonthsOpen', 26), ('Promo2Weeks', 27), ('StoreType', 5), ('Assortment', 4), ('PromoInterval', 4), ('CompetitionOpenSinceYear', 24), ('Promo2SinceYear', 9), ('State', 13), ('Week', 53), ('Events', 22), ('Promo_fw', 7), ('Promo_bw', 7), ('StateHoliday_fw', 4), ('StateHoliday_bw', 4), ('SchoolHoliday_fw', 9), ('SchoolHoliday_bw', 9)] '''
- 这里是每个分类变量及其基数的列表。
- 即使原始数据中没有缺失值,你仍然应该留出一个未知值。
- 确定嵌入大小的经验法则是基数大小除以 2,但不超过 50。
emb_szs = [(c, min(50, (c+1)//2)) for _,c in cat_sz] emb_szs ''' [(1116, 50), (8, 4), (4, 2), (13, 7), (32, 16), (3, 2), (26, 13), (27, 14), (5, 3), (4, 2), (4, 2), (24, 12), (9, 5), (13, 7), (53, 27), (22, 11), (7, 4), (7, 4), (4, 2), (4, 2), (9, 5), (9, 5)] '''
然后将嵌入大小传递给学习器:
m = md.get_learner( emb_szs, len(df.columns)-len(cat_vars), 0.04, 1, [1000,500], [0.001,0.01], y_range=y_range )
问题:除了随机初始化,是否有初始化嵌入矩阵的方法?我们可能会在课程后面谈论更多关于预训练的内容,但基本思想是,如果 Rossmann 的其他人已经训练了一个神经网络来预测奶酪销售,你可能会从他们的店铺嵌入矩阵开始,以预测酒类销售。例如,Pinterest 和 Instacart 就是这样做的。Instacart 使用这种技术来为他们的购物者规划路线,Pinterest 使用它来决定在网页上显示什么。他们有产品/店铺的嵌入矩阵在组织中共享,这样人们就不必训练新的了。
问题:使用嵌入矩阵相比独热编码有什么优势?对于上面的星期几示例,我们可以轻松地传递 7 个数字(例如,星期日为[0, 1, 0, 0, 0, 0, 0])。那也是一组浮点数,完全可行——这通常是统计学中多年来使用分类变量的方式(称为“虚拟变量”)。问题是,星期日这个概念只能与一个单一的浮点数相关联。因此它具有这种线性行为——它说星期日更多或更少是一个单一的事物。通过嵌入,星期日是一个四维空间中的概念。我们通常发现的情况是,这些嵌入向量往往具有丰富的语义概念。例如,如果周末有不同的行为,你会发现周六和周日的某个特定数字更高。
通过使用高维向量而不仅仅是一个单一数字,深度学习网络有机会学习这些丰富的表示。
嵌入的概念被称为“分布式表示”——神经网络的最基本概念。这是神经网络中的一个概念,它具有一个高维表示,很难解释。这个向量中的数字甚至不必只有一个含义。如果这个数字低,那个数字高,它可能表示一件事,如果那个数字高,那个数字低,它可能表示另一件事,因为它经过了这个丰富的非线性函数。正是这种丰富的表示使得它能够学习这样有趣的关系。
问题:嵌入适用于某些类型的变量吗?[01:02:45] 嵌入适用于任何分类变量。唯一不能很好工作的是具有太高基数的变量。如果您有 60 万行数据,一个变量有 60 万个水平,那就不是一个有用的分类变量。但总的来说,在这个比赛中的第三名真的决定将所有不太高基数的变量都作为分类变量。一个很好的经验法则是,如果您可以将一个变量变成分类变量,最好这样做,因为这样它可以学习到丰富的分布式表示;否则,如果您将其保留为连续变量,它最多只能尝试找到一个适合它的单一函数形式。
fast.ai 深度学习笔记(二)(2)https://developer.aliyun.com/article/1482682