使用浏览量的协调过滤推荐算法

无敌的宇宙
无敌的宇宙
擅长邻域:Java,HTML,JavaScript,MySQL,支付,退款,图片上传

分类: Java 专栏: java 标签: 使用浏览量的协调过滤推荐算法

2026-03-22 23:13:08 97浏览

使用浏览量的协调过滤推荐算法

mysql

DROP TABLE IF EXISTS `sx_productview`;
CREATE TABLE `sx_productview` (
  `id` int NOT NULL AUTO_INCREMENT COMMENT '主键自增长',
  `user_id` int NOT NULL COMMENT '用户唯一标识符,用于区分不同用户',
  `item_id` int NOT NULL COMMENT '被浏览/交互的内容项ID(如商品ID、文章ID等)',
  `timestamp` bigint NOT NULL COMMENT '记录事件发生的时间戳(通常为Unix时间戳,毫秒级精度)',
  `is_repeated` tinyint(1) NOT NULL DEFAULT '0' COMMENT '标记该次浏览是否为重复访问(true表示用户之前已看过该内容)',
  `duration` bigint NOT NULL COMMENT '用户停留/交互持续时间(单位通常是毫秒)',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='物品浏览权重表';

package com.ruoyi.system.utils;




import com.ruoyi.system.domain.SxProductview;

import java.util.*;
import java.util.stream.Collectors;
/*
* 协同过过滤,根据用户浏览商品的数据推荐*/
public class ItemCF {
// 用户-物品浏览矩阵:<用户ID, <物品ID, 加权浏览得分>>
private Map<Integer, Map<Integer, Double>> userItemMatrix;
// 物品相似度矩阵:<物品ID, <相似物品ID, 相似度>>
private Map<Integer, Map<Integer, Double>> itemSimilarityMatrix;

public ItemCF(List<SxProductview> viewLogs) {
this.userItemMatrix = buildUserItemMatrix(viewLogs);
this.itemSimilarityMatrix = calculateSimilarity();
}

// 1. 数据预处理:构建用户-物品评分矩阵
private Map<Integer, Map<Integer, Double>> buildUserItemMatrix(List<SxProductview> logs) {
Map<Integer, Map<Integer, Double>> matrix = new HashMap<>();
long currentTime = System.currentTimeMillis();

for (SxProductview log : logs) {
double score = 1.0; // 基础分
if (log.getIsRepeated()) score += 0.5;
if (log.getDuration() > 30000) score += 2.0;

// 时间衰减:3天前的行为权重降为30%
double decay = Math.exp(-0.3 * (currentTime - log.getTimestamp()) / (86400000));
double finalScore = score * decay;

matrix.computeIfAbsent(log.getUserId(), k -> new HashMap<>())
.merge(log.getItemId(), finalScore, Double::sum);
}
return matrix;
}

// 2. 核心算法:计算物品相似度矩阵
private Map<Integer, Map<Integer, Double>> calculateSimilarity() {
Map<Integer, Map<Integer, Double>> simMatrix = new HashMap<>();
Map<Integer, Set<Integer>> itemUsersMap = new HashMap<>();

// 构建物品-用户倒排表
for (Map.Entry<Integer, Map<Integer, Double>> entry : userItemMatrix.entrySet()) {
int userId = entry.getKey();
for (int itemId : entry.getValue().keySet()) {
itemUsersMap.computeIfAbsent(itemId, k -> new HashSet<>()).add(userId);
}
}

// 计算物品两两相似度
List<Integer> itemIds = new ArrayList<>(itemUsersMap.keySet());
for (int i = 0; i < itemIds.size(); i++) {
int itemA = itemIds.get(i);
Set<Integer> usersA = itemUsersMap.get(itemA);

for (int j = i + 1; j < itemIds.size(); j++) {
int itemB = itemIds.get(j);
Set<Integer> usersB = itemUsersMap.get(itemB);

// 计算共同用户
Set<Integer> commonUsers = new HashSet<>(usersA);
commonUsers.retainAll(usersB);
if (commonUsers.isEmpty()) continue;

// 计算加权余弦相似度
double dotProduct = 0, normA = 0, normB = 0;
for (int user : commonUsers) {
double scoreA = userItemMatrix.get(user).get(itemA);
double scoreB = userItemMatrix.get(user).get(itemB);
dotProduct += scoreA * scoreB;
normA += Math.pow(scoreA, 2);
normB += Math.pow(scoreB, 2);
}

double similarity = dotProduct / (Math.sqrt(normA) * Math.sqrt(normB));
similarity *= Math.log(1 + commonUsers.size()); // 热门物品惩罚

// 双向存储相似度
simMatrix.computeIfAbsent(itemA, k -> new HashMap<>()).put(itemB, similarity);
simMatrix.computeIfAbsent(itemB, k -> new HashMap<>()).put(itemA, similarity);
}
}
return simMatrix;
}

// 3. 推荐生成:基于用户最近浏览
//TopN强调按特定规则(如评分、热度等)排序后取前N条数据,结果通常保证有序性
public List<Integer> recommend(int userId, int topN) {
if (!userItemMatrix.containsKey(userId)) {
return Collections.emptyList(); // 冷启动处理
}

// 获取用户最近浏览的5个商品
List<Integer> recentItems = userItemMatrix.get(userId).entrySet().stream()
.sorted(Map.Entry.<Integer, Double>comparingByValue().reversed())
.limit(5)
.map(Map.Entry::getKey)
.collect(Collectors.toList());

// 合并相似商品并排序
Map<Integer, Double> candidateItems = new HashMap<>();
for (int itemId : recentItems) {
itemSimilarityMatrix.getOrDefault(itemId, Collections.emptyMap())
.forEach((similarItem, simScore) -> {
if (!userItemMatrix.get(userId).containsKey(similarItem)) {
double weightedScore = simScore * userItemMatrix.get(userId).get(itemId);
candidateItems.merge(similarItem, weightedScore, Double::sum);
}
});
}

return candidateItems.entrySet().stream()
.sorted(Map.Entry.<Integer, Double>comparingByValue().reversed())
.limit(topN)
.map(Map.Entry::getKey)
.collect(Collectors.toList());
}



// 示例用法
public static void main(String[] args) {
// 模拟用户浏览数据
List<SxProductview> logs = Arrays.asList(
// new SxProductview(1,101, 2001, System.currentTimeMillis() - 100000, false, 25000l),
// new SxProductview(2,101, 2003, System.currentTimeMillis() - 50000, true, 40000l),
// new SxProductview(3,102, 2001, System.currentTimeMillis() - 200000, false, 10000l),
// new SxProductview(4,102, 2005, System.currentTimeMillis() - 220000, false, 70000l),
// new SxProductview(5,102, 2006, System.currentTimeMillis() - 300000, false, 90000l),
// new SxProductview(6,102, 2001, System.currentTimeMillis() - 400000, false, 40000l),
// new SxProductview(7,102, 2002, System.currentTimeMillis() - 500000, false, 15000l),
// new SxProductview(8,102, 2003, System.currentTimeMillis() - 600000, true, 7000l)
);

ItemCF recommender = new ItemCF(logs);
List<Integer> recommendations = recommender.recommend(101, 3);
System.out.println("推荐结果:" + recommendations);
}
}

public class SxProductview extends BaseEntity
{
    private static final long serialVersionUID = 1L;

    /** 主键自增长 */
    private Integer id;

    /** 用户唯一标识符,用于区分不同用户 */
    @Excel(name = "用户唯一标识符,用于区分不同用户")
    private Integer userId;

    /** 被浏览/交互的内容项ID(如商品ID、文章ID等) */
    @Excel(name = "被浏览/交互的内容项ID", readConverterExp = "如=商品ID、文章ID等")
    private Integer itemId;

    /** 记录事件发生的时间戳(通常为Unix时间戳,毫秒级精度) */
    @Excel(name = "记录事件发生的时间戳", readConverterExp = "通=常为Unix时间戳,毫秒级精度")
    private Long timestamp;

    /** 标记该次浏览是否为重复访问(true表示用户之前已看过该内容) */
    @Excel(name = "标记该次浏览是否为重复访问", readConverterExp = "t=rue表示用户之前已看过该内容")
    private Boolean isRepeated;

    /** 用户停留/交互持续时间(单位通常是毫秒) */
    @Excel(name = "用户停留/交互持续时间", readConverterExp = "单=位通常是毫秒")
    private Long duration;


}

添加浏览数据

 @PostMapping("/add")
    public AjaxResult add(@RequestBody Jfview o)
    {

        Jfview ol=null;

        Jfview c=new Jfview();
        c.setUserId(o.getUserId());
        c.setItemId(o.getItemId());
        List<Jfview> li=jfviewService.selectJfviewList(c);


        if(li!=null&&li.size()>0)ol=li.get(0);
        if(ol!=null){
            if(ol.getDuration()!=null)o.setDuration(ol.getDuration()+o.getDuration());
            o.setRepeated(true);
            o.setId(ol.getId());
            jfviewService.updateJfview(o);

        }else{
            o.setRepeated(false);
            jfviewService.insertJfview(o);
        }


        return success();
    }
 //根据用户浏览数据推荐商品,协同过滤,要有一定量的商品,和不同用户的对商品的浏览数据controller

    @GetMapping("/tj")
    public AjaxResult tj( Integer myuid){


        if(myuid==null||myuid<=0){
            List<SxProduct> gli=new ArrayList<>();
            return  success(gli);
        }

        List<SxProductview> logs =  sxProductviewService.selectSxProductviewList(new SxProductview( ));
        ItemCF recommender = new ItemCF(logs);
        List<Integer> recommendations = recommender.recommend(myuid, 10);
        System.out.println("推荐结果:" + recommendations);
        if(recommendations==null||recommendations.size()==0){
            List<SxProduct> gli=new ArrayList<>();
            return   success(gli);
        }



        SxProduct o=new SxProduct();

        o.setStatus("1");
        o.setInidli(recommendations);

        List<SxProduct> gli=sxProductService.selectSxProductList(o  );


        return   success(gli);


    }

uniapp3可以这样写

onShow(() => {
		 startTemp.value=new Date().getTime();
		 console.log("--------onShow----------startTemp.value:"+startTemp.value)
	})
	onDeactivated(() => {
		 endTemp.value=new Date().getTime(); 
		 console.log("-------onDeactivated-----------endTemp.value:"+endTemp.value)
		 saveView();
	})
	onHide(() => {
		 endTemp.value=new Date().getTime(); 
		 console.log("--------onHide----------endTemp.value:"+endTemp.value)
		 saveView();
	})
 网页,由于直接关闭网页会提交不了,所以使用计时器监听

created() {
    this.startTemp=new Date().getTime();
    if(!this.seti)this.seti=setInterval(()=>{
      this.endTemp=new Date().getTime();
      this.saveView();
    },1000*5)

    this.id=this.$route.query.id;
    if(this.$store.state.user.token ){
      this.islogin=1;

    }
    this.getDetail();
    this.getProductList("")
    this.getProductRatingList();


  },
  destroyed() {
    this.endTemp=new Date().getTime();
    this.saveView();
    if(this.seti)clearInterval(this.seti)

  },
  activated() {
    this.startTemp=new Date().getTime();
    if(!this.seti)this.seti=setInterval(()=>{
      this.endTemp=new Date().getTime();
      this.saveView();
    },1000*5)
  },
  deactivated() {
    this.endTemp=new Date().getTime();
    this.saveView();
    if(this.seti)clearInterval(this.seti)
  },
<if test="inidli != null   ">
             and  product_id in
                <foreach item="item" index="index" collection="inidli" open="(" separator="," close=")">
                 #{item}

                </foreach>
            </if>
 saveView(){
      if(localStorage.login_uid && this.id){
        let o={
          userId:localStorage.login_uid,
          itemId:this.id,
          timestamp:this.startTemp,
          duration:this.endTemp - this.startTemp
        }
        addProductview(o).then(response => {
          console.log('保存浏览记录成功', o);
        }).catch(() => {
          console.error('保存浏览记录失败');
        })
      }
    },

data

 seti:null,
      startTemp:0,
      endTemp:0,

好博客就要一起分享哦!分享海报

此处可发布评论

评论(0展开评论

暂无评论,快来写一下吧

展开评论

您可能感兴趣的博客

客服QQ 1913284695