我是兰瓶Coding,一枚刚踏入鸿蒙领域的转型小白,原是移动开发中级,如下是我学习笔记《零基础学鸿蒙》,若对你所有帮助,还请不吝啬的给个大大的赞~

开篇

先说句大实话:商店首页那十几个格子,决定了无数开发者的“生死荣枯”。你以为是“编辑精选”?其实大部分是数据与策略搏斗后的短兵相接:冷启动如何破、质量如何辨、作弊如何怼、个性化怎么稳、分发公平怎么平衡……我们今天把“鸿蒙应用商店(或 OpenHarmony 生态内的应用分发体系)”里的分发策略推荐算法从工程视角掰开揉碎:指标体系、召回架构、排序模型、探索—利用权衡、反作弊与风控、延迟反馈与 A/B 评估、灰度放量与回滚,外加可跑的代码小样。整篇不空谈大道理,更多是“落地脏活”。😎

前言:为什么“下载量好看≠生态健康”?

商店不是简单的“把热门堆上去”。过度追逐安装量,会把生态推向短行为刺激(诱导下载、虚假评分、标题党),长远看留存崩卸载飙投诉多。一个靠谱的分发系统,至少要多目标平衡

  • 用户目标:体验、相关性、隐私与多样性(别老给我一种类型)。
  • 开发者目标:曝光公平、冷启动加速、优质应用复利。
  • 平台目标:长期留存(LTV)、转化(CTR/CVR/Install)、负反馈(差评/卸载/举报)最小化、合规可控。

你可能会问:“难道不能一把梭最热门就完了?”
抱歉,真不行。热门≠适配你;而且“热门逻辑”最容易被灰产拿捏。

一张图先定调:分发推荐系统的骨架

[端侧行为]   [日志/曝光]    [特征工程]     [多通道召回]        [粗排]         [精排]        [重排与约束]
  点击、安装 → PV/UV、曝光 → 用户/应用画像 → CF/Embedding/规则 → GBDT/W&DDCN/Transformer → 去重/多样性/冷启加权
                                                                                             ↓
                                                                                    [风控与合规拦截][灰度 & A/B 放量]
  • 召回:把“可能有用”的集合拉回来(10^5 → 10^3)
  • 粗排:快速打分筛到几百个(轻量模型)
  • 精排:重模型细抠(深度网络/多目标)
  • 重排:多样性、业务约束与冷启动加权
  • 风控:刷量、诱导、违规内容拦截
  • 评估:离线/在线、灰度/回滚

指标到底怎么定?别只盯 CTR!

核心线上指标(按“即时→中期→长期”分层):

  • 即时:CTR(点击率)、CVR(到达详情→安装转化)、ATC(加装车/愿望单)
  • 中期:次日/7日留存卸载率评分分布与文本情感
  • 长期:LTV(按活跃/付费/订阅)用户多样性暴露度应用长尾曝光率

一个简化的多目标优化函数(线上推理时使用):

score = w 1 ⋅ p ^ ( click ) + w 2 ⋅ p ^ ( install ) + w 3 ⋅ p ^ ( d7_ret ) − w 4 ⋅ p ^ ( uninstall ) \text{score} = w_1 \cdot \hat{p}(\text{click}) + w_2 \cdot \hat{p}(\text{install}) + w_3 \cdot \hat{p}(\text{d7\_ret}) - w_4 \cdot \hat{p}(\text{uninstall}) score=w1p^(click)+w2p^(install)+w3p^(d7_ret)w4p^(uninstall)

  • 权重 w i w_i wi 动态按人群/场景(新客/老客、家庭/学生、设备形态)与策略周期调整。
  • 约束:曝光公平(每开发者/品类最低配额)、合规阈值、冷启动额外加权。

召回层:别把“王者”全塞进来

1) 协同过滤(CF)与相似应用召回

  • 基于用户的 CF:找“你像谁”,推荐“他们喜欢的”。
  • 基于物品的 CF:找“它像谁”,推荐“相似应用”。
  • 缺点:冷启动弱、稀疏性高;优点:解释性好。

2) 向量召回(Embedding + ANN)

  • 用行为序列/文本描述/图标视觉特征,训练应用向量用户向量,做近邻检索(如 HNSW/IVF)。
  • 适合跨语义发现(同功能不同关键词)。
一个最小可跑的向量召回示例(Python + FAISS)
import numpy as np, faiss

