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();