Esper 发布的文章

前几年人工智能还没火起来的时候,为了打比赛学了一点,结果当时也没找到什么好的门路,学的一知半解,模型倒是会用了,但对原理还是一知半解。最近因为某些原因,决定重新捡起来精学一下,或许是因为人工智能成功火了起来,优质课程越来越多,也越来越好寻找。找了半天之后,决定看李宏毅老师的机器学习课;第一堂花了两个小时从线性拟合讲到深度学习,直接给我整不会了。好痒啊,要长脑子了.jpg

虽然一直以来对深度学习的各概念有所了解,但是毕竟古话说得好,“知其所以然”。我比较确信,自己正处在一种知道这个东西,并且大概知道怎么用,但并不了解原理的状态之中。换句话说,扔给我一个模型,我能让它跑起来,但如何让它结果更好、跑的更快,就超出能力范围了。这时候恰好遇到了李老师的机器学习课,以一种开了全图挂的美妙方式,在讲述神经网络数学原理的同时,还介绍了拟合函数是如何一步步从单调的线性拟合发展成灵活的,个中思路或许并不是其真实的发展路程,然而足够合理,让我能够理解并记住神经网络为什么会发展为如此形态,故在此记录,防止再次忘记。

1. 最基础的模型:线性拟合

首先从最广义的角度来看,数学建模,就是一类寻找特定函数y=f(x),以能够通过已知量x,准确或近似预测未知量y的函数f(x)的过程。无疑,在假设xy存在关联的情况下,最简单的近似函数即为f(x)=x,即x本身。在考虑一般场景下xy不会简单地为同一个值的问题后,对f(x)=x进行泛化,得到线性拟合的基本形式:f(x)=wx+b,其中w为weight,代表yx影响的程度,即直线的斜率;b为bias,代表xy值之间的固定修正,即直线的截距。

2. 更多的信息:线性组合的线性叠加

通过简单地更换函数的类型,比如将线性函数f(x)=x换成指数函数f(x)=e^x,或者周期函数f(x)=\sin{x},并不能应对绝大多数场景,故在此不纳入讨论范围。

俗话说得好,力大砖飞。正如人懂得越多越聪明一样,为了让数学模型更加准确,一种最为直接的方法是向模型中添加更多的自变量。这种修改使得模型中的自变量从单个x变成了一组变量x_1, x_2, ..., x_n,对应地,数学模型变成了f(x_1, x_2, ..., x_n)=\sum_{i=1}^{n}{w_{i}x_{i}}+b,向量化简写后就是经典的f(\textbf{\textit{x}})=\textbf{\textit{w}}^{T}\textbf{\textit{x}}+b。李老师在22年课程中,使用油管播放量预测任务检验了这一方法的有效性——模型效果确实有所提升,但离实际可用仍然有较大的差距。

3. 更精致的拟合:激活函数

模型预测值与实际值之间的差距被称为模型偏差model bias。显然,模型偏差越小,其预测效果越好。然而,线性拟合,或者说是大部分基本函数,在广阔的定义域上,通常并不具有一个稳定的值,调整参数时会使其在整个定义域上的函数值发生明显变化,我称其为“按下葫芦起了瓢”——通过梯度下降等优化算法调整参数后,某些预测效果特别差的样本得到了提升,然而同时可能存在某些预测效果较好的样本,因为其参数被修改,反而预测效果变差了。换句话说,当模型效果无论怎么训练都无法提升时,其最小偏差即可视为模型误差。

面对基本函数“牵一发而动全身”从而产生的模型偏差问题,最直接的应对思路无疑就是寻找不具有这类特征的函数。换言之,我们可以使用定义域有限的函数进行更加细致的准确度优化。考虑到定义域有限制,而表达方式最为通用的函数,无疑就是分段函数了:取两个点,将函数分为三段,左右侧均为常数值,中间为单调变化的曲线,从左端点值过渡到右端点值:

\alpha(x)= \begin{cases} c_1 &, x < x_L \\ wx+b, &, x_L \le x < x_R \\ c_2, &, x \ge x_R \end{cases}

这样就得到了一个相对完美的函数:在给定的定义域内为变量,在给定定义域之外为常量,满足了对模型更加精细调控的要求。在此将这种函数称为斜坡形函数。

通过将两个斜坡形函数叠加,还能实现更加优雅的函数值调控。构造具有相同形状、但相位不同的两个斜坡形分段函数:

\alpha_1(x)= \begin{cases} c_L &, x < x_{L1} \\ wx+b &, x_{L1} \le x < x_{R1} \\ c_R &, x \ge x_{R1} \end{cases} \alpha_2(x)= \begin{cases} c_L &, x < x_{L2} \\ w(x-(c_R-c_L))+b &, x_{L2} \le x < x_{R2} \\ c_R &, x \ge x_{R2} \end{cases}

