工作内容 | 博客链接 |
---|---|
项目分工讨论及环境搭建 | 初步讨论,进行小组分工 开发环境搭建和相关开发工具的介绍 |
负责模块的初步设计 | 数据库设计 推荐系统设计 数据清洗流程设计 |
博客文章的数据清洗 | TextRank抽取式摘要生成 尝试基于seq2seq的生产式摘要(效果很差) 提取文章的纯文本内容 文章标签与分类的提取 |
推荐系统实现之基于流行度和新鲜度的推荐(热门榜单) | 使用时间戳和阅读量的评分实现 (后期修改)使用Hacker News计算评分 |
推荐系统实现之相似文章推荐 | 使用词袋模型构建向量,计算余弦相似度(效果不好) 学习lucene工具包的使用 学习lucene工具包在java中的使用 使用Lucene建立倒排索引,使用TF-IDF构建向量吗,计算余弦相似度 使用多线程进行相似文章的预计算 |
学习SpringBoot定时任务并实现用户浏览记录的定时抽取 | 用户浏览记录的抽取(用户id,物品id,点击次数)为协同过滤作准备 使用springboot定时任务进行浏览记录定时抽取 |
推荐系统实现之基于Mohout推荐引擎的协同过滤推荐 | 学习Mohout推荐引擎 SpringBoot项目中,基于Mohout推荐引擎的推荐算法的实现(基于用户的协同过滤,基于物品的协同过滤) 协同过滤算法的评估 |
推荐系统实现之基于Spark的ALS协同过滤推荐 | 学习基于Spark MLlib包的ALS推荐算法 基于Spark MLlib包的ALS推荐算法的设计 在Springboot项目中使用Spark的ALS算法实现协同过滤推荐 |
ELK环境的搭建以及基本检索功能的实现 | 在MacOS下搭建ELK的环境 Elasticsearch—用logstash增量导入Mysql数据 Elasticsearch学习以及实现简单的检索功能(带高量)的后端接口 Elasticsearch在阿里云Centos上的搭建(服务器性能太低,运行不起来) |
ElasticSearch检索的进阶 | 在简单短语搜索的基础上实现标点符号分割的句子搜索 分词器的配置及使用(IK分词器和pinyin分词器) 使用ElasticSearch的Suggest实现搜索提示的学习 基于ElasticSearch的Completion Suggest实现搜索提示 在后端项目中使用java API实现搜索提示接口 |
推荐文章列表后端接口 | 有关文章推荐的后端接口实现 |
用户记录的后端接口 | 有关用户记录的后端接口实现 |
文章分类和标签的后端接口 | 有关文章标签和分类的接口实现 |
管理员端博客和文章数据统计的后端接口 | 有关管理员端博客和文章信息统计的后端接口实现 有关管理员端博客和文章信息统计的后端接口实现(续) |
用户订阅博客及标签的后端接口 | 有关用户订阅博客及标签的后端接口实现 |
用户自主提交自己博客信息的后端接口 | 有关用户自主提交自己博客信息的后端接口实现 |
用户画像统计信息的后端接口 | 有关用户画像部分统计信息的后端接口实现 |
后端测试以及性能优化 | 后端bug的修复 后端bug的修复(续) SpringBoot中使用 Spring Cache 集成 Redis进行缓存,提高响应速度 后端bug的修复(续2) 后端sql优化,提升sql运行速度 |
1. 博客文章的数据清洗的设计与实现
拿到通过rss订阅抽取到的文章信息发现以下问题
问题:
(1)摘要信息只是截取文章的前多少字,可能无法真正的表达清楚文章的实际含义。所以我实现了抽取式摘要生成算法——TextRank。它基于BM25算法计算两句话之间的相似程度,得到两个句子的相关性得分,给每个句子一个初始的权重,然后就使用类似PageRank算法的TextRank,结合句子之间的相关性迭代固定的轮次,输出权重最高的句子作为这篇文本的摘要。
(2)文章的内容与html标签混合在一起,对前端的展示有好处。但是在计算文章之间相似度时,而这些无用的html标签会大大的影响计算的准确性。所以我使用正则表达式以及Jsoup实现了html标签中纯文本内容的提取。
(3)每个博客主都会给自己发布的文章定义分类与标签,而有的博客主甚至没有给这篇文章定义分类和标签。
所以我从github找到了掘金社区的所以博客标签,共500多条,将其迁移到我们的项目中作为我们博客平台的系统默认标签,然后将这些标签进行手动分类,保证每一个标签都属于一个类别。
如果抽取到的文章,已经被定义好的标签,则系统默认的标签与文章定义的标签都转化成拼音,进行编辑距离的对比,如果相似则完成了对文章的标注。另外发现每一个标签都会有很多同义词,所以可以对系统定义的每个标签进行描述,如果与描述能相对应,则说明也可以完成文章的标签标注。(如果没有匹配,则统一采用下一种方法)
如果抽取到的文章,没有被定义标签,则将系统默认标签,与此文章的正文及标题进行字串的对比,如果文章中多次出现这个标签,说明很大概率这个文章应该被标注为这个标签,最终选出匹配度最高的三个标签作为此文章的标签(依据:如果一个文章有关某个标签内容,它的内容中会有很大概率携带这个标签)
如果实在没有标签,则标记为未知标签与未分类。
最后通过博客的标签对文章进行分类
2. 各种推荐算法的设计与实现
(1)热榜的设计
刚开始的设计是:
在抽取博客时,会抽取到文章发布的时间,我们可以将这个发布时间转化为时间戳,这个时间戳就可以作为一篇文章基础的评分,时间戳是随着时间不断增加的,新发布的博客会有比较大的时间戳,这样就保证了新来的博客的分值比较大。
在时间戳的基础上,我们可以引入点击量与点赞量,作为博客评分的另一个部分,我们给每一个点击量增加一个权重K,当博客被点击一次后,评分会+K,博客被点赞一次,评分会增加3K,所以总体的评分为时间戳+K点击量+3K点赞量。
为了提高系统的响应时间,我考虑使用了redis的zset来进行数据的缓存,zset是一个根据score进行排序的集合,我们正好可以根据文章的评分让zset对博客的顺序自动的进行排序(key为一个固定的统一的key,value为文章的主码id,socre为文章的评分),我们取数据时,只需要按顺序读出文章id即可。
后期算法改为Hacker News算法,
P为根据浏览数和点赞数算出的评分。T为发布时间与当前时间的距离,为了保证时间T的公平性,我们每天0点的定时任务会重新计算热榜,当前时间设置为第二天的0点,这样在一整天的时间内,用户点赞或者浏览会重新计算评分时,当前时间对于大家都是相同的,不会导致不公平现象的出现。
G为重力因子,决定排名下降的速度。
这样就完成了热榜的算法。
(2)相似文章的推荐
首先我使用了余弦相似度作为文章相似度的衡量标准,刚开始使用词袋法构建两篇文章的词向量,计算余弦相似度,发现计算结果并不理想。
之后使用TF-IDF构建文本的向量,来计算余弦相似度。在这里我试用了Lucene的java api,在每次文章抽取结束后,使用lucene在本地建立好倒排索引,基于已经构建好的倒排索引,获得所有文章的TF-IDF值,作为文本向量,然后使用springboot的线程池,多线程的对于每一篇文章,基于TF-IDF构成的文本向量,计算余弦相似度,从而找到最相似的十篇文章,将相似文章的id存入redis的set中。(key为articleSimilarity+文章id,value为set,存放相似文章的id)。在进行相似文章的推荐时,我们只需要读取redis获得相似文章的id即可。
(3)基于Apache Mahout实现的协同过滤推荐
在实现协同过滤推荐之前,由于需要用户的浏览记录作为依据,所以我们用脚本随机模拟了2000多个用户的十万条浏览记录,作为推荐的依据。
然后使用Apache Mahout中提供的协同过滤算法进行了推荐,并且通过使用 Apache Hadoop 库,Mahout可以实现分布式的计算。
我们使用定时任务将用户的浏览记录定时抽取到本地,进行离线推荐。
基于协同过滤本应该是针对文章进行推荐,但是考虑到两个用户共同读到的文章很少,无法有效的求用户的相似度,所以使用两个用户读过的标签进行推荐,这样可以解决相似度的计算问题。(其实仔细想想还是很有道理的:只有看的文章一样的两个用户才比较相似吗?其实并不是。直观上,对于博客文章来说,如果两个用户喜欢的大体方向一致就行)
最终实现的效果是,将基于用户协同过滤推荐的(标签,评分)列表和基于物品协同过滤推荐的(标签,评分)进行综合评估,选择综合评分最高的top10个标签,然后通过这些标签筛选用户没有阅读过的文章进行推荐。
问题是:在用户每次刷新推荐列表时,都会去完成上述的计算流程,导致速度较慢用户体验不要,之后进行了进一步的优化。
(4)基于Spark MLlib包的ALS(交替最小二乘)协同过滤推荐算法
考虑到Apache Mahout的协同过滤推荐是将基于用户的协同过滤与基于物品的协同过滤分开推荐的,并且还不能进行参数的调优,效果不是很好,所以在项目后期使用ALS协同过滤推荐算法进行了替代。
它解决了评分矩阵稀疏的问题,通过预测对稀疏矩阵进行了填充,并且还能对正则化参数,分解矩阵的排名以及迭代次数这三个参数进行调整,让模型达到最好的效果。
这个推荐标签的计算采用定时的方式,定时的将用户浏览记录进行抽取,并且基于hadoop实现推荐的计算,将定时将推荐标签的结果存入redis之中,给用户推荐时只需要读取redis的推荐标签结果,然后实现文章的推荐。
3. 基于ElasticSearch的检索模块的设计与实现
(1)在本地搭建了ElasticSearch+logstash+Kibana环境
(2)对logstash进行插件的安装以及环境的配置,实现了将数据库中的文章数据定时增量的倒入ElasticSearch中进行索引的建立。
(3)在使用ElasticSearch的javaAPI实现了简单的检索,通过将句子通过标点符号分割,分词并去除停用词之后,实现了长句的查询功能,并且实现了查询结果的高量显示。
(4)给ElasticSearch配置IK分词器和pinyin分词器,分词效果比自带的分词器要好,最终实现的查询效果也更加的准确。
(5)使用ElasticSearch的Suggest实现搜索的建议
ElasticSearch提供了四种Suggest的类型,我重点是用了Completion Suggester,它是针对自动补全场景而设计的建议器。此场景下用户每输入一个字符的时候,就需要即时发送一次查询请求到后端查找匹配项,获得搜索建议的结果。
实现搜索建议时,使用IK分词器和pinyin分词器进行综合使用,通过对分词器的不同配置,实现了五种不同的分词效果,这五种分词效果可以在不同情况下有序进行使用(对于全中文文本,中英混合文本以及纯英文文本采用不同的配置),通过使用这些不同的配置,来保证搜索提示覆盖的全面性和准确性。
4. 后端接口的设计与实现
(1)将之前实现了所有推荐算法进行整合,实现了文章推荐的后端接口,为前端推荐文章列表。
并且还可以通过文章的点赞数量,浏览数据进行降序排序,返回文章列表
(2)有关用户记录(用户在博客系统中的行为)的后端接口实现
在用户使用系统时,会在系统中进行浏览以及点赞,这些行为需要记录。并且用户可以查看自己的浏览历史以及点赞历史,这些接口都需要进行实现。
(3)有关博客系统的分类和标签的后端接口的实现
我们需要根据标签id返回文章列表,根据每个标签下文章数量排序返回标签列表
(4)有关用户订阅博客及标签的后端接口实现
用户在博客系统中,可以订阅自己喜欢的博客主或者标签,需要针对这个需求实现后端接口。
将基于标签的订阅与博客的订阅信息放在一个表中,通过type区分,实现了添加订阅,删除订阅,获取订阅状态,获取订阅列表,等功能。
(5)有关用户画像部分信息统计的后端接口
我们在描述用户画像时,需要获取某个用户在博客系统中的一些信息,如:
获取用户最近一段时间的点赞量 浏览量 以及阅读时间偏好
获取用户喜爱的标签列表(给用户推荐的标签)
基于用户对不同博客中文章的浏览次数或点赞次数,给出用户最喜欢的博客主
获取用户浏览次数最多的文章
(7)有关系统博客主的相关接口实现
我们博客系统有从各处搜集来的博客主的信息(rss订阅),我们也支持注册用户自主提交自己的自建博客的信息,所以需要实现相关接口,如:
用户提交自己的自建博客信息
用户更新自己的博客信息
查询用户是否提交过自己的自建博客信息(rss订阅)
如果用户提交过自己的自建博客,则可以获取博客中,文章的数量
如果用户提交过自己的自建博客,则可以获取用户提交博客的一些统计信息(最近几天发布的文章的数量 发布文章的趋势,自己博客中的文章的浏览量的变化以及点赞量的变化)
(8)有关管理员端信息统计的后端接口实现
管理员端需要对博客系统的全局进行把握跑,所以需要对于全局的数据进行一些统计操作,如: 被浏览文章最多的博主,以及文章浏览的数量,降序排序 获取最近几天给文章点赞最多的用户,及点赞数量,降序排列 获取最近几天浏览文章最多的用户,及浏览数量,降序排列 获取最近几天某个标签被浏览的次数,倒序返回 获取文章最近几天的浏览数量,降序排序 获取文章最近几天的点赞数量,降序排序 获取最近几天每个博客发布的文章数量,降序排序 获取所有博客,最近几天发布文章的数量
5. 后端测试以及性能优化
(1)优化sql语句的设计
(2)SpringBoot中使用 Spring Cache 集成 Redis进行缓存,提高响应速度
在更新不太频繁的接口上,设置60s的缓存,当用户对同一个接口在短时间内获取数据时,直接返回redis中的缓存信息,而不需要从数据库中读取信息,提高