ES计算余弦相似度

  • Post author:
  • Post category:其他




一、前言

最近在项目中做数据推荐的功能,比如,猜你喜欢。主动给用户推荐用户喜欢的商品。如何判断某个商品是不是用户喜欢的呢?在调研过程中,发现es可以做相似度的计算,相似度可以表示用户对某个商品的喜爱程度。下面我就介绍一下如何实现的。



二、数据源

要进行数据计算,首先是要有数据基础。我们的数据源是什么呢?我们目前数据来源是用户行为。我们会采集用户近100天的用户行为。根据用户行为计算出用户的向量。比如我们会统计用户浏览行为,点击行为,分享行为等,根据这些行为的数据,来计算出用户的向量。当然我们计算的是多维度的向量,为了简单表示,大家可以用二维向量来理解。如下面的图:

在这里插入图片描述

图中每一个小点点都是一个用户,用户会散列到象限中。

同理,我们的每一个商品也会有商品的向量。也会散列到象限中。



三、余弦相似度

相似度是什么呢?可以理解为,相似度越近,用户越喜欢。如何计算相似度呢?我们在中学的时候学习了三角函数,其中有cos(x),可以来表示相似度,如下面图,cos(x) = b/c。当x角度越小的时候,cos(x)越小。

在这里插入图片描述

余弦相似度是很常见的相似度计算方案。除了余弦相似度,常见的

相似度计算有

  • 欧几里得距离
  • 皮尔逊相关系数
  • 余弦相似度
  • Tanimoto系数(广义Jaccard相似系数)

在这里插入图片描述



四、使用es计算余弦相似度

es如何计算相似度呢?

es 7.6.0 中提供了查询接口

先看下面的查询:

POST /index_vec_32/_search
{
  "from": 1, 
  "size": 10,
  "query": {
    "script_score": {
      "query": {
        "match_all": {}
      },
      "script": {
        "source": "cosineSimilarity(params.queryVector, doc['vec'])+1",
        "lang": "painless",
        "params": {
          "queryVector": [0.1, 0.2, -0.3,0.1,0.1,0.1,0.1,0.1,0.1,0.1,0.1,0.1,0.1,0.1,0.1,0.1,0.1,0.1,0.1,0.1,0.1,0.1,0.1,0.1,0.1,0.1,0.1,0.1,0.1,0.1,0.1,0.1]   
        }
      }
    }
  }, "_source": {
        "includes": [
            "tid",
            "sku"
        ],
        "excludes": []
    }

}

查询结果如下:

{
  "took" : 2,
  "timed_out" : false,
  "_shards" : {
    "total" : 3,
    "successful" : 3,
    "skipped" : 0,
    "failed" : 0
  },
  "hits" : {
    "total" : {
      "value" : 4,
      "relation" : "eq"
    },
    "max_score" : 1.282883,
    "hits" : [
      {
        "_index" : "index_vec_32",
        "_type" : "_doc",
        "_id" : "10_108219",
        "_score" : 1.282883,
        "_source" : {
          "sku" : 10,
          "tid" : "108219"
        }
      },
      {
        "_index" : "index_vec_32",
        "_type" : "_doc",
        "_id" : "10_108258",
        "_score" : 1.282883,
        "_source" : {
          "sku" : 10,
          "tid" : "108258"
        }
      },
      {
        "_index" : "index_vec_32",
        "_type" : "_doc",
        "_id" : "10_108262",
        "_score" : 1.282883,
        "_source" : {
          "sku" : 10,
          "tid" : "108262"
        }
      },
      {
        "_index" : "index_vec_32",
        "_type" : "_doc",
        "_id" : "10_108275",
        "_score" : 1.282883,
        "_source" : {
          "sku" : 10,
          "tid" : "108275"
        }
      }
    ]
  }
}

es提供了script查询模型, 我们可以在查询的地方进行余弦相似度计算。根据这个特点,我们就可以来进行计算了。

java实现:

 String inlineScript = "cosineSimilarity(params.queryVector, doc['vec'])+1";
        Map<String, Object> param = new HashMap<>();
        param.put("queryVector", vector);
        Script script = new Script(ScriptType.INLINE, Script.DEFAULT_SCRIPT_LANG, inlineScript, param);
        //筛选条件
        BoolQueryBuilder query = QueryBuilders.boolQuery();
        //eq
        query.must(QueryBuilders.matchPhraseQuery("aaaaa","xxxx");
        //in
        if (!CollectionUtil.isNullOrEmpty(list)) {
            Set set = new HashSet<Integer>();
            for (Integer fieldValue : list) {
                set.add(Integer.valueOf(fieldValue));
            }
            query.must(QueryBuilders.termsQuery("tid", set));
        }
        SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
        searchSourceBuilder.query(QueryBuilders.scriptScoreQuery(query, script));
        //查询返回字段
        String[] includeFields = new String[]{"tid", "sku"};
        searchSourceBuilder.fetchSource(includeFields, null);
        //分页参数
        searchSourceBuilder.from(0);
        searchSourceBuilder.size(topNumber);

        SearchRequest searchRequest = new SearchRequest(ESConstant.calIndex);
        searchRequest.source(searchSourceBuilder);

        LinkedList<ResDTO> list = new LinkedList<>();
        try {
            SearchResponse scrollResponse = client.search(searchRequest, RequestOptions.DEFAULT);
            SearchHits searchHits = scrollResponse.getHits();
            SearchHit[] hits = searchHits.getHits();
            for (SearchHit searchHit : hits) {
                String sourceAsString = searchHit.getSourceAsString();
                ResCalDTO dto = JsonUtil.fromJSON(sourceAsString, new TypeReference<ResCalDTO>() {
                });
                dto.setScore((double) searchHit.getScore());
                ResDTO resDTO = new ResDTO();
                resDTO.setTid(dto.getTid());
                resDTO.setScore(dto.getScore());
                if (dto.getSku().length() < 2) {
                    dto.setSku("0" + dto.getSku());
                }
                list.add(resDTO);
            }

        } catch (IOException e) {
            logger.error("es query error,{}", e);
        }
        return list;

通过上面方法我们可以从es中算出top N的最相似的数据,这个就是我们需要给用户推荐的数据。



五、思考

还是学习数学好呀!数学解决了很多的问题,应用的计算机真是无敌的存在!



版权声明:本文为kisscatforever原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。