抱歉,您的浏览器无法访问本站
本页面需要浏览器支持(启用)JavaScript
了解详情 >

我的好友曹智铭写了一篇关于用神经网络给画图题打分的文章,简要幽默地介绍了神经网络的原理,也是一个很好的将所学知识融入生活实际的例子,大家快去看看吧!

在北京,一名普通高中生取得毕业证的必要不充分条件是,通过信息技术学业水平考试(简称信息技术学考)。为了帮助我们学校的同学进行学习和有针对性地练习,我所在的俱乐部搭建了北大附中信息技术学考练习系统。

文章链接:https://czhiming.cn/2024/01/wow-ai-resnet/

零. 缘起

在北京,一名普通高中生取得毕业证的必要不充分条件是,通过信息技术学业水平考试(简称信息技术学考)。为了帮助我们学校的同学进行学习和有针对性地练习,我所在的俱乐部搭建了北大附中信息技术学考练习系统。

它是一个基于Python的Django框架的网站,允许学生通过北大附中分配的邮箱注册登录,在线自主练习并获取分数,教师和学生都可以查询到历次练习的详细数据。

不过,这个网站曾经最大的问题就在于,信息技术学考有一类题目,要求学生使用Python的turtle库,按照题目给的样例图案进行编程,实现画出视觉上看起来相似的图案。由于它不给详细指令(比如,这个三角形边多长,颜色具体是哪个哪个颜色),学生画出的总会与样例图案有些许差别,因此不可能通过将学生程序画出的图片与样例图案逐像素比对来检查是否正确,只能老师判卷(真正学考的时候也是人工判卷)。但这样一来,学生得不到及时的反馈,而且老师也增大了负担。turtle画图每一次都会给你展示它的画图过程,而不是直接画出结果,所以信息技术老师总是要等很久,以看到学生程序最终给出的图案,而且学生人数也很多。

直到那一天,我们想,这一切,何尝不是计算机所擅长的呢?
壹. 模型引入

经过一番学习,我们发现只要对一些业界成熟的用于图像识别的卷积神经网络模型进行“魔改”就能契合我们的需求了。最终我们选择的是微软亚洲研究院研发的ResNet50模型,主要是因为这个模型准确度还是相当高的,而且应用相当广泛。

不过呢,我们并没有去找一个“开箱即用”的那种ResNet的库或者插件或者API之类的,因为那就没啥意思了,而且靠别人就是不如靠自己,万一人家那边服务器挂了咋办?😂所以我们就找到了ResNet50的开源代码,把它扔在了信息技术学考练习系统的服务器上。

好啦,现在问题是,不会用emmm……于是我们被迫(bushi)学习了一下卷积神经网络模型的工作原理,简记如下。

让我们来设想一下,人类是怎么认出他所见过的物体或者人的。比如说,当我2023年5月第二次见到李天星的时候,很显然如果把看到她的样子用相机拍下来,和第一次见到她的样子的照片,像素差别是很大的。我的大脑却绝不会由于什么像素差别大而拒绝认定这两个人像是同一个人。那么,让我们来深入地思考一下,我究竟是怎么认出来李天星的呢?

我们考虑当我第一次见到李天星的时候,由于我非常兴奋,我大脑中大量的神经元会被立即激活,并“存储”下她的许多特征。比如说,很可能我的神经元a会记下她眼睛的特征是α,神经元b会记下她头发的特征是β,c会记下她戴不戴眼镜……那么当我接下来遇到人的时候,神经元会依照我视网膜传来的信号决定要不要兴奋(即,是否被激活)。比如当这个人具有眼睛特征α,那么神经元a就会被激活,这个人就有概率是李天星。反之,如果我看到这个人戴着眼镜,那么c就不被激活,这个人是李天星的概率就小一些。当存储李天星的特征的大多数甚至全部神经元被激活时,这个人就很可能是李天星了。

卷积神经网络模型的基本原理正是如此,当然,它相对生物的神经还做了一些优化。首先,图像会被逐像素地以数字化形式传到模型的第一层。接着,通过卷积核遍历整张图,并获取到这张图的一系列特征(可能是色彩、纹理等各种特征),并传到下一层,继续分析。通过逐层深入,最终会传出一个向量,每个分量表示其某种特征具有的可能性。

还是举我认李天星的例子。比如第一层,就是我看到的李天星的整张图片,全部逐像素数字化。考虑第二层的许许多多结点,其中有一个t,就是看她眼镜下面一点那里的颜色和纹理是不是与皮肤相同的。那么模型看到这张照片后(其实是一大串数字),就必定能够得出这个满足t的满足程度,这个值赋给t,继续传给下一层。第三层还有一个结点k,是表示没有佩戴眼镜的。我们知道,假如人的眼睛下方和皮肤类似,那么很可能这个人没有佩戴眼镜。因此,从结点t指向k的权值m就很大,这意味着,如果满足t,那么很可能满足k。那么将t乘以t到k的权值m,再加上一个用于修正的常数n,赋给k。就这么依次下去,经过很多很多层,最终获取到很多特征的满足的可能性大小。