# 假设我们已有 apps 的 128 维向量
np.random.seed(0)
app_embeddings = np.random.randn(5000, 128).astype('float32')
index = faiss.index_factory(128, "IVF256,Flat")
index.train(app_embeddings)
index.add(app_embeddings)

# 查询:用户画像向量
user_vec = np.random.randn(1, 128).astype('float32')
D, I = index.search(user_vec, k=50)  # top-50 作为召回
print("Recall candidates:", I[0][:10])

3) 规则与运营通道

  • 必备:精品库、编辑精选、合规白名单、系统更新必推、安全修复优先。
  • 运营场景:节日活动、区域热点、设备专属(折叠屏/车机/手表)。

工程建议:召回池分层(向量/CF/规则/长尾),统一去重 + 标记召回通道,便于后续重排打多样性。

粗排:轻量模型,先把“坑货”挡在外面

目的:快速从 10^3 过滤到 10^2。
模型选择:GBDT(XGBoost/LightGBM)、LR(含交叉特征),追求算力性价比

LightGBM 粗排训练小样(Python)

import lightgbm as lgb
import pandas as pd
from sklearn.model_selection import train_test_split
from sklearn.metrics import roc_auc_score

df = pd.read_csv('sample_events.csv')  # user/app/context features + label_click
feats = [c for c in df.columns if c not in ('label_click', 'user_id', 'app_id')]
X_train, X_val, y_train, y_val = train_test_split(df[feats], df['label_click'], test_size=0.2, random_state=42)

train_set = lgb.Dataset(X_train, label=y_train)
val_set = lgb.Dataset(X_val, label=y_val)
params = dict(objective='binary', metric='auc', learning_rate=0.05, num_leaves=64, max_depth=-1)
model = lgb.train(params, train_set, num_boost_round=500, valid_sets=[val_set], early_stopping_rounds=50)
print("AUC:", roc_auc_score(y_val, (model.predict(X_val))))
model.save_model('l0_lgbm.txt')

精排:多目标深度模型,抠细节抠到发光

  • Wide & Deep:记忆(wide)+ 泛化(deep)
  • DCN/DeepFM:自动特征交叉
  • Transformer/序列模型:用户近期行为(序列建模)
  • 多任务学习:同时预测点击、安装、次日留存;共享底座,任务塔分头。

Keras 多任务精排示例(简化)

import tensorflow as tf
from tensorflow import keras as K

def build_mmoe(num_sparse, num_dense, vocab_size, k=4):
    # Embedding for sparse ids (e.g., app_id, category_id)
    inputs_sparse = [K.layers.Input(shape=(1,), dtype='int32', name=f'sparse_{i}') for i in range(num_sparse)]
    inputs_dense = [K.layers.Input(shape=(1,), dtype='float32', name=f'dense_{i}') for i in range(num_dense)]
    emb = [K.layers.Embedding(vocab_size, 16)(x) for x in inputs_sparse]
    emb = [K.layers.Flatten()(e) for e in emb]
    x = K.layers.Concatenate()(emb + inputs_dense)
    # MMoE experts
    experts = [K.layers.Dense(64, activation='relu')(x) for _ in range(k)]
    # Gates per task
    def gate(experts, name):
        g = K.layers.Dense(k, activation='softmax')(x)
        o = tf.add_n([experts[i] * tf.expand_dims(g[:, i], -1) for i in range(k)])
        o = K.layers.Dense(64, activation='relu')(o)
        return o
    ctr_tower = gate(experts, 'ctr')
    cvr_tower = gate(experts, 'cvr')
    d1r_tower = gate(experts, 'd1r')  # day-1 retention

    out_ctr = K.layers.Dense(1, activation='sigmoid', name='ctr')(ctr_tower)
    out_cvr = K.layers.Dense(1, activation='sigmoid', name='cvr')(cvr_tower)
    out_d1r = K.layers.Dense(1, activation='sigmoid', name='d1r')(d1r_tower)

    model = K.Model(inputs=inputs_sparse + inputs_dense, outputs=[out_ctr, out_cvr, out_d1r])
    model.compile(optimizer='adam',
                  loss={'ctr': 'binary_crossentropy', 'cvr': 'binary_crossentropy', 'd1r': 'binary_crossentropy'},
                  loss_weights={'ctr': 1.0, 'cvr': 2.0, 'd1r': 1.5})
    return model

