两个项目都是本学期开始做的,vNote先,AIQ后。这篇总结在vNote开发结束后就想去写,无奈又进行项目完善,然后又接上了AIQ的开发,拖到vNote省赛失利的结果出来后才来写。
我们似乎做了当时应该做的所有事情,可是还是被刷了。在开发中技术、能力上面都成长较多,尤其是与给力的队友们交流时发现了很多需要学习的地方。除去技术,在项目整体规划方面也学到了很多,这得益于亲自编写了某些文档。
vNote和AIQ可以称为一个项目,只是前端页面有所不同,其后端与数据库都是同一套,所以我们在下面就称为AIQ(因为我们确实不只是做了一个笔记软件)了。
本项目是 集搜题、归纳、学情分析为一体的线上教育平台,支持 教师、学生绑定发布测试和通知。
前台主要功能有:拍照搜题、整页试卷切题搜题、题目解析(优质题友解答、视频解析、官方解析)、笔记整理(根据笔记内容进行题目推荐、智能笔记分类、多种方式录入)、错题整理(上传讲解视频、快速录入)、智能生成测试薄弱点试卷、智能题目推荐、题友、商城(静态)。
本项目利用 前后端分离的开发模式,前端使用Vue框架,后端为Phalapi框架。其中拍照切题功能使用python作脚本语言完成。数据库使用mysql,加入redis做缓存层(这个没有太多用)。前端响应式开发,使用Cordova转成App端(目前只有安卓,IOS还没去弄听说挺麻烦...)。
在讨论中前端也大致了解了一下...实际开发中只大概看了下前端源码,并没有深入了解。还是老老实实写后端。
这个是写到一半加上去的,还是重新写一个单独的博客来记录下phalapi的使用吧。
链接:http://www.goodtimp.com/article/39
本来打算使用 Tensorflow 的深度学习框架去做的,初步看完教程后觉得门槛太高了...没有太多时间去弄,而且数据集采集也不是很好去弄。更别说模型训练了,所以就搁置了。只写了Tensorflow的整理的四篇笔记(一、二、三、四)。
ps: 这里引用了之前写的技术文档里的内容,偷个懒...
首先我们看一下效果:
AIQ的拍照切题功能后来利用 Python语言丰富的图像处理库与深度学习开源算法框架 ,首先对图像进行 灰度化、二值化、除噪、高斯滤波、膨胀、canny边缘提取 等预处理,然后通过两个线程对两种不同算法同时计算,其中包括 采用CTPN+CRNN的文字定位与识别 (效果不好后来调用了百度API...),针对用户上传图像的文字内容分析后进行图像切割;另一个则是对用户上传试卷图像进行列切分与行切分和0像素膨胀段落化等操作后,针对于图像像素内容本身进行图像切割;两种算法再通过智能算法合并结果推送出更加合理的切题方案。
我们通过Python的图像处理库(Opencv、PIL等)首先检测图像的二值化阈值,进行简单处理后得到图像的二值化图。 针对于某些拍出的图像可能并不是垂直的,类似于:
对于这样的图片在进行后续切题操作时会出现很多不必要的麻烦,所以我们通过高斯滤波和canny边缘提取后便可得到试卷轮廓,但是由于图像某些轮廓区域无法闭合导致出现信息区域轮廓没法提取,所以需要加入适当的膨胀。在经过轮廓查找与筛选掉无关内容后,通过四点法透射变换便可得到我们想要的结果。
测试了CTPN和CRNN源码算法源码, 但是效果并不好,所以后面采用了百度的文字识别定位的API,针对百度api返回的结果进行相关算法合并行信息和处理后返回切题结果与文字识别结果。 我们看一下效果:
我们针对于用户上传的图片进行预处理后,针对于其进行列切分和行切分。列切分目的对于某些A3的试卷左右分开便于后续处理,而行切分目的是利用行距信息为切题提供参数。
我们通过观察行距可以发现,对于行距较大的往往是分割不同题型、不同题目的关键参数,而对于大量出现的行距值应为普通行距。我们可以使用递归的思想,从行距最大的开始分割,将较大的几个行距分割成不同的区域便于后续分割。
我们得到行距信息之后,再有列切割后的二值化图像分别进行 0 像素膨胀,得到如下图的膨胀图像。根据图中信息,我们可以对原图像文字内容进行分。对于选择题目往往会出现一行中出现多个分块,而两个选择题的题干位置便可以通过选项位置来确定。
流程:用户上传图片 -> 前台图像文字识别 -> 将文字信息post给后台 -> 后端通过算法匹配数据库内容 -> 得到与之最为相似的题目信息返回前台 -> 前台呈现给用户
其中最大的问题就是:数据库内题目数量过多,匹配速度太慢。而且对于某些题目可能识别并不是很准确,所以匹配的时候不能使用mysql一般的 = 或者 like 匹配,需要寻找其他算法可以计算文本相似度。
OK!问题知道了,如何解决再下:
刚才我们提到了,不能直接利用 = 、like 这样的匹配方式进行匹配,我们需要计算 用户发送来的题目文本 与 题库中原题问文本 进行对比得到两个文本的相似程度。
对于本项目中我们用到了DP算法内的一个 叫 编辑距离 的算法,时间复杂度为O( n * m )。
编辑距离: 一个字符串使用 某些操作 变成另外一个字符串的最少的次数。(某些操作包括:增加、删除、修改 一个字符)
其中动态转移方程为:
注:个人感觉编辑距离更适合文本长度相差不是很大的时候使用。 针对类似的算法,可以查看之前我写的另外一篇博客:利用Python实现 相似数据查重 。
本项目的题库题目现在是4k道题目,每个题目文字数量 从两位数到四位数 都有,如果对每道题目都进行编辑距离的匹配 那那就可能需要进行几十秒甚至更多的时间,所以我们需要在进行编辑距离匹配的 前期先进行筛选。
我们在搜索题目时加入了利用 多热编码+TF-IDF算法+余弦相似度算法 进行初步的检索,这类似于字典里面的目录,我们可以通过 拼音(或者偏旁)快速定位到某个字在哪几页里面,大大提高我们查字典的效率。
多热编码:
多热编码思路类似于 独热编码,区别就是编码时会有多位是1, 这比较适合于参数较多的数据。在本项目中的参数便是我们内置的语料库(主要包含科目知识点等信息),编码方式我们使用的是稀疏表示法。之前在看深度学习时有记录多热编码,这里放个连接可以自行查看:http://www.goodtimp.com/article/36。
我们现在通过多热编码,可以对一道题目根据内置语料库提取 编码(这里可以认为是关键字)。后来我们的题目越来越多,又因为我们的语料库建立更侧重于是 知识点(例如八年级物理的语料库有: 密度、质量...)的建立(当然对于像英语、语文则是另外一种策略),所以出现了越来越多的相同 编码 的题目。如果都对其进行编辑距离计算也会消耗较多的时间,于是我们需要更精准的判断哪些题目与查找的更相似。
TF-IDF算法
对题目的 多热编码 进行加权,然后通过 余弦相似度 计算 两个题目的 多热编码相似度 ,从而筛选更加相似的题目。而TF-IDF(term frequency–inverse document frequency)就是一种用于资讯检索与资讯探勘的加权技术。
TF-IDF(term frequency–inverse document frequency)是一种用于资讯检索与资讯探勘的加权技术。TF-IDF是一种统计方法,用以评估一字词对于一个文件集或一个语料库中的其中一份文件的重要程度。字词的重要性随着它在文件中出现的次数成正比增加,但同时会随着它在语料库中出现的频率成反比下降。
余弦相似度
余弦相似度将所有的事物的相似度范围都应该是0~1。余弦相似度的特点是余弦值接近1,夹角趋于0,表明两个向量越相似。三角形越扁平,证明两个个体间的距离越小,相似度越大;反之,相似度越小。
我们首先获取需要匹配的两道题目的 关键字与对应权重信息 组合为相应数组,并有两个数组中关键字组成“词集”,根据“词集”与对应权重得到两道题目的“词重向量”,利用公式得到两个向量的 相似度。
ps : 不是我 markdown 不会写 math,只是 vue 这个解析器有问题....hahhahah
后来题库越来越多,经过测试发现从 数据库拿取数据到后端 还没有开始进行计算的时间占了总共请求返回时间的 80 %以上,所以优化数据库成了优化算法效率的最关键部分。(当然,时间最主要浪费在了数据传输上面,但是数据库的优化也是非常重要的)
通过查阅资料,我们大致从建库某些字段的数据类型到初始值,建立某些字段索引等等去优化了一边数据库。 这里放几个知乎的链接,可以去看一下:
后面我们还想着加入了redis,但是代码写出来了没怎么去应用他。
ps : 数据库相关知识对于后端来说是非常非常重要的东西,需要恶补一下...
我们这个项目有很多地方都用的了 推荐算法,包括:主页题目推荐、智能生成个人薄弱试卷、根据题目推荐相似题目、根据用户上传笔记推荐相似题目等。后两者用的是差不多同一个推荐算法(类似于搜题算法):基于内容和TF-IDF的类似题目混合推荐算法,第一个则是利用:基于用户协同推荐算法,第二个则是进行 埋点统计后运用后两者的推荐算法。当然我们还需要进行 弱题目剔除、优质题目优先推荐 等算法进行处理。
皮尔逊相关系数的用户相似度计算:
皮尔逊相关系数是比欧几里德距离更加复杂的可以判断人们兴趣(知识点薄弱)的相似度的一种方法。 - r[a,p]表实用户 a 对题目 i 的评分 - a∈1, ...,n; i∈1, ...,m; I 为题目集。我们根据相关学术论文论文,经过总结比较得到用户a对题目p的预测评分通过下图式子计算。
图片文字摘自vNote技术文档
我们对用户进行点赞、收藏、错题整理的行为进行埋点统计,统计用户在一段时间内(一天、一周等可自定义时长)的行为数据汇总 。
本来想存储一下用户针对与题目的某些操作时长等信息的,可是前端数据获取不太好获取所以就没加上去,后来也没有用到。
这个算法怎么说呢...跟搜题的思路基本是一致的,我们通过 多热编码 进行 余弦相似度 计算,得到与某题最为相似的几道题目。这里就不展开说了。
弱题目:用于用户练习用处并不大的题目。例如:已经做过的题目,报错较多的题目等/手动微笑
利用node.js的Crawler框架,使用还算是比较简单的。
var e = new Crawler({
// `maxConnections` 将被强制修改为 1
// 两次请求之间将闲置1000ms
rateLimit: 3000,
// 在每个请求处理完毕后将调用此回调函数
callback: function (error, res, done) {
if (error) {
console.log(error);
} else {
data = {};
// var $ = res.$;
var $ = Cheerio.load(res.body, {
decodeEntities: false
}); // html转换实体编码,有些会直接显示文字编码不是真正的文字
}
done();
}});
uri="http://www.goodtimp.com"
e.queue(uri);//爬取一个
e.queue(uri,Canshu=123);//传递参数
//e.queue([url1,url2...]);//一次添加多个
除去上面提到的技术上面的提升,对于项目开发有了更多的认识。
由于时间仓促,为了电商的初赛赶项目原型,使得在项目开发过程中遇到的了很多问题,不得不对数据库进行增加、删除、修改,对具体算法停下来再去查阅资料,由于讨论不充分某些问题的解决也是一次一次的迭代才达到理想的效果。
后来在撰写服务外包文档期间,查阅了相关资料,对 开发模式 进行了系统的学习,也希望以后能尽量避免相关问题。
可以先开一下相关术语的介绍:http://www.goodtimp.com/article/41
再来看一下我们的架构图:
这个项目花费了大量的时间,期间还去看了Tensorflow的入门教程,还有其他的很多内容,例如上传视频时服务器允许接受数据大小配置、sql利用了较多的原生sql语句等等。意识到需要学习的内容还有许多许多...
这里非常感谢 电商、服务外包比赛中各位的付出, 希望以后还有机会可以有共同赶项目的机会。
项目中开发人员的个人博客分别为:后端ipso、前端Mccyu、前端LYU
再次感谢每位的付出!祝好。