SparrowRecys——线上服务

一、线上服务主要内容

  • 把候选物品和离线处理好的特征载入到服务器
  • 将离线模型上线
  • 在线进行模型服务(model serving)
    如何做到负载均衡、缓存、推荐服务降级机制:

二、本项目选择服务器——Jetty服务器

Jetty服务器

public class RecSysServer {
    //主函数,创建推荐服务器并运行
    public static void main(String[] args) throws Exception {
        new RecSysServer().run();
    }
    //推荐服务器的默认服务端口6010
    private static final int DEFAULT_PORT = 6010;


    //运行推荐服务器的函数
    public void run() throws Exception{
        int port = DEFAULT_PORT;
        //绑定IP地址和端口,0.0.0.0代表本地运行
        InetSocketAddress inetAddress = new InetSocketAddress("0.0.0.0", port);
        //创建Jetty服务器
        Server server = new Server(inetAddress);
        //创建Jetty服务器的环境handler
        ServletContextHandler context = new ServletContextHandler();
        context.setContextPath("/");
        context.setWelcomeFiles(new String[] { "index.html" });


        //添加API,getMovie,获取电影相关数据
        context.addServlet(new ServletHolder(new MovieService()), "/getmovie");
        //添加API,getuser,获取用户相关数据
        context.addServlet(new ServletHolder(new UserService()), "/getuser");
        //添加API,getsimilarmovie,获取相似电影推荐
        context.addServlet(new ServletHolder(new SimilarMovieService()), "/getsimilarmovie");
        //添加API,getrecommendation,获取各类电影推荐
        context.addServlet(new ServletHolder(new RecommendationService()), "/getrecommendation");
        //设置Jetty的环境handler
        server.setHandler(context);


        //启动Jetty服务器
        server.start();
        server.join();
    }

三、存储模块redis

前言:类似Embedding特征是在离线环境下生成的,而推荐服务器是在线上环境中运行的。那离线特征数据是如何导入到线上让推荐服务器使用?
需要一个中介redis将embedding存入,线上模型推荐再将redis里的embedding取出。

  • 分级存储:把越频繁访问的数据放到越快的数据库甚至缓存里,把海量的全量数据放到廉价但是查询速度较慢的数据库中。
    在这里插入图片描述
    SparrowRecsys采取的分级存储策略:
  • 用户特征总数较大,难以全部存入服务器内存中,将其存入redis里。
  • 物品特征总数较小,将其存入服务器内存中。
  • HDFS(单机环境下以本机文件系统为例)存储每次处理的全量特征和训练得到的embedding。。
    在这里插入图片描述
    Redis基础知识:
  • Redis所有数据以key-value形式存储,key只能是字符串,value支持结构有string(字符串)、list(链表)、set(集合)、zset(有序集合) 和 hash(哈希)。
  • Redis的QPS峰值很高,还有数据易丢失,不应该把关键业务数据存储在Redis中。
  • Redis基本操作:set、get、keys,value的数据类型用到了string.
    将Embedding向量存入redis中:

if (saveToRedis) {
  //创建redis client
  val redisClient = new Jedis(redisEndpoint, redisPort)
  val params = SetParams.setParams()
  //设置ttl为24小时
  params.ex(60 * 60 * 24)
  //遍历存储embedding向量
  for (movieId <- model.getVectors.keys) {
    //key的形式为前缀+movieId,例如i2vEmb:361
    //value的形式是由Embedding向量生成的字符串,例如 "0.1693846 0.2964318 -0.13044095 0.37574086 0.55175656 0.03217995 1.327348 -0.81346786 0.45146862 0.49406642"
    redisClient.set(redisKeyPrefix + ":" + movieId, model.getVectors(movieId).mkString(" "), params)
  }
  //关闭客户端连接
  redisClient.close()
}

将embedding向量从redis取出:在服务器端,希望将服务器把所有物品Embedding向量阶段性缓存在服务器内部,用户embedding进行实时查询。
过程:先用keys把所有物品embedding向量前缀键找出,依次将embedding向量载入内存。


//创建redis client
Jedis redisClient = new Jedis(REDIS_END_POINT, REDIS_PORT);
//查询出所有以embKey为前缀的数据
Set<String> movieEmbKeys = redisClient.keys(embKey + "*");
int validEmbCount = 0;
//遍历查出的key
for (String movieEmbKey : movieEmbKeys){
    String movieId = movieEmbKey.split(":")[1];
    Movie m = getMovieById(Integer.parseInt(movieId));
    if (null == m) {
        continue;
    }
    //用redisClient的get方法查询出key对应的value,再set到内存中的movie结构中
    m.setEmb(parseEmbStr(redisClient.get(movieEmbKey)));
    validEmbCount++;
}
redisClient.close();

具体到为用户推荐过程中,利用接口查出用户embedding,与内存中embedding进行相似度计算,得到最终的推荐列表。

本项目存储embedding使用方式为分布式文件系统+Redis+服务器内存。
value除了设置为string格式还能有其他存储结构存储embedding向量数据?