model = build_mmoe(num_sparse=3, num_dense=10, vocab_size=200000)
model.summary()

线上打分:用前文那套线性组合或神经重排头生成最终分数,同时带入多样性罚项

重排:别让十个结果“长一个脸”

  • 多样性:基于类别/开发者/召回通道做 Maximal Marginal Relevance(MMR)
  • 冷启动加权:新应用在有效窗口期给曝光保底,同时受负反馈快速降权
  • 公平约束:同开发者上限、区域合法合规过滤、敏感类目限流
  • 布局策略:卡片样式与位置也影响点击(上行实验检验)

MMR 重排小样(Python)

import numpy as np

def mmr_rerank(items, sim, lambda_=0.7, K=20):
    # items: [(id, base_score, category, dev)]
    S = []
    C = items[:]
    while len(S) < min(K, len(items)):
        best, best_val = None, -1e9
        for i, (iid, s, cat, dev) in enumerate(C):
            diversity_penalty = 0.0
            for (sid, _, scat, sdev) in S:
                diversity_penalty = max(diversity_penalty, sim[iid, sid])
            val = lambda_ * s - (1 - lambda_) * diversity_penalty
            if val > best_val:
                best, best_val, idx = C[i], val, i
        S.append(best); C.pop(idx)
    return S

探索 vs. 利用:别把新应用扼杀在摇篮里

多臂赌博机(MAB):UCB / Thompson Sampling

  • 场景:冷启动期、活动位、频道头部位
  • 做法:给每个候选一个“臂”,按不确定性优先探索。
Thompson Sampling 简化(Beta-Bernoulli)
import numpy as np
rng = np.random.default_rng(0)

class TSArm:
    def __init__(self): self.a, self.b = 1, 1  # Beta(1,1)
    def sample(self): return rng.beta(self.a, self.b)
    def update(self, reward):  # reward∈{0,1}
      self.a += reward; self.b += 1 - reward

arms = [TSArm() for _ in range(10)]
for t in range(10000):
    i = np.argmax([a.sample() for a in arms])
    reward = (rng.random() < 0.05 + i*0.005)  # 模拟不同真实转化
    arms[i].update(int(reward))

工程建议:在频道/位置层用 MAB 控探索比例,而具体排序结果仍由精排模型决定。

反作弊与风控:没有风控,再强的模型也是“瓷娃娃”

  • 行为异常:点击/安装分布不合常理、设备指纹异常、短时突增、地理跳跃
  • 关联图谱:开发者账号、支付、证照、签名、机型/设备指纹的关系图(社交图挖团伙)
  • 内容合规:图文审核(识别违规素材)、隐私权限滥用(过度读通讯录等)
  • 诱导/刷量识别:AB 安装不一致、安装即卸、评分二峰化、评论模板化
  • 在线拦截:风控分降权/屏蔽 + 进入审核队列 + 反馈给开发者

延迟反馈 & 反事实评估:别被“当天数据”骗了

  • 延迟反馈:安装→使用→留存,至少跨 1~7 天,优化时要回溯更新样本权重。
  • 反事实评估(IPS/DR):因线上展示带来偏置,离线评估要用展示概率校正
  • 校准:Platt/Isotonic,对不同人群/位置做分数校准,减少“看上去很准”的错觉。

A/B 测试与灰度放量:先小后大,随时回滚

  • 分桶:用户维度 hash(设备ID/账号ID),避免跨实验污染
  • 统计检验:非正态就Bootstrap置信区间;多指标Holm控制误报
  • 灰度:1%→5%→20%→50%→100%,每步看业务与风控双指标
  • 回滚:指标恶化自动回退,保留实验流水线,方便复盘

端侧协同:鸿蒙端上也能“助攻推荐”

  • 端侧轻召回:本地最近使用/偏好品类的无网兜底
  • 端测特征:设备形态(折叠、平板、车机、手表)、系统版本、场景(车载/运动/儿童)
  • 预取与缓存:负载低谷期预拉一屏候选,秒开页面
  • 隐私保护:端上匿名化/差分隐私采样,不上报原始敏感字段

ArkTS 端侧预取示意(简化)

// 伪代码:进入商店首页时,先从本地缓存/预取服务拿到候选
@Entry
@Component
struct HomePage {
  @State apps: Array<AppItem> = [];

