2025-12-27 21:14:53
腾讯云EO可以查看一些指标信息,但是更加详细的信息需要我们下载离线日志自行分析。
腾讯云会将日志打包为.gz格式,解压后文件会包含多行,每一行都是一个JSON格式的数据,对应一条EO的请求日志,日志格式可以参考腾讯云文档。
我们可以批量获取最近一个月的日志下载链接

之后复制所有链接并保存到urls.txt文件中。
我们参考官方文档使用docker来启动集群,首先下载.env和docker-compose.yml,之后在.env文件中设置es和kibana的密码都是123456,然后设置STACK_VERSION=9.2.3。考虑到数据量比较大,可以提高容器的内存大小,我这里设置了一台8G。
1 |
# Password for the 'elastic' user (at least 6 characters) |
设置好了之后使用命令docker-compose up -d启动ES集群。
之后可以通过http://127.0.0.1:5601访问kibana,用户名elastic,密码123456。
使用如下的代码下载解析日志,并保存到ES中
1 |
import gzip |
执行如上代码,就能够下载日志并保存到ES了(这会花费比较多的时间,我这里花费了100多分钟)。
数据索引完毕之后,我们可以查看索引信息
1 |
~ curl 'https://127.0.0.1:9200/eo_logs/_count' --header 'Authorization: Basic ZWxhc3RpYzo9dk5Cc0QwSTNZRWFPa2RoZFFhZg==' -k |
可以看到一共索引了3000多万条数据,我们还可以查看索引的mapping和详细信息如下
1 |
{ |
具体每个字段的含义如下
| 字段名 | 含义 | 说明 |
|---|---|---|
| ClientIP | 客户端 IP | 访问 EdgeOne 边缘节点的真实用户 IP |
| ClientISP | 客户端运营商 | 用户网络所属运营商,如电信、联通、移动 |
| ClientRegion | 客户端地区 | 用户所在国家或地区 |
| ClientState | 客户端省份/州 | 用户所在省份或州级行政区 |
| ContentID | 内容标识 | EO 内部用于标识访问资源的唯一 ID |
| EdgeCacheStatus | 缓存状态 | 边缘节点缓存命中情况:Hit / Miss / RefreshHit / Bypass |
| EdgeFunctionSubrequest | 子请求数量 | 边缘函数触发的内部子请求次数 |
| EdgeInternalTime | 内部处理耗时 | 边缘节点内部处理请求所消耗的时间(毫秒) |
| EdgeResponseBodyBytes | 响应体大小 | 返回给客户端的响应 Body 字节数 |
| EdgeResponseBytes | 响应总大小 | 返回给客户端的总字节数(Header + Body) |
| EdgeResponseStatusCode | 响应状态码 | 边缘节点返回的 HTTP 状态码 |
| EdgeResponseTime | 总响应耗时 | 从边缘节点接收请求到完成响应的总耗时(毫秒) |
| EdgeServerID | 边缘节点 ID | 实际处理请求的 EdgeOne 节点标识 |
| EdgeServerIP | 边缘节点 IP | 实际处理请求的边缘节点 IP 地址 |
| ParentRequestID | 父请求 ID | 关联内部转发或子请求的父级请求标识 |
| RemotePort | 客户端端口 | 客户端发起连接时使用的端口 |
| RequestBytes | 请求大小 | 客户端请求报文大小(字节) |
| RequestHost | 请求域名 | 客户端请求的 Host 域名 |
| RequestID | 请求 ID | EdgeOne 为请求生成的唯一标识 |
| RequestMethod | 请求方法 | HTTP 请求方法,如 GET、POST |
| RequestProtocol | 请求协议 | 使用的 HTTP 协议版本(HTTP/1.1、HTTP/2、HTTP/3) |
| RequestRange | Range 请求 | 请求头中的 Range 字段,用于分段或断点下载 |
| RequestReferer | 来源页面 | 请求头中的 Referer 信息 |
| RequestStatus | 请求状态 | EdgeOne 定义的请求处理状态 |
| RequestTime | 请求时间 | 请求到达 EdgeOne 的时间 |
| RequestUA | User-Agent | 客户端 User-Agent 信息 |
| RequestUrl | 请求路径 | 请求的 URL 路径(不包含查询参数) |
| RequestUrlQueryString | 查询参数 | 请求 URL 中的 Query String |
| _import_time | 导入时间 | 日志被导入 Elasticsearch 的时间 |
| _source_file | 日志来源 | 生成该日志的原始文件或对象标识 |
然后我们想看指定域名的请求耗时情况(从EdgeOne接收到客户端发起的请求开始,到响应给客户端最后一个字节,整个过程的耗时,对应字段EdgeResponseTime),可以使用如下DSL
1 |
POST /eo_logs/_search |
得到结果如下
1 |
{ |
我们重点关注百分比:
| 百分位 | 含义 | 解读 |
|---|---|---|
| p50 | 5 ms | 一半请求 5ms 内完成(极快) |
| p90 | 25 ms | 90% 的请求很健康 |
| p95 | 74 ms | 95% 的请求 < 100ms(优秀) |
| p99 | 594 ms | 1% 请求接近 / 超过 0.5s |
可以看到这个域名的请求速度还是很快的。
此外,我们还可以分析哪些资源的下载比较慢
1 |
POST /eo_logs/_search |
我们可以针对上面查询到的慢速URL去做特定的优化和缓存预热。只是,上面的这个DSL不够严谨,因为单纯使用请求时间来判断速度快慢是不足够的,请求时间也会受到资源大小的影响。因此,我们使用资源的大小比上请求耗时,这个就代表这个资源的下载速度,之后我们从小到大排序,就可以知道哪些资源可能会下载比较慢了。具体DSL如下
1 |
POST /eo_logs/_search |
根据上面的查询结果,我们就可以知道哪些资源的下载速度可能比较慢,之后就可以针对这些URL对应的资源去做专门的优化了。
2025-08-23 00:57:47
XXL-JOB是一个分布式任务调度平台,它可以很方便的实现分布式的任务调度。
首先我们下载XXL-JOB的源码
git clone [email protected]:xuxueli/xxl-job.git
之后我们根据doc/db/tables_xxl_job.sql中的sql创建对应的数据库和表。
在启动XXL-JOB之前,有些配置需要修改:
xxl-job-admin/src/main/resources/logback.xml中property的value需要设置为xxl-job/xxl-job-admin/data/applogs/xxl-job/xxl-job-admin.log,否则可能会因为文件夹不存在而启动不了xxl-job-admin/src/main/resources/application.properties中数据库的地址、用户名和密码,以及accessToken等等需要修改为自定义的配置值修改完配置,就可以启动服务了
mvn clean packagecd xxl-job-admin/targetjava -jar xxl-job-admin-3.1.2-SNAPSHOT.jar
这样服务就启动成功了,之后我们可以访问http://127.0.0.1:8080/xxl-job-admin/进入控制台
我们基于SpringBoot来构建业务服务,首先我们添加依赖
<dependency> <groupId>com.xuxueli</groupId> <artifactId>xxl-job-core</artifactId> <version>3.1.1</version></dependency>
之后创建一个XXL-JOB的配置bean
1 |
|
配置好了之后我们就可以新建一个任务了
1 |
|
添加了配置和任务之后,我们就可以启动服务了。
在控制台中,我们选择执行器管理,之后新增一个执行器。其中,AppName就是上面配置的应用名,如上就是test,名称就是这个管理器的自定义名称,注册方式选择自动注册即可。
创建好执行器,我们就可以新增任务了。在任务管理中,我们新增一个执行器为刚刚新增那个执行器的任务。运行模式选择BEAN,JobHandler设置为@XxlJob注解中的值demoJobHandler。之后当任务执行的条件达到时,demoJobHandler方法就会执行了。
2025-07-24 23:22:56
NGINX的auth_request模块提供了一种统一的认证机制,可以在NGINX层面进行JWT鉴权,而不需要在每个后端服务中重复实现认证逻辑。
首先我们定义一下nginx的配置,它的配置如下
1 |
server { |
有了这个nginx的配置之后,我们就可以实现鉴权的逻辑了,具体逻辑如下
1 |
import jwt |
启动nginx和如上python服务,之后我们使用如下payload和header以及密钥生成token
payload
{ "alg": "HS256", "typ": "JWT"}
header
{ "user_id": 656670838050885}
密钥
a-string-secret-at-least-256-bits-long
生成得到token
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyX2lkIjo2NTY2NzA4MzgwNTA4ODV9.caQ6cp-BA-OMxXu4zTUjV0OiZo1iygvdi7GPQNjNVHM
之后我们就可以使用token进行测试了,具体测试结果如下
~ AUTH_HEADER="Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyX2lkIjo2NTY2NzA4MzgwNTA4ODV9.caQ6cp-BA-OMxXu4zTUjV0OiZo1iygvdi7GPQNjNVHM"~ curl -H "$AUTH_HEADER" http://localhost:8965/api/hello{"message":"hello from auth","user_id":"656670838050885"}~ curl -H "$AUTH_HEADER" http://localhost:8965/api/public/hello{"msg":"hello without auth"}~ curl -H "$AUTH_HEADER" http://localhost:8965/api/login<!doctype html><html lang=en><title>404 Not Found</title><h1>Not Found</h1><p>The requested URL was not found on the server. If you entered the URL manually please check your spelling and try again.</p>~ curl http://localhost:8965/api/public/hello{"msg":"hello without auth"}~ curl http://localhost:8965/api/helloUnauthorized%
如上我们正确设置了Authorization之后就可以正常访问需要鉴权的接口了,但是去掉了Authorization之后需要鉴权的接口就会返回Unauthorized。此外还可以看到,不需要鉴权的接口,即使不添加鉴权配置也是可以正常访问的。
2025-07-11 19:23:07
APISIX支持获取jwt的信息,并且将这个信息进行解码并转发给后端服务。
首先我们根据官方脚本来启动APISIX服务
~ curl -sL "https://run.api7.ai/apisix/quickstart" | shDestroying existing apisix-quickstart container, if any.Installing APISIX with the quickstart options.Creating bridge network apisix-quickstart-net.77e35df073894075ad77facd9d1c7d2a35b280213732c1b631052caede079bab✔ network apisix-quickstart-net createdStarting the container etcd-quickstart.d123605c8b7658b130be97e5f44e7a160aa85858db008032ecf594266225e342✔ etcd is listening on etcd-quickstart:2379Starting the container apisix-quickstart.38434806c63b3a72f53fb6ad849cb4c11781eebaff79c8db04510226593fcf46⚠ WARNING: The Admin API key is currently disabled. You should turn on admin_key_required and set a strong Admin API key in production for security.✔ APISIX is ready!
启动了APISIX之后,我们首先创建一个插件配置。在这个插件中我们定义了一个Lua方法,这个方法的目的是从请求的header中获取authorization信息,并进行解码,之后将解码的信息放到HTTP header中传给后端
curl --location --request PUT 'http://127.0.0.1:9180/apisix/admin/plugin_configs/1001' \--header 'Content-Type: application/json' \--header 'Accept: */*' \--header 'Host: 127.0.0.1:9180' \--header 'Connection: keep-alive' \--data-raw '{ "plugins": { "serverless-pre-function": { "phase": "access", "functions": [ "return function(_, ctx) local core = require(\"apisix.core\") local jwt = require(\"resty.jwt\") local auth_header = ctx.var.http_authorization if not auth_header then return end local token = auth_header:match(\"Bearer%s+(.+)\") if not token then return end local obj = jwt:load_jwt(token) if obj and obj.valid and obj.payload then if obj.payload.user_id then core.request.set_header(\"X-User-Id\", obj.payload.user_id) end if obj.payload.role then core.request.set_header(\"X-User-Role\", obj.payload.role) end end end" ] } }}'
如上的fucntions属性中添加了一个Lua方法,格式化之后的Lua代码如下
1 |
return function(_, ctx) |
这段代码实现了如下几个功能:
创建了这个插件之后,我们再新建一个consumer。在APISIX中,consumer代表了一类客户端,比如APP。我们可以针对这类客户端添加一些配置,多种不同类型的客户端(比如APP、网页、开放平台,等等)可以分别设置成不同的consumer以方便管理
curl --location --request PUT 'http://127.0.0.1:9180/apisix/admin/consumers/app' \--header 'Content-Type: application/json' \--header 'Accept: */*' \--header 'Host: 127.0.0.1:9180' \--header 'Connection: keep-alive' \--data-raw '{ "username": "app", "plugins": { "jwt-auth": { "key": "app-key", "secret": "a-string-secret-at-least-256-bits-long", "algorithm": "HS256" } }}'
如上添加了一个名为app的consumer,它的key是app-key,加密方式是HS256,密钥是a-string-secret-at-least-256-bits-long。有了解析插件和consumer之后,我们就可以创建路由了。
如下请求会创建一个ID为1的路由,使用了ID为1001插件,并且添加了jwt-auth的配置,路由的后端是https://httpbin.org,这个网站会把我们请求的信息返回给我们。
curl --location --request PUT 'http://127.0.0.1:9180/apisix/admin/routes/1' \--header 'Content-Type: application/json' \--header 'Accept: */*' \--header 'Host: 127.0.0.1:9180' \--header 'Connection: keep-alive' \--data-raw '{ "uri": "/headers", "plugin_config_id": 1001, "plugins": { "jwt-auth": {} }, "upstream": { "type": "roundrobin", "nodes": { "httpbin.org:80": 1 } }}'
在创建好了plugin_config、consumer和route之后,我们就可以测试请求了。首先我们构建如下payload
1 |
{ |
这个payload包含了user_id和role两个业务属性,exp代表这个jwt的过期时间戳,key是APISIX用于识别匹配哪个consumer的,这里我们选择匹配app-key这个consumer。之后我们将该payload和密钥a-string-secret-at-least-256-bits-long一起在https://jwt.io/进行编码,得到编码jwt信息如下
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJrZXkiOiJhcHAta2V5IiwidXNlcl9pZCI6MTAwMDAxLCJyb2xlIjoiYWRtaW4iLCJleHAiOjE5MDAwMDAwMDB9.qG7PNPz2XlatmjrhNW_xf6SmI8T9JSIx2lJVJcAox0I
之后我们执行HTTP请求,将这个jwt放到Authorization header中
curl --location --request GET 'http://127.0.0.1:9080/headers' \--header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJrZXkiOiJhcHAta2V5IiwidXNlcl9pZCI6MTAwMDAxLCJyb2xlIjoiYWRtaW4iLCJleHAiOjE5MDAwMDAwMDB9.qG7PNPz2XlatmjrhNW_xf6SmI8T9JSIx2lJVJcAox0I' \--header 'Accept: */*' \--header 'Host: httpbin.org:80' \--header 'Connection: keep-alive'
请求得到的响应如下,可以看到user_id和role属性已经成功的传给后端服务了
1 |
{ |
2025-06-22 18:54:59
从某种意义上说,推荐系统和搜索引擎对于用户来说是两个互补的工具。搜索引擎满足了用户有明确目的时的主动查找需求,而推荐系统能够在用户没有明确目的的时候帮助他们发现感兴趣的新内容。
基于用户行为分析的推荐算法是个性化推荐系统的重要算法,学术界一般将这种类型的算法称为协同过滤(Collaborative filtering)算法。顾名思义,协同过滤就是指用户可以齐心协力,通过不断地和网站互动,使自己的推荐列表能够不断过滤掉自己不感兴趣的物品,从而越来越满足自己的需求。
用户行为在个性化推荐系统中一般分两种——显性反馈行为(explicit feedback)和隐性反馈行为(implicit feedback)。显示反馈行为是用户主动做的,比如给视频点赞、给书籍打分等等;隐式反馈行为的代表就是用户浏览页面,这种行为显示出来的用户偏好不是那么明显,但是数据量更大。
计算两个用户的兴趣相似程度:给定用户u和用户v,N(u)表示用户u曾经有过正反馈的物品集合,N(v)表示用户v曾经有过正反馈的物品集合。可以使用Jaccard公式计算两个用户的兴趣相似程度
或者使用余弦相似公式计算相似程度
以余弦相似公式为例,假设有用户ABCD,物品abcde,用户喜欢的物品如下
| 用户 | 物品 a | 物品 b | 物品 c | 物品 d | 物品 e |
|---|---|---|---|---|---|
| A | ☑️ | ☑️ | ☑️ | ||
| B | ☑️ | ☑️ | |||
| C | ☑️ | ☑️ | |||
| D | ☑️ | ☑️ | ☑️ |
那么我们可以得到用户A和BCD的相似度
具体计算过程以AD的相似度计算为例
{d},|{d}| = 1,所以分子为13 x 3 = 9,开根号为31 / 3
以上逻辑可以用代码进行实现:
1 |
def similarity(users): |
执行后得到结果如下
A: {"B": 0.4082482904638631, "C": 0.4082482904638631, "D": 0.3333333333333333}B: {"A": 0.4082482904638631, "C": 0.0, "D": 0.4082482904638631}C: {"A": 0.4082482904638631, "B": 0.0, "D": 0.4082482904638631}D: {"A": 0.3333333333333333, "B": 0.4082482904638631, "C": 0.4082482904638631}
据此我们就可以得到各个用户之间的兴趣相似度了。有了用户兴趣的相似度之后,我们可以给用户推荐和他兴趣最相似的K个用户喜欢的物品。我们可以使用如下公式计算用户u对物品i的感兴趣程度
其中,S(u, K)包含和用户u兴趣最接近的K个用户,N(i)是对物品i有过行为的用户集合,wuv是用户u和用户v的兴趣相似度,rvi代表用户v对物品i的兴趣,因为使用的是单一行为的隐反馈数据,所以所有的rvi=1。
具体的逻辑实现如下:
1 |
def recommend(user, users, w, k): |
通过这个代码我们就可以计算得到指定用户的物品推荐程度了。完整的代码如下:
1 |
import json |
执行代码得到的结果如下
A: {"B": 0.4082482904638631, "C": 0.4082482904638631, "D": 0.3333333333333333}B: {"A": 0.4082482904638631, "C": 0.0, "D": 0.4082482904638631}C: {"A": 0.4082482904638631, "B": 0.0, "D": 0.4082482904638631}D: {"A": 0.3333333333333333, "B": 0.4082482904638631, "C": 0.4082482904638631}d: 0.8164965809277261a: 0.4082482904638631c: 0.4082482904638631
由上面的结果我们可以知道,针对用户C,最推荐的物品是物品d。
根据上面的例子我们已经简单了解了基于用户的协同过滤算法,不过这种算法存在问题,主要是
因此,在实际的使用中,更常见的是基于物品的协同过滤算法
为了挖掘长尾信息,避免热门物品对推荐产生影响,减小二八定律的出现。可以用如下公式计算物品之间的相似度
分子是同时喜欢物品i和物品j的用户数,分母是喜欢两个物品用户数的并集。为了减小计算量,我们可以构建一个矩阵来存储某个用户喜欢的物品集合。
举个例子,比如用户A喜欢物品 {a, b, d},那我们可以构建如下矩阵
| a | b | c | d | e |----|-----|-----|-----|-----|-----|a | 0 | 1 | 0 | 1 | 0 |b | 1 | 0 | 0 | 1 | 0 |c | 0 | 0 | 0 | 0 | 0 |d | 1 | 1 | 0 | 0 | 0 |e | 0 | 0 | 0 | 0 | 0 |
因为a、b、d可以组成ab、ad、bd,所以将矩阵中的对应位置都填上1。这是一个用户的物品信息,对于多个用户,只需要把这些矩阵相加即可。例如有5个用户,他们的物品信息和生成的对应物品矩阵如下
用户 1: {a, c, d}
| a | b | c | d | e |----|-----|-----|-----|-----|-----|a | 0 | 0 | 1 | 1 | 0 |b | 0 | 0 | 0 | 0 | 0 |c | 1 | 0 | 0 | 1 | 0 |d | 1 | 0 | 1 | 0 | 0 |e | 0 | 0 | 0 | 0 | 0 |
用户 2: {b, c, e}
| a | b | c | d | e |----|-----|-----|-----|-----|-----|a | 0 | 0 | 0 | 0 | 0 |b | 0 | 0 | 1 | 0 | 1 |c | 0 | 1 | 0 | 0 | 1 |d | 0 | 0 | 0 | 0 | 0 |e | 0 | 1 | 1 | 0 | 0 |
用户 3: {a, d, e}
| a | b | c | d | e |----|-----|-----|-----|-----|-----|a | 0 | 0 | 0 | 1 | 1 |b | 0 | 0 | 0 | 0 | 0 |c | 0 | 0 | 0 | 0 | 0 |d | 1 | 0 | 0 | 0 | 1 |e | 1 | 0 | 0 | 1 | 0 |
用户 4: {b, d}
| a | b | c | d | e |----|-----|-----|-----|-----|-----|a | 0 | 0 | 0 | 0 | 0 |b | 0 | 0 | 0 | 1 | 0 |c | 0 | 0 | 0 | 0 | 0 |d | 0 | 1 | 0 | 0 | 0 |e | 0 | 0 | 0 | 0 | 0 |
用户 5: {a, b, c, e}
| a | b | c | d | e |----|-----|-----|-----|-----|-----|a | 0 | 1 | 1 | 0 | 1 |b | 1 | 0 | 1 | 0 | 1 |c | 1 | 1 | 0 | 0 | 1 |d | 0 | 0 | 0 | 0 | 0 |e | 1 | 1 | 1 | 0 | 0 |
将这5个用户的物品信息相加,得到矩阵
| a | b | c | d | e |----|-----|-----|-----|-----|-----|a | 0 | 1 | 2 | 3 | 2 |b | 1 | 0 | 3 | 2 | 3 |c | 2 | 3 | 0 | 2 | 3 |d | 3 | 2 | 2 | 0 | 2 |e | 2 | 3 | 3 | 2 | 0 |
在这个矩阵中值越高,代表物品的相关度越高。接下来我们将这个规则用代码进行实现
1 |
number = 'number' |
如上我们先计算每个物品各自被用户喜欢的次数,再计算每个物品和其它物品同时被某个用户喜欢的次数,之后根据物品相似度公式即可计算出物品之间的相关性。为了简单起见,如上代码只使用了一个字典变量,物品自己被喜欢的次数被保存在key为number的字段中,物品和其它物品同时被喜欢的次数则保存在key为其它物品ID的字段中。
有了如上逻辑之后,我们就可以计算物品相似度了,假设有用户如下
{ 'A': {'a', 'b', 'd'}, 'B': {'a', 'c'}, 'C': {'b', 'e', 'a'}, 'D': {'c', 'd', 'e'}}
计算得到的物品相似度
b: {'a': 0.8164965809277261, 'd': 0.5, 'e': 0.5}a: {'b': 0.8164965809277261, 'd': 0.4082482904638631, 'c': 0.4082482904638631, 'e': 0.4082482904638631}d: {'b': 0.5, 'c': 0.5, 'e': 0.5, 'a': 0.4082482904638631}c: {'e': 0.5, 'd': 0.5, 'a': 0.4082482904638631}e: {'b': 0.5, 'c': 0.5, 'd': 0.5, 'a': 0.4082482904638631}
可以看到物品a和物品b的相关度是最高的。在得到物品的相关度之后,我们可以使用如下公式计算用户u对一个物品j的兴趣
N(u)是用户喜欢的物品集合,S(j, K)是物品j最相似的K个物品的集合,wji是物品j和i的相似度,rui是用户对物品i的兴趣,可令rui为1。我们可以把这个逻辑使用代码进行实现
1 |
def recommend(interacted_items: Union[set, dict], w, k): |
有了计算用户和物品相关度的代码,我们就可以把逻辑结合起来,实现向用户推荐物品了。完整的代码实现如下
1 |
import math |
以上代码的执行结果如下,可见用户B和物品d的相关度最高
b: {'a': 0.8164965809277261, 'd': 0.5, 'e': 0.5}d: {'b': 0.5, 'c': 0.5, 'e': 0.5, 'a': 0.4082482904638631}a: {'b': 0.8164965809277261, 'd': 0.4082482904638631, 'c': 0.4082482904638631, 'e': 0.4082482904638631}c: {'d': 0.5, 'e': 0.5, 'a': 0.4082482904638631}e: {'b': 0.5, 'c': 0.5, 'd': 0.5, 'a': 0.4082482904638631}d: 0.9082482904638631b: 0.8164965809277261e: 0.5
基于物品的推荐在工程中使用的比基于用户的推荐要多,因为UserCF(User Collaborative Filtering)的推荐更社会化,反映了用户所在的小型兴趣群体中物品的热门程度,而ItemCF(Item Collaborative Filtering)的推荐更加个性化,反映了用户自己的兴趣传承。
隐语义模型核心思想是通过隐含特征(latent factor)联系用户兴趣和物品,它可以通过对数据进行分类来实现推荐。这种基于用户对数据的兴趣分类的方式,需要解决如下三个问题:
隐含语义分析技术(latent variable analysis)采取基于用户行为统计的自动聚类,来实现数据自动分类。
一个推荐系统好不好,可以从用户满意度、预测准确度、覆盖率、多样性、新颖性、惊喜度、信任度、实时性、健壮性、商业目标等多个角度来进行评测
我们可以使用TopN推荐的方式来计算准确度,TopN的准确度一般通过准确率(precision)/召回率(recall)来进行度量。令R(u)是根据用户在训练集上的行为给用户作出的推荐列表,而T(u)是用户在测试集上的行为列表。那么,推荐结果的召回率定义为:
推荐结果的准确率定义为:
简单来说,R(u)代表系统推荐给用户u的Top-N列表(预测值),T(u)代表用户实际喜欢或点击过的项目(真实值),召回率和准确率公式的分子都是同时存在于推荐列表和用户喜欢列表的物品数。召回率的分母是用户喜欢的物品数,召回率是看系统有没有把用户喜欢的物品推荐出来。准确率的分母是系统推荐的物品总数,目的是看推荐有多少是对的。
| 指标 | 含义 | 关注点 |
|---|---|---|
| Recall | 你真正喜欢的内容中,被系统找出来了多少 | 不漏掉好东西 |
| Precision | 系统推荐的内容中,有多少真的是你喜欢的 | 不乱推荐垃圾 |
我们可以把召回率和准确率的计算通过如下代码实现
1 |
def precision_recall(test_data, train_data, n, recommend_func): |
2025-06-19 01:44:52
Hooked: How to Build Habit-Forming Products
如何卖出更多的产品:产能 -> 营销/渠道 -> 产品设计
上瘾如何设计产品:触发 -> 行动 -> 多变的酬赏 -> 投入
习惯是大脑借以掌握复杂举动的途径之一。神经系统科学家指出,人脑中存在一个负责无意识行为的基底神经节,那些无意中产生的条件反射会以习惯的形式存储在基底神经节中,从而使人们腾出精力来关注其他的事物。当大脑试图走捷径而不再主动思考接下来该做些什么时,习惯就养成了。为解决当下面临的问题,大脑会在极短的时间内从行为存储库里提取出相宜的对策。
(就是基底核,有点像缓存的作用)
我们所要描述的体验更接近于“痒”,它是潜伏于我们内心的一种渴求,当这种渴求得不到满足时,不适感就会出现。那些让我们养成某种习惯的产品正好可以缓解这种不适感。比起听之任之的做法,利用技术或产品来”挠痒痒”能够更快地满足我们的渴求。一旦我们对某种技术或产品产生依赖,那它就是唯一的灵丹妙药了。
福格行为模型可以用公式来呈现,即B=MAT。B代表行为,M代表动机,A代表能力,T代表触发。要想使人们完成特定的行为,动机、能力、触发这三样缺一不可。1否则,人们将无法跨过”行动线”,也就是说,不会实施某种行为。
多变的酬赏主要表现为三种形式:社交酬赏,猎物酬赏,自我酬赏
沉没成本:通过用户对产品的投入程度,留住用户
总体评价:很薄的一本书,有部分的观点有参考意义,但是大部分的论调都是老生常谈。大多数的观点在很多心理学的书籍里面已经讲过了,本书主要是讲怎么依赖于这些原理来进行实操,有一定的参考意义。