针对海量的新闻资讯数据,如何快速的根据用户的检索需要,完成符合用户阅读需求的新闻资讯推荐?本篇文章主要采用余弦相似度及基于用户协同过滤算法实现新闻推荐,通过余弦相似度算法完成针对不同新闻数据之间的相似性计算,实现分类标签。通过协同过滤算法发现具备相似阅读习惯的用户,展开个性化推荐。
本次新闻推荐系统:
主要包含技术:springboot,mybatis,mysql,javascript,vue.js,html,css
主要包含算法:余弦相似度,基于用户协同过滤推荐
系统采用前后端分离的开发模式完成,系统前端主要采用Vue.js,javascript,html,CSS等技术实现。系统后端框架采用springboot+mybatis+mysql数据库搭建,针对海量的新闻资讯数据采用分表操作,完成数据存储分析。系统前后端数据交互,采用Ajax异步调用传输JSON实现。系统架构主要分为基础数据存储,新闻资讯爬虫,新闻分析计算,新闻网站前端四个层面,其中爬虫主要定时采集互联网各大新闻网站的公开资讯数据,完成数据清洗,过滤等操作。系统主要架构设计如下图:
其他效果省略
余弦相似度,又称为余弦相似性,是通过计算两个向量的夹角余弦值来评估他们的相似度。余弦相似度将向量根据坐标值,绘制到向量空间中,如最常见的二维空间。
余弦相似度衡量的是2个向量间的夹角大小,通过夹角的余弦值表示结果,因此2个向量的余弦相似度为:
分子为向量A与向量B的点乘,分母为二者各自的L2相乘,即将所有维度值的平方相加后开方。
余弦相似度的取值为[-1,1],值越大表示越相似。
余弦相似度java代码实现
public static double getSimilarity(String doc1, String doc2) {
if (doc1 != null && doc1.trim().length() > 0 && doc2 != null && doc2.trim().length() > 0){
Map<Integer, int[]> AlgorithmMap = new HashMap<Integer, int[]>();
//将两个字符串中的中文字符以及出现的总数封装到,AlgorithmMap中
for (int i = 0; i < doc1.length(); i++) {
char d1 = doc1.charAt(i);
if (isHanZi(d1)) {//标点和数字不处理
int charIndex = getGB2312Id(d1);//保存字符对应的GB2312编码
if (charIndex != -1) {
int[] fq = AlgorithmMap.get(charIndex);
if (fq != null && fq.length == 2) {
fq[0]++;//已有该字符,加1
} else {
fq = new int[2];
fq[0] = 1;
fq[1] = 0;
AlgorithmMap.put(charIndex, fq);//新增字符入map
}
}
}
}
for (int i = 0; i < doc2.length(); i++) {
char d2 = doc2.charAt(i);
if (isHanZi(d2)) {
int charIndex = getGB2312Id(d2);
if (charIndex != -1) {
int[] fq = AlgorithmMap.get(charIndex);
if (fq != null && fq.length == 2) {
fq[1]++;
} else {
fq = new int[2];
fq[0] = 0;
fq[1] = 1;
AlgorithmMap.put(charIndex, fq);
}
}
}
}
Iterator<Integer> iterator = AlgorithmMap.keySet().iterator();
double sqdoc1 = 0;
double sqdoc2 = 0;
double denominator = 0;
while (iterator.hasNext()) {
int[] c = AlgorithmMap.get(iterator.next());
denominator += c[0] * c[1];
sqdoc1 += c[0] * c[0];
sqdoc2 += c[1] * c[1];
}
double v = denominator / Math.sqrt(sqdoc1 * sqdoc2);//余弦计算
v = Double.isNaN(v) ? 0d : v;
return v;
} else {
throw new NullPointerException(" the Document is null or have not cahrs!!");
}
}
协同过滤算法是一个大类,主要有基于用户、基于物品、两者结合等分支,这里我主要介绍的是基于用户的协同过滤算法。主要的思想也很简单,中国有一句俗语“物以类聚,人以群分”,我们可以有很大的把握认为一个和你很相似的用户喜欢的物品也大概率也是你喜欢的物品,这就是基于用户的协同过滤推荐算法的思想。实现基于用户协同过滤推荐,主要包含以下几个步骤:
1.计算用户相似度
2.获取需要推荐给用户的物品(本系统内主要是新闻数据)
基于用户协同推荐算法实现
/***
* 协同过滤算法
* 1. 找到与目标用户兴趣相似的用户集合
* 2. 找到这个集合中用户喜欢的、并且目标用户没有听说过的新闻推荐给目标用户
* @param userInfos
* @param recommendUser
* @return
*/
public static List<GPair<String, Double>> XtglNewsTj(List<GPair<String, List<String>>> userInfos, String recommendUser) {
int N = userInfos.size();
//建立用户稀疏矩阵,用于用户相似度计算【相似度矩阵】
int[][] sparseMatrix = new int[N][N];
//存储每个用户对应的不同总数eg: A 3
Map<String, Integer> userItemLength = new HashMap<>();
//建立新闻到用户的倒排表 eg: a A B
Map<String, Set<String>> itemUserCollection = new HashMap<>();
Set<String> items = new HashSet<>();//辅助存储新闻集合
Map<String, Integer> userID = new HashMap<>();//辅助存储每一个用户的用户ID映射
Map<Integer, String> idUser = new HashMap<>();//辅助存储每一个ID对应的用户映射
for(int i = 0; i < N ; i++){//依次处理N个用户 输入数据 以空格间隔
userItemLength.put(userInfos.get(i).getKey(), userInfos.get(i).getValue().size());//eg: A 3
userID.put(userInfos.get(i).getKey(), i);//用户ID与稀疏矩阵建立对应关系
idUser.put(i, userInfos.get(i).getKey());
//建立新闻--用户倒排表
for(int j = 0; j < userInfos.get(i).getValue().size(); j ++){
if(items.contains(userInfos.get(i).getValue().get(j))){//如果已经包含对应的新闻--用户映射,直接添加对应的用户
itemUserCollection.get(userInfos.get(i).getValue().get(j)).add(userInfos.get(i).getKey());
}else{//否则创建对应新闻--用户集合映射
items.add(userInfos.get(i).getValue().get(j));
itemUserCollection.put(userInfos.get(i).getValue().get(j), new HashSet<String>());//创建新闻--用户倒排关系
itemUserCollection.get(userInfos.get(i).getValue().get(j)).add(userInfos.get(i).getKey());
}
}
}
System.out.println(itemUserCollection.toString());
//计算相似度矩阵【稀疏】
Set<Map.Entry<String, Set<String>>> entrySet = itemUserCollection.entrySet();
Iterator<Map.Entry<String, Set<String>>> iterator = entrySet.iterator();
while(iterator.hasNext()){
Set<String> commonUsers = iterator.next().getValue();
for (String user_u : commonUsers) {
for (String user_v : commonUsers) {
if(user_u.equals(user_v)){
continue;
}
sparseMatrix[userID.get(user_u)][userID.get(user_v)] +=1;
}
}
}
/计算用户之间的相似度【余弦相似性】
int recommendUserId = userID.get(recommendUser);
List<GPair<String, Double>> res = new ArrayList<>();
for (int j = 0;j < sparseMatrix.length; j++) {
if(j != recommendUserId){
System.out.println(idUser.get(recommendUserId)+"--"+idUser.get(j)+"相似度:"+sparseMatrix[recommendUserId][j]/Math.sqrt(userItemLength.get(idUser.get(recommendUserId))*userItemLength.get(idUser.get(j))));
}
}
//计算指定用户recommendUser的新闻推荐度
List<GPair<String, Double>> recommondInfos = new ArrayList<>();
for(String item: items){//遍历每一件新闻
Set<String> users = itemUserCollection.get(item);//得到 当前新闻的所有用户集合
if(!users.contains(recommendUser)){//如果被推荐用户当前新闻,则进行推荐度计算
double itemRecommendDegree = 0.0;
for(String user: users){
itemRecommendDegree += sparseMatrix[userID.get(recommendUser)][userID.get(user)]/Math.sqrt(userItemLength.get(recommendUser)*userItemLength.get(user));//推荐度计算
}
recommondInfos.add(new GPair<>(item, itemRecommendDegree));
}
}
recommondInfos.sort(new Comparator<GPair<String, Double>>() {
@Override
public int compare(GPair<String, Double> o1, GPair<String, Double> o2) {
return o2.getValue().compareTo(o1.getValue());
}
});
return combine(recommendUser, userInfos,recommondInfos);
}