|
@@ -0,0 +1,174 @@
|
|
|
+package com.tzld.piaoquan.recommend.server.service.recall.strategy;
|
|
|
+
|
|
|
+import com.tzld.piaoquan.recommend.server.model.Video;
|
|
|
+import com.tzld.piaoquan.recommend.server.service.FeatureService;
|
|
|
+import com.tzld.piaoquan.recommend.server.service.filter.FilterParam;
|
|
|
+import com.tzld.piaoquan.recommend.server.service.filter.FilterResult;
|
|
|
+import com.tzld.piaoquan.recommend.server.service.filter.FilterService;
|
|
|
+import com.tzld.piaoquan.recommend.server.service.recall.FilterParamFactory;
|
|
|
+import com.tzld.piaoquan.recommend.server.service.recall.RecallParam;
|
|
|
+import com.tzld.piaoquan.recommend.server.service.recall.RecallStrategy;
|
|
|
+import lombok.extern.slf4j.Slf4j;
|
|
|
+import org.apache.commons.collections4.CollectionUtils;
|
|
|
+import org.apache.commons.collections4.MapUtils;
|
|
|
+import org.apache.commons.lang3.StringUtils;
|
|
|
+import org.springframework.beans.factory.annotation.Autowired;
|
|
|
+import org.springframework.beans.factory.annotation.Qualifier;
|
|
|
+import org.springframework.data.redis.core.RedisTemplate;
|
|
|
+import org.springframework.stereotype.Component;
|
|
|
+
|
|
|
+import java.util.*;
|
|
|
+import java.util.stream.Collectors;
|
|
|
+
|
|
|
+/**
|
|
|
+ * 头部视频,类似品类召回
|
|
|
+ */
|
|
|
+@Slf4j
|
|
|
+@Component
|
|
|
+public class HeadCate2RovRecallStrategy implements RecallStrategy {
|
|
|
+
|
|
|
+ @Autowired
|
|
|
+ private FilterService filterService;
|
|
|
+ @Autowired
|
|
|
+ @Qualifier("redisTemplate")
|
|
|
+ public RedisTemplate<String, String> redisTemplate;
|
|
|
+
|
|
|
+ @Autowired
|
|
|
+ private FeatureService featureService;
|
|
|
+
|
|
|
+ private static final String PUSH_FROM = "recall_strategy_head_cate2_rov";
|
|
|
+
|
|
|
+ private static final String SIM_MERGE_CATE2_KEY_FORMAT = "alg_recsys_good_cate_pair_list:%s";
|
|
|
+ private static final String MERGE_CATE2_VIDEO_LIST_KEY_FORMAT = "alg_recsys_recall_good_cate_pair_rovn:%s";
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public List<Video> recall(RecallParam param) {
|
|
|
+ if (Objects.isNull(param.getVideoId())) {
|
|
|
+ return Collections.emptyList();
|
|
|
+ }
|
|
|
+
|
|
|
+ // 获取头部视频基础信息
|
|
|
+ String vidStr = String.valueOf(param.getVideoId());
|
|
|
+ Map<String, String> headVideoInfo = featureService.getHeadVideoInfo(vidStr);
|
|
|
+ if (MapUtils.isEmpty(headVideoInfo)) {
|
|
|
+ return Collections.emptyList();
|
|
|
+ }
|
|
|
+
|
|
|
+ // 不存在品类或无效品类
|
|
|
+ String mergeCate2 = headVideoInfo.get("merge_second_level_cate");
|
|
|
+ if (StringUtils.isBlank(mergeCate2) || "unknown".equals(mergeCate2)) {
|
|
|
+ return Collections.emptyList();
|
|
|
+ }
|
|
|
+
|
|
|
+ // 获取相似品类
|
|
|
+ String simCate2Key = String.format(SIM_MERGE_CATE2_KEY_FORMAT, mergeCate2);
|
|
|
+ String simCate2List = redisTemplate.opsForValue().get(simCate2Key);
|
|
|
+ if (Objects.isNull(simCate2List)) {
|
|
|
+ return Collections.emptyList();
|
|
|
+ }
|
|
|
+
|
|
|
+ List<Video> videoResult = new ArrayList<>();
|
|
|
+
|
|
|
+ Map<String, Double> mergeCate2Pair = this.parsePair(simCate2List);
|
|
|
+
|
|
|
+ Map<String, Map<String, Double>> recallVideoMap = this.cate2Recall(new ArrayList<>(mergeCate2Pair.keySet()));
|
|
|
+
|
|
|
+ // 过滤
|
|
|
+ List<Long> allVid = recallVideoMap.values().stream()
|
|
|
+ .map(Map::keySet)
|
|
|
+ .flatMap(Collection::stream)
|
|
|
+ .map(Long::parseLong)
|
|
|
+ .collect(Collectors.toList());
|
|
|
+
|
|
|
+ FilterParam filterParam = FilterParamFactory.create(param, allVid);
|
|
|
+ FilterResult filterResult = filterService.filter(filterParam);
|
|
|
+ Set<Long> filterVids = new HashSet<>(filterResult.getVideoIds());
|
|
|
+
|
|
|
+ for (Map.Entry<String, Double> entry : mergeCate2Pair.entrySet()) {
|
|
|
+ String cate = entry.getKey();
|
|
|
+ Double cateScore = entry.getValue();
|
|
|
+
|
|
|
+ Map<String, Double> videoMap = recallVideoMap.getOrDefault(cate, new HashMap<>());
|
|
|
+ for (Map.Entry<String, Double> videoEntry : videoMap.entrySet()) {
|
|
|
+ long vid = Long.parseLong(videoEntry.getKey());
|
|
|
+
|
|
|
+ // 过滤之后不存在的视频,过滤掉
|
|
|
+ if (!filterVids.contains(vid)) {
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+
|
|
|
+ Double videoScore = videoEntry.getValue();
|
|
|
+
|
|
|
+ Video video = new Video();
|
|
|
+ video.setVideoId(vid);
|
|
|
+ video.setRovScore(cateScore * videoScore);
|
|
|
+ video.setPushFrom(PUSH_FROM);
|
|
|
+ videoResult.add(video);
|
|
|
+ }
|
|
|
+
|
|
|
+ }
|
|
|
+
|
|
|
+ videoResult.sort(Comparator.comparingDouble(o -> -o.getRovScore()));
|
|
|
+
|
|
|
+ return videoResult;
|
|
|
+ }
|
|
|
+
|
|
|
+ private Map<String, Double> parsePair(String value) {
|
|
|
+ if (StringUtils.isBlank(value)) {
|
|
|
+ return new HashMap<>();
|
|
|
+ }
|
|
|
+
|
|
|
+ String[] split = value.split("\t");
|
|
|
+ if (split.length != 2) {
|
|
|
+ return new HashMap<>();
|
|
|
+ }
|
|
|
+
|
|
|
+ String[] valueList = split[0].trim().split(",");
|
|
|
+ String[] scoreList = split[1].trim().split(",");
|
|
|
+ if (valueList.length != scoreList.length) {
|
|
|
+ return new HashMap<>();
|
|
|
+ }
|
|
|
+
|
|
|
+ Map<String, Double> resultMap = new HashMap<>();
|
|
|
+ for (int i = 0; i < valueList.length; i++) {
|
|
|
+ resultMap.put(valueList[i].trim(), Double.parseDouble(scoreList[i].trim()));
|
|
|
+ }
|
|
|
+
|
|
|
+ return resultMap;
|
|
|
+ }
|
|
|
+
|
|
|
+ private Map<String, Map<String, Double>> cate2Recall(List<String> mergeCate2List) {
|
|
|
+
|
|
|
+
|
|
|
+ List<String> redisKeys = mergeCate2List.stream().map(i -> String.format(MERGE_CATE2_VIDEO_LIST_KEY_FORMAT, i)).collect(Collectors.toList());
|
|
|
+ List<String> values = redisTemplate.opsForValue().multiGet(redisKeys);
|
|
|
+ if (CollectionUtils.isEmpty(values)) {
|
|
|
+ return new HashMap<>();
|
|
|
+ }
|
|
|
+
|
|
|
+ Map<String, Map<String, Double>> resultMap = new HashMap<>();
|
|
|
+
|
|
|
+ for (int i = 0; i < mergeCate2List.size(); i++) {
|
|
|
+ String mergeCate2 = mergeCate2List.get(i);
|
|
|
+
|
|
|
+ String value = values.get(i);
|
|
|
+ if (StringUtils.isBlank(value)) {
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+
|
|
|
+ Map<String, Double> recallVideoMap = this.parsePair(value);
|
|
|
+ if (MapUtils.isEmpty(recallVideoMap)) {
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+
|
|
|
+ resultMap.put(mergeCate2, recallVideoMap);
|
|
|
+ }
|
|
|
+
|
|
|
+ return resultMap;
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public String pushFrom() {
|
|
|
+ return PUSH_FROM;
|
|
|
+ }
|
|
|
+}
|