我是写Python的,刚入行那会儿觉得自己很牛。列表推导式玩得溜,装饰器也写得飞起,GitHub上的star比同事多两倍。直到有一天,组长丢过来一个机器学习需求。他说,用逻辑回归做个用户流失预测模型。我打开编辑器,脑子一片空白。代码里到处都是我的名字,但“损失函数”是什么,“梯度下降”又是什么。我一个都没见过。
我硬着头皮去翻官方文档。sklearn的文档写得挺好,但全是数学符号,一堆希腊字母,看着像天书。参数“C”是正则化强度的倒数,“solver”要选“lbfgs”还是“sag”?我看不懂。网上教程一搜一大把,但每篇都写着“首先定义假设函数,然后计算代价函数”。我连假设函数在哪儿都不知道。那段时间我写代码全靠试错,参数调来调去,模型准确率就是上不去。同事问我为什么选这个参数,我说“凭感觉”。
后来我去补了线性代数和微积分。说实话,不是多难的东西,就是矩阵乘法跟求导。但这恰恰是当年我混过去的课。我上课玩手机,考试靠小抄,觉得自己以后写代码用不着。现在报应来了。向量的点积不过是数字乘完再相加,可我在代码里就是没办法把特征矩阵和权重向量联系起来。同事说这叫“内积”,我觉得这叫“惩罚”。
有一次看人家用Python写了一个SVM的demo。代码不长,核心就是一个核函数。他把数据从二维映射到了三维空间,然后画了一条完美的分割线。我看了半天,觉得他就是在二维坐标里画了一条弯弯曲曲的线。我问他怎么做到的,他说“这就是核技巧啊,你学过核函数就能理解”。我连什么核函数都没听过。那是一个周末的下午,我一个人在办公室对着那不到五十行的代码,从头到尾读了三遍,不懂就是不懂。我气得差点把显示器关了。
数学不好,连调包都调不舒坦。你要知道,sklearn里每个模型都有默认参数。你以为默认的就是最优的,其实不是。正规化项惩罚强度的默认值是1.0,但实际业务里这个值可能是0.01或者100。你不理解其中含义,就只能全试一遍。我试过九种不同参数组合,等结果跑了整整一个下午。最后发现默认参数才是最差的。我同事只看了一眼公式,用了三分钟就推导出最优惩罚强度应该在0.5附近。那份报告他写了一个结果,附了一句推导过程。我写了一个编程题。这就是差距。
现在我开始理解那些数学课的意义了。它不是让你记住泰勒展开,不是让你背麦克劳林级数。它训练的是另一种思维方式。比如你遇到的问题数据很多、维度很高,你不用全看,你只需要知道哪些对结果影响最大。这就是求偏导。比如你处理图片分类,不同角度、不同光照下的同一物体看起来不一样,但内核是同一个东西。这就是特征空间的思想。机器学习跟写业务代码完全是两码事,业务代码讲究逻辑严密,机器学习讲究概率统计。你写个计算器,永远知道输入2加3等于5。但模型给你预测的结果,从来不说百分之百对,它只会告诉你概率。
现在我写机器学习代码,每一步都慢下来。哪怕只是加载数据集,我也先看数据的分布。均值是多少,方差又是多少,异常值多不多。这些都不难,初中数学水平。但你要有意识去看。很多人跟我一样,上来就用pandas读个csv,接着调用train_test_split切分,然后fit,predict。不做的中间环节是数据清洗、特征工程。这些才是关键。特征工程本身就是数学,你得算互信息、算相关系数,知道哪些特征冗余了。别傻傻把所有特征都喂给模型,会过拟。
有一次我写了一万多行代码做推荐系统,最后发现效果不如人家的矩阵分解。矩阵分解就是线性代数的知识,四个字母U、Σ、V。但你能用Python实现吗?实现是其次,理解才重要。我只知道调用sklearn.decomposition里的NMF,参数设好就行了。但项目需要你自定义损失函数的时候,“包装好的轮子”就不够用了。你只能自己写。你自己写不出来。
程序员最怕的不是项目难,是解决问题的方法超出了自己的能力边界。写CRUD界面,调API接口,这是能力圈内的事。搞机器学习,这是能力圈外的事。你连学都不愿意学,那你就永远停在圈内。别觉得我说话难听,我自己就是从这个坑里爬出来的。说实在话,数学不好这事,不是说你不会算1+1等于2,而是说你没有耐心去看那些推导过程。你觉得它没用。实际上,当你真要面对复杂问题的时候,只有数学能给你提供唯一可靠的路径。试错只能撞大运。
我现在写Python代码,还会在文件开头注释里写下损失函数的公式。不是给别人看,是提醒自己。这个公式是从哪里来的,为什么这么写。以后碰到新问题,能不能复用。当年混过的数学,现在全变成了“看不懂的文档”和“调不好的参数”。没人救得了你,只有你自己愿意回头翻翻课本。你看到这里,如果能停下来想想自己有没有踩过同样的坑,那这篇文档就没白写。