SpringBoot2.7.10+Elasticsearch7.9.3实现智能推荐功能

理念

基于用户行为数据的权重设置实现

点击行为

  • o 数据收集与存储:在搜索系统的后端,记录用户的每次搜索请求以及对应的搜索结果展示和点击情况。这些数据可以存储在数据库中,包括搜索关键词、展示的搜索结果列表(包含文档 ID、标题等信息)、用户点击的文档 ID、点击时间等。
  • o 权重更新算法:定期(如每天或者每周)对这些数据进行统计分析。计算每个文档的点击次数占总展示次数的比例,即点击率(CTR)。例如,一个文档在 100 次展示中被点击了 10 次,那么 CTR = 10 / 100 = 0.1。根据 CTR 来调整文档的权重。可以设置一个基本的权重调整公式,如权重 = 原始权重 ×(1 + CTR× 调整因子),其中调整因子可以根据实际情况设定,如 0.5。如果原始权重为 0.8,那么调整后的权重 = 0.8×(1 + 0.1×0.5) = 0.84。
  • o 点击深度与停留时间分析:除了 CTR,还可以分析用户在点击文档后的行为。通过 JavaScript 等技术在前端记录用户在文档页面的停留时间和滚动深度等信息,并将这些数据发送回后端。对于停留时间长、滚动深度大的文档,可以认为用户对其更感兴趣,给予额外的权重提升。例如,停留时间超过一定阈值(如 30 秒)的文档,权重可以再增加 0.1。

搜索历史与偏好

  • o 用户画像构建:收集用户的搜索历史、浏览历史、收藏记录等数据,构建用户画像。可以使用机器学习算法或者简单的统计方法来挖掘用户的兴趣领域。例如,对用户浏览的文档进行分类,统计每个类别出现的频率,确定用户最感兴趣的几个类别。
  • o 权重调整策略:当用户进行新的搜索时,对于与用户兴趣类别相关的文档,在权重计算时增加一个偏好因子。例如,如果用户经常浏览科技类文档,在搜索 “人工智能” 相关内容时,科技类文档的权重可以在原始权重基础上增加 0.3。这个偏好因子的大小可以根据用户对该兴趣领域的关注度(如浏览频率、收藏比例等)来动态调整。

反馈与评价

  • o 反馈数据收集:在搜索结果页面或者文档页面提供反馈渠道,如点赞、差评、评论等按钮。收集用户的反馈数据并存储在数据库中,包括反馈类型(点赞或差评)、反馈的文档 ID、反馈时间等。
  • o 权重更新机制:根据反馈数据更新文档权重。例如,对于每一个点赞,文档权重增加 0.1;对于每一个差评,权重降低 0.2。同时,可以考虑反馈的时效性,近期的反馈对权重的影响更大。可以设置一个时间衰减函数,如权重调整 = 反馈权重 ×exp(-(当前时间 - 反馈时间)/ 时间常数),其中时间常数可以根据实际情况设定,如一周的时间(以秒为单位)。

时间因素

  • o 时间戳记录:在索引文档时,记录文档的发布时间和更新时间。在搜索时,计算当前时间与文档时间的时间差。
  • o 权重衰减函数:对于时效性强的内容(如新闻),可以使用指数衰减函数来调整权重。例如,设新闻文档的初始权重为 1.0,权重衰减函数为 weight = initial_weight×exp(-(current_time - publish_time)/decay_constant),其中 decay_constant 可以根据内容类型设定,如对于新闻可以设为一天(以秒为单位)。对于具有长期价值的内容,如学术论文,可以设置较小的衰减率或者不进行衰减。

基于内容特征的权重设置实现

关键词匹配

  • o 数据结构与索引:使用倒排索引来存储关键词与文档的关系。倒排索引是一种数据结构,它记录了每个关键词出现的文档列表以及在文档中的位置(如标题、正文等)。在索引构建过程中,需要对文档进行分词处理,将文本内容分解为一个个的关键词。例如,对于文档 “智能搜索的权重设置很重要”,分词后得到 “智能搜索”、“权重设置”、“很重要” 等关键词,然后将这些关键词与文档的标识(如文档 ID)一起存入倒排索引中。
  • o 权重计算算法:当进行搜索时,首先在倒排索引中找到包含搜索关键词的文档列表。对于标题中出现关键词的文档,可以给一个基础权重值(如 1.0),对于摘要中出现关键词的文档,权重可以设为 0.8,正文中出现关键词的文档权重设为 0.6。如果一个文档的标题和正文都出现了关键词,那么它的权重可以是标题权重和正文权重的某种组合,如相加(1.0 + 0.6 = 1.6)或者按照一定比例相加(如标题权重 ×0.7 + 正文权重 ×0.3)。