  aboutToAppear() {
    PrefetchService.get('home_top_k').then(list => {
      this.apps = list; // 即刻渲染
    });
    // 背景刷新:上报曝光,拉取最新重排结果
    setTimeout(async () => {
      const latest = await Api.fetchRanked('home');
      this.apps = mergeWithDiversity(this.apps, latest);
    }, 300);
  }
}

从 0 到 1 的可落地“PoC”清单(你可以照这个顺序撸)

  1. 数据打通:曝光/点击/安装/卸载/评分日志一致化,打埋点字典
  2. 特征仓:用户、应用、上下文特征 schema;在线/离线一致
  3. 多通道召回:CF + 向量(FAISS/HNSW)+ 规则
  4. 粗排模型:LightGBM 先把 AUC/分布站稳
  5. 精排多任务:CTR/CVR/D1 留存共训;蒸馏到轻模型便于线上
  6. 重排与约束:多样性、冷启动保底、开发者配额
  7. 风控:基础反作弊 + 合规拦截
  8. A/B 基建:分桶、统计、灰度、回滚
  9. 端侧协同:预取/兜底 + 私有化特征
  10. 指标看板:短中长期全链路,异常报警

一个“可运行的小闭环”示范(玩真的)

目标:给“首页 1 个卡位”做可复现的探索—利用闭环,冷启动新应用,但不劣化整体转化。

  1. 向量召回拉 200 个候选;
  2. 粗排 LightGBM → top 60;
  3. 精排多任务模型 → top 10;
  4. MAB 在这 10 个里为“首卡位”挑 1 个;
  5. MMR 重排保证列表多样性
  6. 产出日志(曝光/点击/安装),次日回灌训练集;
  7. 每天按App 分层调整冷启加权
  8. 灰度放量,指标不好自动回滚到旧策略。

真实世界的坑(踩过的都懂)

  • 日志不一致:曝光漏埋或时区乱,A/B 结论直接失真
  • 负反馈延迟:卸载/差评慢,短期“火”长期“塌”
  • 作弊绕过:只盯 CTR,不看“装即卸”,立刻被刷穿
  • 过拟合老用户:新用户体验差、生态创新被挤出
  • 多目标权重拍脑袋:建议贝叶斯优化离线网格 + 在线细调
  • 只做线上 A/B:离线校验弱,迭代成本高

小结:分发是系统工程,不是“1 个模型的胜利”

召回够广,排序够准,重排有度,风控兜底,评估可靠,灰度稳健——这是一个好商店的“六边形战士”。鸿蒙生态/应用商店要做的,不是盯某个单点炫技,而是把工程闭环数据治理做扎实。当你能优雅地拒绝“短期虚火”,才能迎来“长期真香”。

附:数据与特征 Schema(节选,落地即用)

  • user_profile: {uid, region, lang, device_type, age_bucket, ...}
  • app_profile: {app_id, cat, dev_id, size, permission_cnt, rating_avg, rating_text_emb, ...}
  • context: {slot_id, ts, layout, net, sys_ver, entry_scene}
  • actions: exposure, click, detail_view, install, uninstall, rating
  • 派生特征N天滑窗统计序列 Embedding端侧特征指纹(匿名)

送你三套“开箱就用”的策略配方

  1. 新用户冷启配方

    • 召回:规则(高质量白名单)+ 向量(文本/视觉)
    • 排序:GBDT + 新客特征增强
    • 重排:多样性↑、冷启权重↑
    • 放量:小流量 + 强风控
  2. 老用户个性化配方

    • 召回:序列 Embedding(近 30 天行为) + 相似应用
    • 排序:MMoE(CTR/CVR/留存)
    • 重排:去同质化 + 开发者上限
    • 放量:稳定流量 + 负反馈快速收敛
  3. 活动频道拉新配方

    • 召回:活动主题词 + 品类白名单 + 社区热度
    • 排序:轻量蒸馏模型(延迟敏感)
    • 重排:位置多样化 + 防刷阈值
    • 放量:峰值预热 + 限时回滚开关

一句“人话”压轴

好分发不是“把好东西堆前面”,而是“把对的人和对的应用在对的场景里放在一起”。 做到这点,下载量只是顺手的结果,留存和好评才是终点的掌声。😉

(未完待续)

Logo

讨论HarmonyOS开发技术,专注于API与组件、DevEco Studio、测试、元服务和应用上架分发等。

更多推荐