  • redis keys命令不能用在生产环境中,如果数量过大效率十分低,导致redis长时间堵塞在keys上。
  • Redis value 可以用pb格式存储, 存储上节省空间. 解析起来相比string, cpu的效率也应该会更高

四、召回层

推荐物品规模庞大时,如何快速又准确筛选掉不相关物品,从而节约排序时所消耗的资源。
召回层策略有:

  • 单策略召回:制定一条规则或一个简单模型快速召回可能相关物品
  • 多路召回:采用不同的策略、特征或简单模型,分别召回一部分候选集,然后把候选集混合在一起供后序排序模型使用策略。
  • 基于embedding召回

public static List<Movie> retrievalCandidatesByEmbedding(User user){
    if (null == user){
        return null;
    }
    //获取用户embedding向量
    double[] userEmbedding = DataManager.getInstance().getUserEmbedding(user.getUserId(), "item2vec");
    if (null == userEmbedding){
        return null;
    }
    //获取所有影片候选集(这里取评分排名前10000的影片作为全部候选集)
    List<Movie> allCandidates = DataManager.getInstance().getMovies(10000, "rating");
    HashMap<Movie,Double> movieScoreMap = new HashMap<>();
    //逐一获取电影embedding,并计算与用户embedding的相似度
    for (Movie candidate : allCandidates){
        double[] itemEmbedding = DataManager.getInstance().getItemEmbedding(candidate.getMovieId(), "item2vec");
        double similarity = calculateEmbeddingSimilarity(userEmbedding, itemEmbedding);
        movieScoreMap.put(candidate, similarity);
    }
   
    List<Map.Entry<Movie,Double>> movieScoreList = new ArrayList<>(movieScoreMap.entrySet());
    //按照用户-电影embedding相似度进行候选电影集排序
    movieScoreList.sort(Map.Entry.comparingByValue());


    //生成并返回最终的候选集
    List<Movie> candidates = new ArrayList<>();
    for (Map.Entry<Movie,Double> movieScoreEntry : movieScoreList){
        candidates.add(movieScoreEntry.getKey());
    }
    return candidates.subList(0, Math.min(candidates.size(), size));
}

embedding召回过程:

  • 获取用户embedding
  • 获取影片候选集:选取10000部热门电影作为候选集,并获取物品(电影)embedding,计算用户embedding和物品embedding之间相似度。
  • 根据相似度排序,返回规定大小的候选集。

其中第二步是最耗时的,有办法可以解决吗?
使用局部敏感哈希搜索embedding最近邻:embedding向量

五、离线模型部署到线上

方法主要有:

  • 预存推荐结果和embedding结果
  • 预训练embedding+轻量级线上模型:
  • PMML模型
  • Tensorflow Serving

1.预存推荐结果和embedding结果

预存推荐结果:在离线环境下生成对每个用户的推荐结果,再将结果预存到Redis中,再线上环境取出预存数据直接推荐给用户。
优点:线下平台和线上平台完全解耦,线上服务过程没有复杂计算,推荐线上延迟极低
缺点:存储用户和物品的组合推荐结果,用户数量、物品数量规模过大发生组合爆炸,线上数据库无力支撑。
这种推荐方式适合冷启动和热门榜单。
预存embedding结果
离线训练好embedding,在线上通过相似度运算得到最终推荐结果。本项目通过Item2vec生成物品embedding,再存入Redis中,这就是预存embedding结果的应用。
然而预存embedding结果是在线下计算embedding,这样的方式缺少线上场景特征的引入,表达能力受限。

2.预训练embedding+轻量级线上模型

用深度网络离线训练embedding存入内存数据库,在线上实现逻辑回归或浅层神经网络轻量级模型拟合优化目标。
线上部分从Redis拿到离线生成embedding向量,跟其他特征embedding向量组合在一起,扔到标准的多层神经网络进行预估。不是end-end模型。

3.PMML模型

end-end,不能够支持所有复杂模型

4.Tensorflow Serving

离线使用Tensorflow的Keras接口完成模型构建和训练,再利用 TensorFlow Serving 载入模型,用 Docker 作为服务容器,然后在 Jetty 推荐服务器中发出 HTTP 请求到 TensorFlow Serving,获得模型推断结果,最后推荐服务器利用这一结果完成推荐排序。
基于Docker 的Tensorflow Serving,

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值
>