将两式相减,得到

\alpha(x)=\alpha_1(x)-\alpha_2(x)= \begin{cases} 0 &, x < x_{L1} \\ h(x) &, x_{L1} \le x < x_{R2} \text{(偷懒懒得展开了)} \\ 0 &, x \ge x_{R2} \end{cases}

,直接将\alpha(x)变成了一个只在指定范围内有非零值的函数“片段”,彻底消灭了该函数对定义域其他范围的值影响。

(ps:md个烂typecho,写个css乱用通配符,害得我公式都变形了,调了半天才搞好)

此时的\alpha(x)仅仅只是一个应用于x之上的分段函数。而使其通用泛化的方式非常直接:直接把wx+b塞进\alpha(x)里,即可将原本的线性拟合模型转化为“定义域限定函数”。通过这种方式,近似地实现了使用多个互不干扰的函数构建数学模型的能力,提升了模型的泛化能力,使模型具有更小的偏差下限,能够更好地拟合真实情况。

那么如何构建这种形式的函数呢?答案是对\alpha(x)做进一步分解,使用惊人的注意力不难发现,可以将斜坡形函数拆分为两个更加简单的折线形函数:

(x)^+= \begin{cases} 0 &, x < 0 \\ x &, x \ge 0 \end{cases}

是不是眼熟起来了?是的,芝士ReLU函数(Rectified Linear Unit,带修正的线性单元)。由于在计算机中可以较为轻松地依据数据符号进行条件运算,因而ReLU函数不仅具有良好的数学性能,在实际的模型演算过程中也有得到广泛应用。

根据我个人理解,\alpha(x)这种能将任意输入值映射到具有特定特征(在特定定义域上,其值域范围有限)的函数被统称为激活函数activation function。估计激活这个名字来源于神经网络,意味着神经元对任意外部条件做出的响应,被映射(编码)为一种固定的表达方式。

既然是“统称”,那么自然还有其他的激活函数。不知道是不是出于梯度计算的考虑,其余常见的激活函数都是在定义域上连续可导的,如Sigmoid和tanh:

\sigma(x)=\frac{1}{1+e^{-x}} \tanh{x}=\frac{e^{x}-e^{-x}}{e^{x}+e^{-x}}

以Sigmoid为例,引入了激活函数的线性回归模型现在演化成了这个样子:

\begin{aligned} f(x)&=\sum_{i=1}^{n}{c_i\sigma(w_ix+b_i)}+b \\ &=\sum_{i=1}^{n}{c_i\frac{1}{1+e^{-(w_ix+b_i)}}}+b \end{aligned}

4. “我全都要”:神经网络

(这是书呆子 这是手指.jpg)诶!如果我们结合第二步跟第三步的思路,既引入多个自变量增加已知信息,又引入激活函数降低模型偏差,岂不美哉?

没错,这就是神经网络。按上述思路改进后,得到如下形式的数学模型:

\begin{aligned} f(x)&=\sum_{i=1}^{n}{c_i\sigma(\sum_{j=1}^{n}{w_{ij}x_{i}}+b_i)}+b \\ &=\textbf{\textit{c}}^T\sigma(\textbf{\textit{W}}\textbf{\textit{x}}+\textbf{\textit{b}})+b \end{aligned}

可以看到,模型由多个包含激活函数的子模型(“神经元”)构成,每个神经元将所有自变量作为输入,经过激活函数计算后进行累加汇总,得到最终结果。借用李老师公开课上的图来看,现在的数学模型长这个样子:

nn.png

可以看到,相比于传统的单函数模型,这种模型可近似看作多个子模型独自处理输入,然后汇总成输出,更加贴近人类,或者是神经元处理事件的模型。或许这就是其被称作神经网络的原因吧。从李老师的实验可以看出,使用(单层)神经网络相对于线性回归模型,其准确度有所提升——但显然还不够。

5. “大力出奇迹”:多层神经网络

上述数学模型的发展过程无疑验证了一个道理:大力出奇迹。随着入参和数学模型的规模和复杂度提升,其模型准确度也在或多或少、但是稳定地提升。那么问题来了:如何在神经网络的基础上,进一步提升数学模型的准确度?

扩大输入规模不失为一种有效的方法。然而,在数据集规模固定,或是出于性能要求限制了输入规模的情况下,就需要依靠更加优质的模型结构来提升其精准度了。在今天——AI进入人们——或者至少我的视野近八年的时间内,不论是其神经元结构,还是激活函数的构造,都大同小异,但是模型的性能却得到了质的飞跃。究竟是什么改变了神经网络?

答案是神经元间的拓扑结构。在第4节的数学模型中,“神经元”之间仅仅是并列关系,都只是按照自己的喜好对输入数据赋不同的权重,然后给出输出值;神经元之间没有任何“交流”。

