1.选择相似度算法
-
余弦相似度原理:
- 余弦相似度通过测量两个向量之间的夹角的余弦值来度量它们之间的相似性。
- 它将每个用户的标签表示为一个向量,然后计算这些向量之间的余弦相似度。
- 公式:cos(\theta) = \frac{A \cdot B}{|A| |B|}cos(θ)=∥A∥∥B∥A⋅B
- 余弦相似度适用于标签数据量较大的情况,因为它可以有效地处理高维向量空间中的相似性计算。
- 杰卡德
- 杰卡德相似系数是一种度量两个集合相似性的方法,通过计算两个集合交集的大小与并集大小的比例来得到。
- 它特别适用于标签数据,因为它可以有效地处理集合(即用户的标签集合)之间的相似性。
- 公式:J(A, B) = \frac{|A \cap B|}{|A \cup B|}J(A,B)=∣A∪B∣∣A∩B∣
- 场景选择
- 余弦相似度:适用于标签数量较多,且每个标签的权重可能不同的场景。它特别适合于文本分析、推荐系统等,其中每个标签(或词)的重要性可能不同。
- 杰卡德相似度:适用于标签数量较少,每个标签权重相同的场景。它特别适合于简单的标签匹配,如社交网络中的兴趣匹配。
测试代码
public class CosineSimilarity {
public static double calculateSimilarity(User user1, User user2) {
Map<String, Integer> tags1 = user1.getTags();
Map<String, Integer> tags2 = user2.getTags();
// 计算点积
double dotProduct = 0;
for (String tag : tags1.keySet()) {
dotProduct += tags1.get(tag) * (tags2.getOrDefault(tag, 0));
}
// 计算向量长度
double magnitude1 = calculateMagnitude(tags1);
double magnitude2 = calculateMagnitude(tags2);
// 防止除以0
if (magnitude1 == 0 || magnitude2 == 0) {
return 0;
}
// 计算余弦相似度
return dotProduct / (magnitude1 * magnitude2);
}
private static double calculateMagnitude(Map<String, Integer> tags) {
double magnitude = 0;
for (int value : tags.values()) {
magnitude += value * value;
}
return Math.sqrt(magnitude);
}
public static void main(String[] args) {
// 创建两个用户并添加标签
User user1 = new User("Alice");
user1.addTag("books");
user1.addTag("music",10);
user1.addTag("sports");
User user2 = new User("Bob");
user2.addTag("music");
user2.addTag("sports");
user2.addTag("books",10);
// 计算余弦相似度
double similarity = calculateSimilarity(user1, user2);
System.out.println("余弦相似度: " + similarity);
}
}
public class User {
private String name;
private Map<String, Integer> tags; // 标签及其权重
public User(String name) {
this.name = name;
this.tags = new HashMap<>();
}
public void addTag(String tag) {
tags.put(tag, 1); // 添加标签,权重设为1
}
public void addTag(String tag, int weight) {
tags.put(tag, weight); // 添加标签,权重设为指定值
}
public Map<String, Integer> getTags() {
return tags;
}
// 其他getter和setter方法
}
es实现相似度匹配
上面方法需要遍历整个数据库进行分数排序,考虑到全部用户id的获取排序,取出最佳适配度,这显然不显示
所以我们可以使用es,包含了很多内置的相似度比较和分词器,
1.创建索引
使用空白分词器
PUT /users
{
"settings": {
"analysis": {
"analyzer": {
"whitespace_analyzer": { // 使用内置的 whitespace 分析器
"type": "whitespace"
}
}
}
},
"mappings": {
"properties": {
"userId": {
"type": "keyword" // 用户ID,不进行分词
},
"tags": {
"type": "text",
"analyzer": "whitespace_analyzer", // 使用 whitespace 分析器
"search_analyzer": "whitespace", // 搜索时使用标准分析器
"term_vector": "with_positions_offsets" // 存储词汇信息
}
}
}
}
添加测试数据
POST /users/_doc
{
"userId": "user1",
"tags": "tag1 tag2 tag3"
}
POST /users/_doc
{
"userId": "user2",
"tags": "tag1 tag4"
}
POST /users/_doc/
{
"userId": "user3",
"tags": "tag2 tag3 tag5"
}
search指令
GET /users/_search
{
"query": {
"more_like_this": {
"fields": ["tags"],
"like": "tag1 tag4",
"min_term_freq": 1,
"max_query_terms": 10,
"min_doc_freq": 1,
"boost_terms": 1.0,
"boost": 1.0
}
}
}
//权重需求设置
GET /users/_search
{
"query": {
"bool": {
"should": [
{
"match": {
"tags": {
"query": "tag1",
"boost": 10
}
}
},
{
"match": {
"tags": {
"query": "tag4",
"boost": 1.2
}
}
}
]
}
}
}
使用java相关客户端发送获取数据,反序列化成对象使用