诸神缄默不语-个人CSDN博文目录
李宏毅2021春季机器学习课程视频笔记集合
首先以当日流量作为自变量 x x x,预测次日流量 y y y。
整个深度学习模型的步骤如下图所示:建立模型、定义算法、优化参数
从线性模型到神经网络
上面的那种模型都是线性模型linear model,但是线性模型太简单,不管怎么调参数都只是一条直线(下图蓝线),无法拟合复杂的真实情况(如下图红线),因此我们需要换一种更有弹性的模型
这种模型本身的限制叫做model bias(注意跟模型参数bias不一样)
在YouTube上这一部分的视频评论中,李老师回复称这里的model bias就是inductive bias
事实上任何分段线性函数piecewise linear curve(就是像红线这种每一段都是直线的曲线)都可以用一系列这种蓝线来加总拟合,不是piecewise linear curve的曲线也可以近似成piecewise linear curve
这种蓝线(hard sigmoid)可以用sigmoid来拟合
不同的参数( c c c b b b w w w) → 不同的sigmoid function → 组合逼近出不同的piece linear function → 近似得到不同的continuous function
来自弹幕:
为什么需要非线性变化?用于拟合hard sigmoid
为什么需要不同参数?为了得到不同的hard sigmoid
最后合出来的红线(真实情况)就可以写成这样的公式: y = b + ∑ i c i y=b+\sum\limits_{i}c_i y=b+i∑ci s i g m o i d ( b i + w i x 1 ) sigmoid(b_i+w_ix_1) sigmoid(bi+wix1),如图所示
用直观的方式画出这个新模型
将三个式子简化为矩阵形式
σ \sigma σ表示做sigmoid
将这三个式子连起来
就得到了模型最后的式子
将未知参数排成一个向量( W W W 拿行和拿列是一样的)组成 θ \theta θ(就是下文优化部分用的)
老师答疑环节:
①注意这个sigmoid的数目是可以自己定的(就是神经网络里的神经元的数目是可以自己定的),而且sigmoid越多,可以产生出的piecewise linear function就越复杂(可以产生和sigmoid一样多的线段)
②不用hard sigmoid是因为公式难写出来,其实也可以用
在实际运行中,不会一次运行所有数据,而会用batch:将整个数据分成很多batch,每次运行一个batch,update一次参数;将所有batch运行完一次,是为一个epoch。
batch size是需要设置的超参。
从Sigmoid到ReLU
hard sigmoid可以看作是两个ReLU加起来,如图所示:
所以Sigmoid也可以换成ReLU:实验结果显示ReLU的效果会好一些。
深度神经网络:很多层→实验证明加层效果会好
一个sigmoid就是一个neuron,很多个neuron就组成了neural network神经网络。
neuron层叫layer,输入输出之外的层叫hidden layer。
有很多层就叫deep learning。
既然很多个sigmoid或ReLU就能拟合任意函数,那么展开一层极多的神经元不就行了吗,为什么选择把网络变深而不是变胖呢?这一问题留待以后解读。
我自己做过注释的colab文件,已放在GitHub上:Google_Colab_Tutorial.ipynb
这部分对无法登入Google Colab的同学而言可能没必要看……
我的浏览器会直接显示中文版,所以我就截图我自己的中文版了。
PyTorch Tutorial分成了两部分,第一部分是中文讲的视频,主要介绍了PyTorch在一整个深度学习流程中的用法;第二部分是英文讲的视频和colab文件,主要通过 max()
及其相关函数介绍了PyTorch文档的阅读及使用技巧。
这一部分助教讲得飞快,没有基础大概是听不懂的(反正我看弹幕里很多人抱怨听不懂),在此安利我之前写的博文:60分钟闪击速成PyTorch(Deep Learning with PyTorch: A 60 Minute Blitz)学习笔记。这篇文章是PyTorch官方入门教程Deep Learning with PyTorch: A 60 Minute Blitz的笔记。这个教程很好懂,很推荐。
x = x.to('cpu')
x = x.to('cuda')
torch.cuda.is_available()
我自己做过注释的colab文件,已放在GitHub上:Google_Colab_Tutorial.ipynb
主要是用max()
函数举例,讲了这个函数的功能,PyTorch官方文档怎么看、怎么查、怎么用,以及一些常见的PyTorch使用过程中会出现的异常。
整体任务就是用深度学习做一个回归任务,通过CMU的一组源自美国的数据来预测新冠病毒阳性案例数。
数据字段:
simple baseline就是助教提供的代码,直接运行即可。
我自己做过注释的notebook文件版本放在了GitHub上:HW1_simple_baseline.ipynb。我自己跑的线上结果是private score 1.59707,public score 1.52015。
在数据预处理阶段,simple baseline提供的代码有一些问题,在下面的4.4部分我详细讲。
整体代码的逻辑如下图所示:
我写的代码已上传至GitHub:HW1_medium_baseline.ipynb
medium baseline就是把助教给的simple baseline,数据从使用全部特征改成只使用40个州和前两天测出阳性的比例这42个特征。
核心代码是将target_only这个超参调整为True,然后在Dataset部分判断target_only时,在为True的判断条件下将特征改成:
feats=list(range(40))
feats.append(57)
feats.append(75)
即可。
这样本地loss会变差,但是线上loss会提升很多。
在kaggle平台上可以看到medium baseline在private leaderboard上是1.36937,在public leaderboard上是1.28359。然后我跑出来的结果是private 1.06281,public 1.05794,满足要求。
strong baseline在private leaderboard上是0.89266,在public leaderboard上是0.88017。
以下按顺序介绍调参的参考调整方向、简述我自己的调参心得、详细描述我的调参工作流程。
可资参考的参数调整方向(来源于助教给的notebook文件最后的cell):
反正我自己是没有调出来很高的参数……
从simple baseline到medium baseline就在本地loss变高,但是线上loss变低,这说明本来就已经过拟合了。这就已经导致我调参很尴尬了,因为本地loss没有参考价值了啊,它已经过拟合了啊!
但是我一开始还以为它至少总能保证本地评估指标优于线上评估指标吧,毕竟它是在本地优化的参数啊!但是medium baseline的本地loss就已经比线上loss表现更差的simple baseline要低了(不过这两个loss其实不是一个loss,本地loss是MSE,线上loss是RMSE。下面详细介绍情况),所以我就一直在试图努力优化本地loss,同时参考线上loss来调参(其实按道理讲不应该这样干的!调参应该只基于验证集,测试集只是用来参考评估模型结果的,对着测试集结果调参这种行为是不符合机器学习道义的,是不讲武德的!)。
然后我调到public score最低1.01048之后,我被colab禁用GPU了。
然后我就找找网上的公开代码,发现大家都不公开。为什么呢,明明提交时间已经截止很久了……我只找到了一个贴近strong baseline的代码:HW1_local.ipynb。我觉得这个代码写得很好,提到了很多有参考价值的内容,具体的以下讲。但是这个结果我就很震惊好吧!他把本地loss从原来的MSE调成了kaggle上用的RMSE,所以应该是同一个评估指标,但是他本地loss跑出来0.9594,线上loss跑出来0.88749,本地loss比线上loss还高!!!这数据就很他妈扯淡好吧,这根本就不科学吧!!!这还怎么调啊,这个作者是真的牛逼啊,这都能调出来!!!
如果有别的结果较好的开源代码请cue一下我,我只在kaggle那个比赛和GitHub上查了一下代码。
真的,在那一瞬间,我彻底明白了什么叫玄学,什么叫炼丹。以及我真的开始有点质疑我巧妙错过提升线上指标的参数是个什么神仙运气了,这就是玄不改非、氪不改命的深度学习版吗?我累了。
然后我就不想再认真调了,我就参考这位作者的代码来在我之前自己闭门造车瞎调参的基础上再修改修改了代码,不再不讲武德地对着测试集硬调了。但是,不知道是不是因为我脸黑,所以我测试集结果更差了……
……
……
……
在测试集上获得了更好的结果,但是违背了机器学习的道义,这值得吗?
以下首先概括性地介绍调参时使用的方法和对应效果,然后捋一下我的悲惨调参实验日志……
相关系数核心代码:
selected_feature_columns=[]
threshold=0.8 #阈值
#对于state列:计算斯皮尔曼系数
for i in range(40):
s_ce=train_df['tested_positive.2'].corr(train_df.iloc[:,i+1],method='spearman') #spearman coefficient
if abs(s_ce)>=threshold:
selected_feature_columns.append(i)
#对于40列后的数值型特征:计算皮尔森系数
for i in range(40,93):
p_ce=train_df['tested_positive.2'].corr(train_df.iloc[:,i]) #pearson coefficient
if abs(p_ce)>=threshold:
selected_feature_columns.append(i)
SelectKBest核心代码:
from sklearn.feature_selection import SelectKBest
from sklearn.feature_selection import f_regression
K=20
bestfeatures = SelectKBest(score_func=f_regression, k=K)
bestfeatures.fit(train_df.iloc[:,:-1].values,train_df.iloc[:,-1].values)
selected_feature_columns=list(bestfeatures.get_support(True))
选择参数后,在Dataset部分将feat部分代码增加 feats=list(set(feats+selected_feature_columns))
或直接修改为 feats=selected_feature_columns
即可。
len_data=len(data)
len_train=int(0.7*len_data)
indices_train=list(rng.choice(len_data,len_train,replace=False))
if mode == 'train':
indices=indices_train
elif mode == 'dev':
indices=[i for i in range(len(data)) if i not in indices_train]
我在文件代码里忘写replace=False了,所以不是不放回抽样……所以我想我大约是写错了,但是我懒得改了,反正效果本来就这么差
torch.sqrt()
即可。但是不知道为什么添加之后效果变差了很多,按道理讲应该不变啊?Dataset部分:
传入mu和std参数
if self.mode == "train": #如果是训练集,均值和方差用自己数据
self.mu = self.data[:, 40:].mean(dim=0, keepdim=True)
self.std = self.data[:, 40:].std(dim=0, keepdim=True)
else: #测试集和开发集,传进来的均值和方差是来自训练集保存,如何保存均值和方差,看数据dataload部分
self.mu = mu
self.std = std
self.data[:,40:] = (self.data[:, 40:] - self.mu) / self.std #归一化
self.dim = self.data.shape[1]
DataLoader部分先加载训练集的Dataset,然后将均值和方差返回,再在加载数据时传入作为参数:
def prep_dataloader(path, mode, batch_size, n_jobs=0, target_only=False, mu=None, std=None): #训练集不需要传mu,std, 所以默认值设置为None
''' Generates a dataset, then is put into a dataloader. '''
dataset = COVID19Dataset(path, mu, std, mode=mode, target_only=target_only) # Construct dataset
if mode == 'train': #如果是训练集,把训练集上均值和方差保存下来
mu = dataset.mu
std = dataset.std
dataloader = DataLoader(
dataset, batch_size,
shuffle=(mode == 'train'), drop_last=False,
num_workers=n_jobs, pin_memory=True) # Construct dataloader
return dataloader, mu, std
在加载数据时:
tr_set, tr_mu, tr_std = prep_dataloader(tr_path, 'train', config['batch_size'], target_only=target_only)
dv_set, mu_none, std_none = prep_dataloader(tr_path, 'dev', config['batch_size'], target_only=target_only, mu=tr_mu, std=tr_std)
tt_set, mu_none, std_none = prep_dataloader(tr_path, 'test', config['batch_size'], target_only=target_only, mu=tr_mu, std=tr_std)
反正实验就跑得很心累……
我调出来最好的结果,public score达到1.01048,文件放在了GitHub上:试图冲击strong baseline,没冲过去
照着那篇靠近strong baseline的代码改了改代码结果线上loss越来越差(为什么?因为我脸黑吗?),文件也放在了GitHub上:有参考的strong baseline
实验日志是在Excel上手写的。不知道别人都是咋实现的,想搞个自动化的工具来帮我写实验日志,自己写真是越写越心累(主要是调出来效果越来越差这一点最心累,就像我写毕业论文的时候,写论文本身并不最心累,因为深知自己的论文毫无学术价值所以只能在其上屎上雕花的这种感觉最心累)。
我直接截图吧。
标红是相对于当前及之前实验,这个评估指标下最好的结果。
以下是经代码参考后的实验:
可以参考我之前写过的PyTorch入门教程60分钟闪击速成PyTorch(Deep Learning with PyTorch: A 60 Minute Blitz)学习笔记 Autograd部分,就是讲自动计算微分的
↩︎
这个视频就是简单讲了一些google colab怎么用的相关内容,推荐了一个类似于colab使用手册/入门手册的网页:https://colab.research.google.com/notebooks/welcome.ipynb。另外还建议使用了一个Google的AI Hub平台,我还没有去了解具体是什么
↩︎
对于tape-based autograd,可以参考stackoverflow上的这个问题:What is tape-based autograd in Pytorch?
大致来说,就是有很多种自动微分方法,PyTorch中的tape-based autograd指它所使用的reverse-mode auto diff方法,可以高效计算微分,而且反向传播能用这种方法。在前向传播时autograd记录操作,在反向传播时重现操作(就像磁带一样,所以叫tape-based)。
手动更新参数时需要置required_grad为False,用torch.no_grad()
实现;也可以用优化器直接自动更新参数(optimizer.step(); optimizer.zero_grad()
)。
与TensorFlow的对比此处不录。
↩︎
对这篇文章的阅读笔记:
深度学习流程中的训练阶段是最耗资源的。
在这个阶段,更新参数和输出结果本质上都是矩阵运算。
深度神经网络可能有数以亿计的海量参数。
为了让神经网络训练更快,可以同时进行这些运算,而GPU可以做到这一点。(此处省略一些GPU本来用于图形渲染时的浮点计算工作……等介绍)
GPU比CPU小,但是有更多的logical core。
炫酷的同步运算举例视频:https://www.youtube.com/watch?v=-P28LKWTzrI(展示了用油漆喷画的过程,如果一个点一个点地喷(就像CPU),就要花好久才能喷出一幅简单的画;但是如果同时喷所有点(就像GPU),就可以非常炫酷地一步喷完蒙娜丽莎)
选择GPU还是CPU,需要考虑:
①Memory Bandwidth
②Dataset Size(如果很大大就用GPU)
③Optimization(CPU上更容易做优化)
④GPU更贵
原话:
If you consider a CPU as a Maserati, a GPU can be considered as a big truck.
The CPU (Maserati) can fetch small amounts of packages (3 - 4 passengers) in the RAM quickly whereas a GPU(the truck) is slower but can fetch large amounts of memory (~20 passengers) in one turn.
↩︎
可以参考我之前写过的NumPy Quickstart Tutorial笔记 ↩︎