程序设计通用能力-标签
程序设计通用能力-标签
在生活中人们有时非常讨厌来自别人的标签,比如 你该 xxxx 你是xxxx 应该 xxxxx ,但是标签化的思想非常便于对资源的查询,根据不同的资源类型进行划分可以更快的帮助用户找到想要搜索的内容.
标签的作用
- 精准搜索与导航: 标签能够提供更精准的搜索和导航功能。用户可以通过选择或搜索特定标签来获取所需资源,从而缩小搜索范围,降低信息获取的难度。
- 个性化体验和推送: 通过了解用户的标签偏好,系统可以为用户提供个性化的资源推送。这种定制化的体验增加了用户的满意度,因为他们可以更快速地找到感兴趣的内容。
- 知识管理和共享: 在团队或组织中,通过为资源打标签,可以更好地管理知识资产,使得团队成员能够在需要时更轻松地找到和共享相关资源。
- 内容筛选和过滤: 标签可以帮助用户在大量内容中进行筛选和过滤,只获取与他们关心的主题相关的资源,提高了信息的价值和可用性。
- 业务分析和决策支持: 标签还可以用于数据分析,帮助企业了解资源的关联性和受欢迎程度,从而做出更准确的业务决策。
- 降低信息重复性: 通过标签,用户可以更容易地发现之前可能已经找到过的资源,避免了信息的重复查找和获取。
- 拓展内容发现: 标签可以引导用户探索更多相关的内容,从而拓展他们的兴趣领域,提供更多学习和发现的机会。
- 数据可视化和洞察: 通过分析不同标签的使用频率和组合,你可以创建数据可视化图表,帮助你更好地了解用户兴趣和趋势。
- 版本控制和更新跟踪: 在软件开发等领域,标签可以用于版本控制和更新跟踪,帮助团队成员明确哪个版本适用于不同环境或用途。
- 提升资源质量: 标签可以用于对资源进行质量评估和分类,使得高质量的资源更容易被用户找到和识别。
总结 : 凡是有价值的数据都可以打上标签,标签之间也可以设计权重,实现更自定意化的推荐,比如节假日带有相关标签的商品计算推送时权重更高 , 比如重点用户身上打 vip 标签 ,让其被检索到概率更高(抖音推送) ,比如按照标签分析用户对不同类别的喜好,分析用户的行为决定下一阶段的侧重点, 重点用户使用体验优化(使用程度重的用户优化其资源加载速度) , 标签的作用就是做区分,有时只有做好了区分才能知道从哪里能获得更多价值.总的来说,标签的优化和应用可以在多个方面增加价值,从提升用户体验、个性化推荐,到精准广告投放和业务决策,都能够从中获益。标签不仅是一种分类和区分的方式,更是优化和提升整体平台价值的关键手段之一。
标签大体分类
- 标签权重与个性化推荐: 给标签设计权重可以根据不同的优先级实现更加个性化的推荐。通过为特定标签设置更高的权重,系统可以更精准地推送与用户兴趣更相关的内容,从而提高用户参与度。
- 节假日与特定标签的关联: 在特定时期,比如节假日,可以将相关标签与商品或内容关联,以提高这些资源的推荐权重。这种策略可以更好地适应用户在不同时间段的需求变化。
- 用户群体标签: 对于重要用户,比如VIP用户,可以为其打上特定的标签,以确保他们能够获得更优质的服务和推荐内容。这有助于提升重要用户的满意度和忠诚度。
- 用户喜好分析: 通过分析用户对不同标签的偏好,你可以更深入地了解用户的兴趣和偏好。这将帮助你在下一阶段的发展中更有针对性地推出新的资源或功能。
- 资源加载速度优化: 对于重度使用的用户,可以根据他们的标签和行为数据优化资源的加载速度,提供更流畅的使用体验。
- 精准营销和广告投放: 基于标签信息,你可以实现更精准的广告投放和营销策略,将广告内容推送给与其相关兴趣标签匹配的用户。
- 数据分析和决策依据: 标签数据可以成为数据分析和业务决策的依据,帮助你更好地了解用户行为、趋势和偏好,以优化产品和服务。
- 用户参与度提升: 通过提供个性化、感兴趣的资源,标签可以增加用户的参与度和互动,从而促进社区的发展和用户积极参与。
标签的实现
前置条件(基础)
- 一张维护标签的表
- 为资源打上标签
- 同一张表上
- 新建表维护资源标签关系
方法一:一张维护标签的表
在这种方法中,你会创建一张独立的数据库表来维护所有标签。每个标签都有一个唯一的标识符,以及与之相关的其他信息,如标签名称、描述等。资源与标签之间的关系通过在资源表中添加一个外键来实现,该外键与标签表中的标识符关联。
优点:
- 简洁: 只需要维护一个标签表,可以集中管理标签的信息。
- 逻辑清晰: 资源与标签之间的关系直接通过外键建立,不需要额外的关联表。
- 标签属性: 可以在标签表中添加更多属性,如描述、权重等。
缺点:
- 查询效率: 如果资源与标签之间的关系变得复杂,查询可能会涉及多个表的联结操作,影响查询效率。
- 维护性: 如果标签数量庞大,可能会导致标签表变得很大,需要考虑数据库性能和维护的问题。
适用场景:
- 标签数量相对较少,标签信息不需要频繁更新。
- 查询效率要求不高,或者标签与资源的关联关系不太复杂。
方法二:新建表维护资源标签关系
在这种方法中,你会创建一个新的数据库表来维护资源与标签之间的关系。该表会记录每个资源与其相关的标签。此外,可能还需要一个标签表来存储标签的信息。
优点:
- 灵活性: 可以更好地处理资源与标签之间的多对多关系,灵活性较高。
- 查询效率: 关系表使得查询与资源标签关系的操作更加高效。
缺点:
- 复杂性: 需要维护两个表,一个是资源表,另一个是资源与标签关系表。
- 数据冗余: 关系表可能会包含重复的资源和标签信息,需要谨慎处理。
适用场景:
- 标签数量较大,资源与标签的关联关系复杂。
- 查询效率要求较高,需要高效地获取资源与标签的关联信息。
选择方法的考虑因素
- 标签数量: 如果标签数量有限,且标签信息不频繁变动,一张维护标签的表可能更合适。如果标签数量较大且关系复杂,可以考虑使用新建表维护关系的方法。
- 查询需求: 如果需要高效地查询资源与标签的关联信息,新建表维护关系的方法可能更适合。
- 数据一致性: 如果标签信息需要频繁变动,但你希望保持资源与标签之间的历史关联,使用新建表维护关系的方法可能更适合。
前端实现方式
- 固定页面标签
- 请求数据库动态渲染
- 固定 + 请求模式
- 固定页面标签:
- 说明: 在页面上直接静态展示固定的标签类别,用户无法修改或添加新标签。
- 实现: 在页面布局中,通过HTML元素预定义标签类别。例如,使用一个固定的导航栏或按钮列表来表示标签类别。点击特定标签后,页面会直接展示与该标签相关的内容,不需要额外的数据库查询。
- 优点: 简单、快速,不需要频繁的数据库查询。
- 缺点: 用户无法个性化添加或删除标签,限制了灵活性。
- 请求数据库动态渲染:
- 说明: 页面加载时,从数据库中获取标签类别数据,然后动态渲染到页面上。
- 实现: 在后端实现一个API,通过数据库查询获取标签数据,并将数据传递给前端。前端通过JavaScript将标签数据渲染到页面上的相应位置。
- 优点: 允许动态更新标签数据,用户可以根据需要选择标签。
- 缺点: 需要进行数据库查询,可能增加服务器负担和页面加载时间。
- 固定 + 请求模式:
- 说明: 结合了前两种方法,页面上展示固定的标签类别,同时提供一种方式来请求数据库中的动态数据。
- 实现: 在页面上展示固定的标签,但为用户提供一个按钮或链接,点击后触发请求,从数据库中获取与选定标签相关的数据并渲染到页面上。
- 优点: 提供了固定标签的便利性,同时也允许动态请求更多数据。
- 缺点: 需要设计合理的用户界面来平衡固定标签和动态请求的交互。
后端实现
主要是查询实现,针对标签为资源信息本身固有属性去实现
- 接收 前端查询的标签列表, 模糊查询
- 接收前端拆寻标签列表,查询全部数据内存种计算
- 根据权重查询部分标签 + 剩余标签内存计算
```(比如个性化推荐时候那些标签权重比较高为必查标签,必查标签查到了之后去进一步做匹配排列返回数据先后顺序)``- 两种方法都使用 , 有限采用算的快的进行数据返回
极限性能具体查询方式好坏需要根据实际数据量情况下进行测试鉴别 , 没有觉得的快慢 , 实际应用时涉及到了用户可选标签的多少, 数据库的性能,(模糊查询的匹配方式 索引大概率是失效的)
标签化资源的实现和应用示例
标签表
涉及到多级标签 ,可以自定义适合场景的规则,比如一级标题代表资源类型,比如视频,音频,文件,文档,二级标题代表这各自领域的东西
ps : 标签的优点在于灵活,给程序打上不同的标签便于用户和程序员两方面更好的操纵资源数据。这里可以给标签一个权重 ,用于在有权中的情况下更加灵活的实现更丰富的功能,同时也可以更加灵活的实现消息推送等功能。
-- 标签
-- auto-generated definition
create table tag
(id bigint auto_increment comment 'id'primary key,tagName varchar(256) null comment '标签名称',userId bigint null comment '用户 id',parentId bigint null comment '父标签 id',isParent tinyint null comment '0 - 不是, 1 - 父标签',createTime datetime default CURRENT_TIMESTAMP null comment '创建时间',updateTime datetime default CURRENT_TIMESTAMP null on update CURRENT_TIMESTAMP,isDelete tinyint default 0 not null comment '是否删除',constraint uniIdx_tagNameunique (tagName)
)comment '标签';
可扩展字段 [场景] [权重]
演示用例
以常见交友软件为例 比如soul 会让你给自己打标签,也会让你做题给你分配不同的星球实际上也是一种标签 , 有时用户的个人信息 ,比如年龄性格专业的行为分析之后也会有一套隐形的标签系统 , 比如淘宝从来不给我推便宜货但也不给我推荐特别贵的 , 比如我的抖音总是能刷到适龄女性。所以以用户身上的标签为例。
create table user
(id bigint auto_increment comment 'id'primary key,userAccount varchar(256) not null comment '账号',userPassword varchar(512) not null comment '密码',userName varchar(256) null comment '用户昵称',userAvatar varchar(1024) null comment '用户头像',userRole varchar(256) default 'user' not null comment '用户角色:user/admin',userMailbox varchar(40) null comment '邮箱',tags varchar(1024) null comment '标签 json 列表',accessKey varchar(512) not null comment 'accessKey',secretKey varchar(512) not null comment 'secretKey',createTime datetime default CURRENT_TIMESTAMP not null comment '创建时间',updateTime datetime default CURRENT_TIMESTAMP not null on update CURRENT_TIMESTAMP comment '更新时间',isDelete tinyint default 0 not null comment '是否删除'
)comment '用户' collate = utf8mb4_unicode_ci;create index idx_userAccounton user (userAccount);
查询代码
通过两种方式查询一种使用数据库查询 ,一种使用内存方式去查询,数据库查询时返回信息做分页处理,当然下面的数据库查询换个分页查询的写法也一样。这没什么难度,写入时候将前端的传值转换成 json 串存起来就好,也可以数据库用 Json 格式的数据存,当然那样操作数据库的时候难度会大一些 ,写起来可能不如基础的 crud 好些 。
@Override
public Page searchUsersByTags(UserTagsQueryRequest userTagsQueryRequest) {List tagNameList = userTagsQueryRequest.getTags();ThrowUtils.throwIf(CollectionUtils.isEmpty(tagNameList), ErrorCode.PARAMS_ERROR);QueryWrapper userQueryWrapper = new QueryWrapper<>();// 设置查询条件// ...List users = userMapper.selectList(userQueryWrapper);List userVOList = users.stream().filter(user -> {String tags = user.getTags();Set tagSet = GSON.fromJson(tags, new TypeToken>() {}.getType());tagSet = Optional.ofNullable(tagSet).orElse(new HashSet<>());return tagSet.containsAll(tagNameList);}).map(this::getUserVO).collect(Collectors.toList());Page userVOPage = new Page<>();long pageSize = userTagsQueryRequest.getPageSize();long current = userTagsQueryRequest.getCurrent();int total = userVOList.size();userVOPage.setTotal(total);userVOPage.setSize(pageSize);userVOPage.setCurrent(current);int index = (int)((current - 1) * pageSize);int toIndex = (int)(current * pageSize);if ( total < index ){userVOPage.setRecords(new ArrayList<>());return userVOPage;}toIndex = Math.min(total, toIndex);List userVOS = userVOList.subList(index, toIndex);userVOPage.setRecords(userVOS);return userVOPage;
}@Deprecated
public List searchUsersByTagsBySQL(List tagNameList) {ThrowUtils.throwIf(CollectionUtils.isEmpty(tagNameList), ErrorCode.PARAMS_ERROR);QueryWrapper userQueryWrapper = new QueryWrapper<>();// 数据库模糊计算for (String tagName : tagNameList) {userQueryWrapper.like("tags", tagName);}//查找数据// TODO 分页List users = userMapper.selectList(userQueryWrapper);return users.stream().map(this::getUserVO).collect(Collectors.toList());
}
当然这没什么了不起,只是能让用户更快的找到自己的爱好罢了。 有些小网站经常这面做,啊常见的不得了。
推荐算法
有灵性的网站才是用户喜欢的, 审美都是不断变化的,一成不变的情况下用户很容易对网站失去兴趣的, 优质内容的更新 ,用户体验的升级,优良的氪金系统才是用户的所爱 ,好吧对于我这种不开 VIP 的人有的时候真的会为了获取信息或者说有些服务买单。比如稳定的梯子,知识星球,服务器等等。
一个简单的匹配机制,类似于 soul 的推荐 ;
基于用户身上已经存在的标签 , 我们可以对用户进行个性化的推荐划分了 。
实现方式为 编辑距离 算法,在打分的基础上进行排序,实现对标签相似度较高的用户进行推荐 。
@Overridepublic List matchUsers(long num, User loginUser) {QueryWrapper queryWrapper = new QueryWrapper<>();queryWrapper.select("id", "tags");queryWrapper.isNotNull("tags");List userList = this.list(queryWrapper);String tags = loginUser.getTags();Gson gson = new Gson();List tagList = gson.fromJson(tags, new TypeToken>() {}.getType());// 用户列表的下标 => 相似度List> list = new ArrayList<>();// 依次计算所有用户和当前用户的相似度for (int i = 0; i < userList.size(); i++) {User user = userList.get(i);String userTags = user.getTags();// 无标签或者为当前用户自己if (StringUtils.isBlank(userTags) || user.getId() == loginUser.getId()) {continue;}List userTagList = gson.fromJson(userTags, new TypeToken>() {}.getType());// 计算分数long distance = AlgorithmUtils.minDistance(tagList, userTagList);list.add(new Pair<>(user, distance));}// 按编辑距离由小到大排序List> topUserPairList = list.stream().sorted((a, b) -> (int) (a.getValue() - b.getValue())).limit(num).collect(Collectors.toList());// 原本顺序的 userId 列表List userIdList = topUserPairList.stream().map(pair -> pair.getKey().getId()).collect(Collectors.toList());QueryWrapper userQueryWrapper = new QueryWrapper<>();userQueryWrapper.in("id", userIdList);// 1, 3, 2// User1、User2、User3// 1 => User1, 2 => User2, 3 => User3Map> userIdUserListMap = this.list(userQueryWrapper).stream().map(user -> getSafetyUser(user)).collect(Collectors.groupingBy(User::getId));List finalUserList = new ArrayList<>();for (Long userId : userIdList) {finalUserList.add(userIdUserListMap.get(userId).get(0));}return finalUserList;}
编辑距离算法
public class AlgorithmUtils {/*** 编辑距离算法(用于计算最相似的两组标签)* 原理:https://blog.csdn.net/DBC_121/article/details/104198838** @param tagList1* @param tagList2* @return*/public static int minDistance(List tagList1, List tagList2) {int n = tagList1.size();int m = tagList2.size();if (n * m == 0) {return n + m;}int[][] d = new int[n + 1][m + 1];for (int i = 0; i < n + 1; i++) {d[i][0] = i;}for (int j = 0; j < m + 1; j++) {d[0][j] = j;}for (int i = 1; i < n + 1; i++) {for (int j = 1; j < m + 1; j++) {int left = d[i - 1][j] + 1;int down = d[i][j - 1] + 1;int left_down = d[i - 1][j - 1];if (!Objects.equals(tagList1.get(i - 1), tagList2.get(j - 1))) {left_down += 1;}d[i][j] = Math.min(left, Math.min(down, left_down));}}return d[n][m];}// [编程学习交流圈](https://www.code-nav.cn/) 连接万名编程爱好者,一起优秀!20000+ 小伙伴交流分享、40+ 大厂嘉宾一对一答疑、100+ 各方向编程交流群、4000+ 编程问答参考/*** 编辑距离算法(用于计算最相似的两个字符串)* 原理:https://blog.csdn.net/DBC_121/article/details/104198838** @param word1* @param word2* @return*/public static int minDistance(String word1, String word2) {int n = word1.length();int m = word2.length();if (n * m == 0) {return n + m;}int[][] d = new int[n + 1][m + 1];for (int i = 0; i < n + 1; i++) {d[i][0] = i;}for (int j = 0; j < m + 1; j++) {d[0][j] = j;}for (int i = 1; i < n + 1; i++) {for (int j = 1; j < m + 1; j++) {int left = d[i - 1][j] + 1;int down = d[i][j - 1] + 1;int left_down = d[i - 1][j - 1];if (word1.charAt(i - 1) != word2.charAt(j - 1)) {left_down += 1;}d[i][j] = Math.min(left, Math.min(down, left_down));}}return d[n][m];}
}
其他用于计算相似度的算法
- Jaccard相似系数: 用于计算集合之间的相似性,尤其在文本、推荐系统等领域常被使用。它衡量两个集合的交集与并集之间的比例,范围在0到1之间。
- 汉明距离(Hamming Distance): 主要用于计算两个等长字符串之间的差异,即在相同位置上不同字符的数量。主要用于处理二进制数据。
- Jaro-Winkler相似度: 用于比较两个字符串的相似性,尤其在姓名匹配等场景中常用。它考虑了字符匹配、字符顺序和字符位置等因素。
- Dice系数: 用于衡量两个集合的相似性,类似于Jaccard相似系数,但在计算时考虑了集合中元素的重复次数。
- TF-IDF(Term Frequency-Inverse Document Frequency): 用于比较文本的相似性,基于词项的出现频率和在文本集合中的重要程度。主要用于信息检索和文本挖掘。
- 皮尔逊相关系数: 用于度量两个变量之间的线性相关性,取值范围在-1到1之间。主要用于统计分析和数据挖掘。
- 曼哈顿距离: 衡量两个点在多维空间中的距离,是各个坐标绝对值差的和。
- 欧几里德距离: 类似曼哈顿距离,但考虑了坐标差的平方和的平方根。
- 标签传播算法(Label Propagation): 用于图数据中的节点分类和相似性传播,将相似节点的标签传播给邻居节点。
- K-Means聚类算法: 用于将数据分成K个簇,相似的数据点被归为同一簇。
- 编辑距离 :
- 应用场景:主要用于比较两个字符串之间的相似性,测量从一个字符串转换为另一个字符串所需的最小编辑操作次数(插入、删除、替换)。
- 功能:常用于拼写检查、自然语言处理中的文本相似度、DNA序列匹配等领域。可以用于判断两个字符串的相似程度以及实现自动纠错。
- 余弦相似度 :
- 应用场景:主要用于比较两个向量之间的相似性,常用于文本挖掘、信息检索等领域。
- 功能:度量两个向量之间的夹角余弦值,值越接近1表示两个向量越相似,越接近0表示越不相似。常用于判断文本、文章等之间的相似性,以及在推荐系统中进行用户相似性计算。
明签和暗签
就像是我们给别人打标签一样, 可能表面一套心里一套,总不能让我的 APP 告诉它他把我归类为
穷AC吧 , 就像是逻辑删除字段一样,有的时候我们不希望这些数据被用户获得,这不同于用户,或者是资源分配者对用户打的标签,而是系统 或者说是在观测用户行为之后,为了给用户更好的服务体验, 暗暗加在用户身上的隐藏标签 ,嗯类似于 隐藏分 , 系统都觉得你菜之类的。明签便于让用户搜索,提供更加精准的搜索,找到想要的信息,那暗签的存在则是为了更好的提高用户的体验,根据历史行为和明签计算重新分批分配用户所属组优化体验,定制广告,好吧就是更魔鬼一些,让用户感受到开发者的温度。数据本来是冰冷的,作为开发者更能体会到产品的趣味性,我们会更注重体验,这是不得不承认的事实,比如定位服务错误,我多付的两块共享单车钱,比如电影院系统故障,充值成功但会员因为系统原因没到账,我们是开发者更能体会到有些有些产品的无趣性,暗签的使用则更能提高产品的温度。当然也会带来更多的收益。
产生的问题
以上示例代码中,每个用户都会计算一遍相似度,然后进行推算,实在是不算靠谱,可以通过缓存的形式去缓解压力,但是难道数据量上去了也这么办?
但是想到的解决办法是 , 统计用户活跃状态,对活跃用户数据进行提前缓存,缓存中放提前算好的相似度匹配最高的前 100 条 id 当传来分页参数的时候先去缓存中获取准备好的 id 然后查询 , 当超过这些数据后在进行相似度匹配计算查询结果分页。
当然有的时候不需要那么的精确,因为我看到了这个
K-Means聚类算法: 用于将数据分成K个簇,相似的数据点被归为同一簇。
刷抖音会发现,有的时候我和我的损友其实刷到的东西都蛮像的,那是否意味着所有人的推荐都一样,但是发现有些其他人的推荐与我们相差甚远,答案就产生了,我们不需要去计算所有的相似度只要分类分好了,到时候这些人互相推荐就好了,如果涉及到个用户推荐资源也是一样,用户分类,打上一个标签,对应一份计算好的资源列表,问题解决,当然具体实现还是蛮复杂的, 需要设计好标签,标签计算,如果数据量更大的话还得使用大数据 ,掌握一些大数据相关的知识。
比如说有几亿个商品,难道要查出来所有的商品? 难道要对所有的数据计算一遍相似度?人数据推荐流程:
检索 =>召回 =>粗排 =>精排 =>重排序等等
检索: 尽可能多地查符合要求的数据 (比如按记录查)
召回: 查询可能要用到的数据 (不做运算)
粗排: 粗略排序,简单地运算 (运算相对轻量)
精排: 精细排序,确定固定排位
一些其他的点
性能计算, 算时间,实战测试不同的数据量对性能的影响
更好的缓存使用策略,节省空间的情况下实现更好的用户体验(比如对计算出来的推荐缓存,换成分簇的算法,或者说定时新建删除,临时表维护用户推荐关系
标签变动用户增长)缓存的提升性能极限
如何关闭批量查询时候的日志,很影响性能百万数据查询的情况下有 60% 左右的性能浪费在打日志上
总结
标签化资源是很重要的一点能力,它很通用,他更符合人的习惯,可以更好的利用信息,无论是提高用户检索,使用体验,还是在创收这方面都是很重要的,但是完整的标签化体系的建立不是一朝一夕就能搞定的了的,不过一套标签体系的建立真的就是一招鲜吃遍天的情况,当然在有些领域可能并不重要,但实际上有些时候对数据进行分类也是标签化思想,举个例子,商品中的促销,热卖,优品,样本中的优良可差,还有最常见的状态字段,这些都是通过给数据打标签来解决实际问题,如果换做今天的问题的话就是他们都是一个 tag 表中的一条数据,给它加上一个使用场景就好了(多对多关系);
好的标签体系是不断的总结设计出来的,可以参考前人的实现一点点归纳成熟的标签体系搭建之后是可以多套系统复用的,所以我觉得他是编程中的通用能力。
其中还涉及到新建标签的权限,比如 CSDN 只能让有影响力的博主创建标签,抖音随便创建标签,这些都是要搭建一套好的标签体系要考虑进去的事情。
本文来自互联网用户投稿,文章观点仅代表作者本人,不代表本站立场,不承担相关法律责任。如若转载,请注明出处。 如若内容造成侵权/违法违规/事实不符,请点击【内容举报】进行投诉反馈!