词频与逆文档频率(TF - IDF)

  • o TF 计算:在构建索引或者搜索时计算词频。对于每个文档中的每个关键词,计算其出现的频率。假设文档总词数为 n,关键词出现的次数为 m,那么词频 TF = m /n。例如,一个文档总共有 100 个词,某个关键词出现了 5 次,那么 TF = 5 / 100 = 0.05。
  • o IDF 计算:首先统计每个关键词在整个文档集合中的出现文档数。设文档集合中总文档数为 N,包含某个关键词的文档数为 d,那么逆文档频率 IDF = log (N /d)。例如,总共有 1000 个文档,某个关键词在 100 个文档中出现,那么 IDF = log (1000 / 100) = log (10)≈1。
  • o TF - IDF 权重计算:最终的 TF - IDF 权重为 TF 和 IDF 的乘积。例如,上述关键词的 TF - IDF 权重为 0.05×1 = 0.05。在实际应用中,可能需要对 TF - IDF 权重进行归一化处理,使其值落在一个合适的范围内,方便后续与其他权重进行组合。

内容标签

  • o 栏目分类:通过数据的类型分为三级栏目。
  • o 大模型标签匹配:通过大模型生成标签。

代码

引入ES的连接客户端maven包

        <dependency>
            <groupId>org.elasticsearch.client</groupId>
            <artifactId>elasticsearch-rest-high-level-client</artifactId>
            <version>7.17.15</version>
        </dependency>

创建ES配置类

import lombok.extern.slf4j.Slf4j;
import org.apache.http.HttpHost;
import org.elasticsearch.client.RestClient;
import org.elasticsearch.client.RestHighLevelClient;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
@Slf4j
public class ElasticSearchConfig {

    //单节点
    @Value("${elasticsearch.url}")
    private String url;

    @Value("${elasticsearch.port}")
    private int port;

    @Bean
    public RestHighLevelClient restHighLevelClient() {
        //设置账号密码的链接方式
        String[] urls = url.split(",");
        HttpHost[] httpHosts = new HttpHost[urls.length];
        for (int i = 0; i < urls.length; i++) {
            httpHosts[i] = new HttpHost(urls[i], port);
        }
        return new RestHighLevelClient(
                RestClient.builder(httpHosts));
    }

}

推荐逻辑查询使用:

ES查询代码我这里只贴了极少部分,可以根据项目需求实现全方位的计算

        SearchSourceBuilder sourceBuilder = new SearchSourceBuilder();
        //设置分页参数
        sourceBuilder.from((suggestListDto.getPageNum() - 1) * suggestListDto.getPageSize());
        sourceBuilder.size(suggestListDto.getPageSize());
        //设置查询条件
        BoolQueryBuilder boolQuery = QueryBuilders.boolQuery();
        //不返回内容字段
        sourceBuilder.fetchSource(null,"content");
        boolQuery.must(QueryBuilders.termQuery("tableType",  "news"));

        //开始自定义评分
        List<FunctionScoreQueryBuilder.FilterFunctionBuilder> filterFunctionBuilders = new ArrayList<>();

        Integer isHotSearch = suggestListDto.getIsHotSearch() == null ? 0 : suggestListDto.getIsHotSearch();

        if (isHotSearch == 0){
            // 添加随机函数 使每次刷新的数据都不一样
            filterFunctionBuilders.add(
                    new FunctionScoreQueryBuilder.FilterFunctionBuilder(
                                ScoreFunctionBuilders.randomFunction() // 使用 randomFunction 方法
                    )
            );
        }

        // 添加根据 pubTime  高斯函数是钟形的——它的衰减速率是先缓慢,然后变快,最后又放缓。
        filterFunctionBuilders.add(
                new FunctionScoreQueryBuilder.FilterFunctionBuilder(
                        QueryBuilders.matchAllQuery(),
                        ScoreFunctionBuilders.gaussDecayFunction("pubTime",
                                LocalDateTime.now()
                                , "10d" // 设置衰减范围,这里是 10 天
                                , "5d" // 设置偏移量,从 5 天开始衰减
                                , 0.5) // 衰减速率,这里是 0.5,表示远离 origin 的时间将衰减得更快
                                .setWeight(10.0f)
                )
        );
       FunctionScoreQueryBuilder functionScoreQueryBuilder = QueryBuilders
                .functionScoreQuery(boolQuery, filterFunctionBuilders.toArray(new FunctionScoreQueryBuilder.FilterFunctionBuilder[filterFunctionBuilders.size()]))
                .scoreMode(FunctionScoreQuery.ScoreMode.SUM) // 可以是 "sum", "multiply", "min", "max", "avg"
                .maxBoost(100);
       sourceBuilder.query(functionScoreQueryBuilder);
            sourceBuilder.sort(SortBuilders.scoreSort().order(SortOrder.DESC));
       sourceBuilder.collapse(collapseBuilder);
       // 构建搜索请求
       SearchRequest searchRequest = new SearchRequest();
       searchRequest.indices(SearchConstants.INDEX_NAME);
       searchRequest.source(sourceBuilder);

       SearchResponse searchResponse = restHighLevelClient.search(searchRequest, RequestOptions.DEFAULT);
       SearchHits hits = searchResponse.getHits();
       SearchHit[] searchHits = hits.getHits();
posted @ 2025-06-27 10:47  World-Peace  阅读(40)  评论(0)    收藏  举报