而这些很多很多个m,n的值又是怎么得到的呢?其实是通过输入大量的训练数据,让模型自行找寻出来的。训练过程中,会对模型输入一个图像,并对它输入这个图像对应的输出的期望。当模型算出来的值不等于期望,那么要求模型自己调节这许许多多的m和n,直到输出符合期望。这何尝不就是人类学习的过程!

不过这一切,大佬已经帮我们训练好了。所以我们只需要把样例图案和学生程序画出的图案输入给它就好啦。

因此,在学生上传上来程序后,我们会在其Python画图题的代码最后加上一个turtle自带的getcanvas()函数,并将最终画出的图案输出成图片,保存在服务器上。

接着,我们将样例图案输入给模型,获取到一个特征向量p;把学生输入的图案输入给模型,获取到特征向量q。

而这时又有了新的问题。这两个是向量,怎么获取到我们希望得到的、容易操作的,两个图片的相似度呢?

好家伙,解决方法居然在我们数学课上刚刚学过。用余弦相似度。😂(参见人教B版数学必修三有关向量数量积的内容)

对于两个向量a和b,其余弦相似度即为它们的点乘除以它们的模的积所得的商,即 a • b / |a| |b|。

(教材里面只针对于平面内的向量,不过很显然【不会证的推托用语】这个向量的定理当然可以合理外推到任意维度。可以想象,在一个平面内,两个向量a与b,cos <a, b>越大,它们的方向越接近;在一个三维的空间内,同样,按照上述公式计算出其余弦值,也反映了这两个向量在空间内的方向的接近程度)

ResNet模型输出的向量有2048个分量,所以,从几何上理解,就是一个2048维的空间里,通过两个向量的“夹角”的余弦值大小,以判定其方向的差异大小。而这每一个分量又都代表着这个图像的特征的情况,自然,方向的接近程度就反映了图片相似度。

余弦函数的值域是[-1, 1],为了获取到一个更符合我们人类直觉的相似度,也就是说让它在[0, 1]上,只需要再把这个余弦相似度扔进一个能把[-1, 1]满射到[0, 1]的增函数即可,比如f(x)=(x+1)/2就很好。

这样,我们就获取到了二者的相似度。我们大为欣喜,以为大功告成,将这个画图题判题代码传到服务器上,谁知……
贰. 事故

好家伙。第二天下午就收到了消息,他们当天下午上信息技术课的同学反映说网站出问题了,具体来说就是画图题判题结果输出的相似度奇奇怪怪的,交啥都给对,相似度还特高,他们没看懂是啥意思。

ehhh…不会吧,ResNet怎么这样…?于是,我们好奇地登上服务器看了看,集体晕倒。

我们获取余弦相似度相似度那,公式只把分子写了进去,分母根本没写。也就是说我们的变量cos_value的值现在是两个向量的数量积。这东西再进行个加一除以二,鬼知道算出来的是个啥,当然会在前端显示出来的相似度的值很奇怪。啊这,显然是昨天忘写了。

诶。不对。分母好像确实有点儿问题。还记得我上面说的什么吗……ResNet给出的特征向量,有2048个分量……还没懂我啥意思??这向量的模长,咋写呀?就算是你写出来了,你叫我们那性能连Visual Studio Code跑起来都很勉强的服务器咋算啊?(它还可能要同时处理多个同学的结果)每个分量,先2048次方一下,相加,这个和再开2048次方……这就是没法算啊。

好吧,于是这个问题我们最终找了个PCA[1]算法来解决,把多维向量简化为低维向量的,并且保留原始向量的主要方向。这样变成了个三维的,计算机就好操作了,我们的代码也好写了(指分子可以不用for循环写了(bushi)。这个算法要涉及一些线性代数的知识,我没有能力在此处解释,有兴趣可以自行百度。

于是,现在画图题判题系统已经基本完善。目前,经过大量数据分析,我们定为必须相似度大于0.9才视作这道画图题作答正确。
叁. 后记

此系统已经服务了北大附中高中部超500名学生(2024.1后台数据库user_meta数据表的数据)。如此多的学生,我校信息技术老师数量却是屈指可数的。而通过机器判卷,包括选择题、画图题等各类题目的全部机判,大大减轻了老师们的负担,这一切无不让我们体会到技术的日新月异,与支撑这些技术底层的数学的奥妙。

后来,薛梵正学长给提交试卷做了ajax异步,并优化了后端的多线程逻辑,这样,尽管判卷时长还是有点长(一套完整试卷平均约10s判完),但是也不至于让学生干等着了。

目前,我对PCA^1还是持崇敬而又怀疑态度的,因为如果它从2048维缩到3维,一定会损失掉一部分信息。那么有没有可能,两个截然不同的2048维向量,在损失掉不同的信息后,殊途同归地得到相同的三维向量?

我想,在我像神经网络模型一般的学习的漫漫旅程中,我终会找到这个问题的答案的。还有更多问题的答案,也将在这旅途中被我逐一揭晓。

评论

留下神评妙论