从第4节的图中不难看出,神经网络具有“多输入、单输出”的特征。什么概念具有类似的结构呢?我想,答案应该是逻辑门。在数字逻辑电路的世界里,多个二元值进入逻辑门后,根据给定的真值表,将会得出一个固定的输出值;而这个输出值作为输入值在逻辑门上的映射,将带着输入值的特征,作为输入值进入下一个逻辑门;通过这种方法,计算机科学家们使用逻辑门构建出了当今千姿百态的处理器世界。相似地,如果提升现有的单层神经网络的维度,即取多个神经网络,并计算它们的输出值;然后,再取一个神经网络,将上一级每个神经网络的输出作为输入值,得到一个新的输出值,像下图所示,不就在神经网络的基础上,实现了模型结构的又一次进化吗?

dnn.png

反向传播back propagation为多层神经网络的实现提供了可能。但是效果好不好呢?尝试一下总是没有损失的。这一试可不得了:DNN,CNN,RNN,GAN,LSTM,BERT,Transformer,GPT... 一个个熟悉的名词如井喷式涌出。是的,仅仅是通过搭积木似的将神经网络进行二维组合,学术界就能拿着玩好几年,直到现在大火的各种AI聊天、按要求生成图片/音频/视频等等,无一不是建立在深度神经网络的基石之上。

那么,引用The Fabric of Cosmos里一段非常经典的话:

How could this be?
Does it bother us? Absolutely.

是的,为什么深度神经网络能够做到传统模型做不到的事情,它到底能做到哪些事情,甚至每一个“神经元”动作的含义,对我们而言都是需要了解的、甚至是未知的。那我们要做的,就是走进迷雾,掀开深度神经网络神秘的面纱。

以防万一有人不记得(笑死,搞得跟真的有人看一样),本站第一个域名是esperz.tk

.tk域名暴雷一事,在2023年中就初见端倪。当时在某交流频道里看到有人说注册的.tk域名无缘无故被收回,当时我还在想这人肯定是拿域名做了什么,然后在跟大伙春秋笔法。然后下半年freenom的名声就开始不对劲了,说是因为域名滥用问题在跟ICANN扯皮,新域名都不让注册了。还好这个时候已经把本站域名的有效期延长到24年了,在freenom没倒之前先将就着用吧。

然而不出意外的话,也该出意外了。24年,也就是今年春节,在家里用自己域名测试网络的时候,突然发现打不开网站了。对,不是提示证书过期,直接找不到网站了;nslookup一查,一个大大的Non-existent domain直接拍在我脸上。在freenom上确认发现,域名变成了PENDING状态,不能再进行任何操作了。好嘛,这下我就知道freenom是真的摆烂,开始拿用户数据乱玩了。

然后就准备开始找下家,来到了经典的eu.org环节。一看,哦豁,也是23年底因为请求太多处理不过来,不让申请新域名了。至此,可靠的白嫖手段用尽,不得不进入花钱环节了。在TLD-List上逛了半天之后,最终还是选择向钱包屈服,搞了个长期的.top域名,也就是现在的esperz.top。依稀想起了数年前室友怂恿我搞个.top域名玩,哎,最终还是没能逃出真香定理。

涨价(应该是)不可能涨价的,希望.top域名不要轻易倒闭吧,不然到时候还要去加价买.cc一类的野鸡域名了QAQ

这一年中也没玩太多新花样,主要是花了不少精力把bustub的lab做完了,但因为没搞新域名就一直搁置了,有时间整理下把思路和心得拿出来分享下吧。

起因是换了电脑之后懒得重装系统,但是为了装新驱动而不得不对系统进行了一个新的更。然而用了一两年之后,重新打开DiskGenius的时候才发现盘上有两个Recovery分区,估计巨硬是在安装程序里,从我原有系统盘的末尾切了1个G出来,又做了一个Recovery分区。然而咱们消费级电脑并没有那么高的可用性要求,不需要一个系统配一个Recovery分区,所以打算把分区表更新下,删掉新加的Recovery,让所有现存Windows系统共用一个Recovery分区。

大体流程参考这篇回答,简单来说原理就是分区备份还原,然后用reagentc命令将Windows配置为用我们设置的新Recovery分区:

  1. 给两个Recovery分区分配盘符;
  2. 如果需要,用dism备份和还原分区数据(比如说要删除的Recovery版本更新,想保留);
  3. reagentc /disable
  4. 删除不要的Recovery分区;
  5. reagentc /setreimage /path Z:\Recovery\WindowsRE配置WindowsRE,假设Recovery挂载到Z盘;
  6. reagentc /enable

