概述
特殊模型问题
模型最基本的东西
1. 确定要预测问题(对象)
2. forward输出什么
3. loss选择什么
对确定预测的问题(对象),一般可以用简单A+B=C来表达。
我想研究A在B的限制下的C。那么A用一系列特征表达,B用一系列特征表达,花里胡哨运算一通得到某个值,我们再强行许愿令其为C即可。
forward输出什么,倒是可以与loss一起考虑的东西。 因为forward的output,又是loss的input。
确定loss怎么算,就知道我们希望模型forward出一个什么东西来。
回归问题。
loss function一般是mse,rmse之类的。
但对于特殊的问题,比如我想预测的东西只能是正数,且数值极小,且是离散的整数。
比如某个冷门小卖部每天卖出的毛巾数量,可能几天都未必有1个交易量。
预测值小且十分稀疏时,(例如0,1,0,2,0,0,1...),mse之类的就不好用了。
比如true_y=1,预测值pred_y=0.8,差值0.2,在算法MSE里,平方之后会变成0.04,太小了。
如此一来,反向传播的梯度也是在小数点后2位以下了。你的学习率lr又是一个小数点后几位的东西,很容易就逼近了精度极限,怎么训练得动。
float16一般是小数点后6位,换成float64也就小数点后14位,可能好是好一点,显存开销也加倍呀。
而假如换一个问题,预测书包价格,true_y=10,pred_y=8,则会拿到4,这样的梯度就比较让人喜闻乐见。
所以可以看出来,y的值域对选什么loss fn,选MSE还是RMSE,有挺大的影响。
那么x的值域有没有影响呢?奇怪的是,现在的人似乎都默认会做输入的scaling,弄成均值0方差1的input_x,所以x的值域就不怎么被考虑了。
回到这个【非负数】、【稀疏】、【离散】的小卖部毛巾问题上。
为了解决值域太小的问题,我试过一种思路。
可以对【tartget_value的分布】做shift,比如大概看了一眼,true_y的分布在[0,3]之间。
我们令true_y' = 10*true_y + 10,这样整个分布就移动到了[10,40]。
之后对(pred_y-10)/10,就是真正的预测值了。
这样的效果好不好呢?只能说对精度有一点提升,实际上拟合能力还是受限于模型本身。
建模的时候,要处理【必须大于等于0】这个事情,还是很难啊。
比如预测出来是9,经过减10除10,得到-0.1,怎么办呢,强制取0?
我是这样做的,但还是会影响精度。
因为得到【-0.1】,有可能是模型想告诉你【我有很小的概率认为它是1,因为有点小所以被表达成负数】。
【如果我想告诉你,我很确定这件事完全不可能发生,预测的购买量就是0,那我为什么不给你输出一个-1000,-10000呢?】
你看,单侧限制【非负数】的预测问题,却输出【负数】的结果,是有很大的可解释空间的。
【负数的绝对值越大】表明【越不可能发生购买】。那么对于【-0.1】这种【绝对值很小的负数】,是不是就说明【不会发生购买】的可能性并没有那么大? 也就是说还是存在一定可能性发生购买的。
如果直接令【-0.1】得0,那么这部分可能性的信息就被丢弃了。
你对【-0.1】表达的信息,认为等价于【-1000000】,这显然是不合理的。
所谓的回归问题,本来就是绕着真实值y进行双侧波动,但这类问题等于只许你在单侧波动。
但另一侧的波动也是包含信息的。本来一个正弦波,你切一刀,下半边就没了,这不是残疾波么。
我试过用ReLU强制平抑,这样可以保证输出一定非负,但容易欠拟合。
loss降不下去,我认为这是信息丢失的缘故。
而且,我们要注意到,这个问题还要求结果必须是离散的。
那么预测出来是25,减10除以10得到1.5,你是算1去提交,还是算2去提交呢?
四舍五入未免太蠢了吧。
对线性模型最后一层来说,1.5到1和到2的距离是一致的,你用四舍五入就违背了这种【等价距离】。
好一点的思路是,对这种离散值问题,用区间分割的方法。
这样一来我们就不再预测某个具体的值,而是预测落在第i个区间的概率。
如此,也就不必为1.5是属于1还是2困扰了。
因为会输出n个概率值,softmax一下就知道是取区间1还是区间2了。
对小卖部毛巾这个问题,可以取n=10,毕竟如果一天卖出10条以上我们反而认为是异常值了。
进一步地,我们得到10个区间,[0,1),[1,2)......[9,10),每个区间取左端点,分别预测概率即可。
用如上方法,我们成功地把一个回归问题,转化成了分类问题。
这样听起来是不是机智多了?至少听上去,信息几乎是无损的。
但这种方法,对于取值范围很大的问题不适用。
如果取值1~2w,softmax要接受2w个维度,显然有点强人所难。
当然也不是不能做,拿算力怼嘛。
可是你有2w个区间,那数据集得多大?
有多少个区间是从来训练不到的?
光有算力有啥用呢,对数据的要求也太高了。
梯度NaN问题
1.输入+输出检查
如果一开始就爆NaN而不是训练一段时间之后,那么就需要检查输入输出数据。
主要是检查输出。毕竟输入里面存在NaN这么蠢的事情,数据清洗的时候如果还没发现也太丢人了。
i)输出的大小,范围。
根据这个区思考,拿输出去计算loss的时候有没有可能出现分母为0,log(0)之类的情况。
当然出于简单无脑易操作的角度,你可以在每一个除法的分母和对数运算的括号里都加上一下小正数。
比如a/(b+1e-20),以及log(x+1e-20)。
虽然无脑,但是隐患也是有的。好自斟酌。
i)输出的形状,这是我踩过的另一个坑了。
回归问题linear最后一层dim=1的话,输出的output.shape=(batchsize,1)
但是输入的标签label_y.shape=(batchsize)
所以根据pytorch的特性,(batchsize,1)-(batchsize) = (batchsize,batchsize)
神奇的计算。
这会影响求导,最后导致梯度爆炸,于是算出来的东西爆炸。
所以在linear之后view一下形状很重要。
out=out.view(-1) #(batchsize)
2.学习率+梯度裁剪
lr=0.08+max_norm=5.0,爆炸NaN。
lr=0.02+max_norm=2.0,正常训练。
就是这么神奇......玄学调参。
optimizer.zero_grad()
loss.backward()
#梯度裁剪
torch.nn.utils.clip_grad_norm_(model.parameters(), max_norm = 2.0)
optimizer.step()
最后
以上就是着急学姐为你收集整理的190523日志的全部内容,希望文章能够帮你解决190523日志所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
发表评论 取消回复