这个时候理论上已经完成了WindowsRE分区的更换,用reagentc /info可以看到,Recovery分区已经被更换为了目标分区。但是如果直接用diskpart或者DiskGenius一类的工具卸载Recovery分区的话,会发现这么一个问题:要么卸载成功,但是重新用reagentc /info看WindowsRE配置又变回了Disabled,要么就卸载失败,任务管理器里还能看到盘符,或者重启之后分区又重新自动挂载上了。

然而天无绝人之路。在网上一顿乱翻,发现也有人有这个疑惑,一看原来Windows还有一个命令可以删除盘符:

mountvol Z: /d

经过测试,使用这个指令卸载盘符可以保证reagentc配置不变、消除盘符的同时,再重启后也没有自动挂载的现象复发了,可以认为达到了与Windows安装时自己配置相同的效果。

啊?这玩意还能有第三期?

起因是想玩玩ChatGLM3,然后发现这个库用的pytorch版本特别新(说是要求2.0以上,实测1.12也行),于是就打算自己编一个新版本玩玩。然后人就傻眼了:编了几天,编译过程是挺顺利的,结果编出来的whl用不了,一import就报OSError 1114,说是某个DLL的初始化函数出了问题,一开始以为是编译器坏了(因为就在当天一次重启把我CUDNN炸了,也不知道是不是SSD的问题 汰渍全责!),前后更换配置(Python版本/MSVC版本/是否启用CUDA,甚至换了电脑)编了几天,SSD都写了快1TB了,还是没有任何进展。

然后也是因为各种配置问题没文档,编不出Debug版的wheel来,就着RelWithDebInfo版用regsvr32看了半天,发现好像是因为某个全局静态变量没有初始化还是咋的,导致DLL加载过程中抛出了c10库的异常,没给任何有用信息就直接把主程序炸了;然而由于该方法只是简单地加载了一个DLL,并不一定遵循PyTorch应有的初始化逻辑,所以也不知道有没有参考价值。

最后实在想不通了,为什么官方编出来的装了可以直接用,于是决定去偷学官方ci的编译脚本,看看编译流程上和自己的到底有啥区别,结果最后发现,好像最大的差别是他用的是Ninja生成,而我用的是MSBuild。虽然认为本质上应该没啥区别,但是抱着死马当活马医的心态,装了VS自带的CMake工具(对,这玩意其实也自带Ninja的),然后有样学样,用Ninja来编译PyTorch。虽然不是很想用咕果家的东西,但是不得不承认的是,Ninja的TUI还是做得比MSBuild好的,至少可以大致了解到编译流程进行了多少= =

这里贴一下中间需要设置的变量:

  1. set CMAKE_GENERATOR=Visual Studio 16 2019(因为我没把Ninja加到PATH,以及VS2017编译会有报错);
  2. set DISTUTILS_USE_SDK=1,不然CUDA配置那里可能报一些奇奇怪怪的错误;
  3. python setup.py bdist_wheel,然后理论上他configure完会出现异常终止,因为MSBuild内部调的Ninja和我们设置的环境变量不符合;
  4. set CMAKE_GENERATOR=,取消generator的设置;
  5. 重新执行3,理论上应该不出现任何问题,直到编译完成。

然后就是经典环节了。pip安装编完的whl,import,没有报错,直接成功了,是的,成功了。

很喜欢计算机人的一句话:啊?

只能说是在摸清问题之前解决了问题了罢。咱又不开发AI框架,既然问题解决了,那探究先到此为止吧。

鉴于某些众所周知的原因,GitHub传输速度太拉,gitee又半死不活的,于是决定给自己搭个本地git仓库用,一方面解决了公私有代码的问题 反正都是私有了,另一方面上传下载快的1p,还可以选择自己喜欢的管理风格。然后简单整理了一下现有的开源git服务器。只能说这玩意一团散沙,不靠友商对比都不知道有哪些,在这里简单列一下。

  1. ruby系:GitLab。这个没什么好说的,说到私有git服务器第一反应必然是这个,似乎也是目前已知的开源git服务器中体量最大的,功能齐全,界面美观。问题在于自己编linux包麻烦,我omnibus-gitlab整了几天都没编出来= =

  2. golang系:Gogsgiteaforgejo。这仨一脉相传,我是先用的forgejo,然后又回头试用了下gogs,发现两者的html模板几乎都长得一模一样,后知后觉地搜了下才发现的。简单概括来说,就是先有的gogs,然后一群贡献者嫌gogs更新慢、不加新功能,然后fork出了gitea;然后gitea搞了几年之后开公司了,codeberg担心gitea搞收费,就又从gitea代码fork出了一个forgejo自己开发(贵圈真乱.jpg)。但是不得不提的是,gogs的确老了,甚至不支持统计展示当前仓库各个语言的代码量。。

  3. Java系:gitbucketGitBlitOneDev。这三家都是个搞个的,其中OneDev比较新,界面做的也比较现代化。