搜广推校招面经一百零三
乐信搜推算法
一、AUC的含义和计算
1.1. AUC = 正样本预测值 > 负样本预测值 的概率
def auc_formula_method(y_true, y_score):pos = []neg = []# 分离正负样本for yt, ys in zip(y_true, y_score):if yt == 1:pos.append(ys)else:neg.append(ys)total_pairs = len(pos) * len(neg)count = 0.0for p in pos:for n in neg:if p > n:count += 1elif p == n:count += 0.5return count / total_pairs
1.2. 基于正样本排名的公式法
AUC=正样本排名总和−m(m+1)2mn
\text{AUC} = \frac{\text{正样本排名总和} - \frac{m(m+1)}{2}}{mn}
AUC=mn正样本排名总和−2m(m+1)
其中:
- mmm:正样本个数
- nnn:负样本个数
- 正样本排名总和:对所有正样本的排序索引求和
- 排序按预测概率
y_score
从小到大进行
def auc_rank_method(y_true, y_score):# 1. 将标签和预测值打包并按预测值升序排序data = list(zip(y_true, y_score))data.sort(key=lambda x: x[1]) # 升序排列# 2. 为每个样本分配排名(从1开始)ranks = {i: rank + 1 for rank, i in enumerate(range(len(data)))}# 3. 计算正样本的排名总和rank_sum = 0m = 0 # 正样本个数for i, (label, score) in enumerate(data):if label == 1:rank_sum += ranks[i]m += 1n = len(y_true) - m # 负样本个数# 4. 公式auc = (rank_sum - m * (m + 1) / 2) / (m * n)return auc
1.3. 积分法(ROC曲线 + 梯形法则)
def auc_integration_method(y_true, y_score):data = list(zip(y_true, y_score))data.sort(key=lambda x: x[1], reverse=True)P = sum(y_true)N = len(y_true) - Ptpr_list = []fpr_list = []tp = 0fp = 0for i in range(len(data)):if data[i][0] == 1:tp += 1else:fp += 1tpr = tp / P if P != 0 else 0fpr = fp / N if N != 0 else 0tpr_list.append(tpr)fpr_list.append(fpr)# 计算曲线下面积(AUC)用梯形法auc = 0.0for i in range(1, len(tpr_list)):auc += (fpr_list[i] - fpr_list[i-1]) * (tpr_list[i] + tpr_list[i-1]) / 2return auc
二、为啥项目使用mape指标,还有那些回归任务的指标
2.1. MAPE(Mean Absolute Percentage Error)
MAPE=1n∑i=1n∣yi−y^iyi∣×100% \text{MAPE} = \frac{1}{n} \sum_{i=1}^{n} \left| \frac{y_i - \hat{y}_i}{y_i} \right| \times 100\% MAPE=n1i=1∑nyiyi−y^i×100%
- 表示预测误差相对真实值的百分比、单位无关(百分比)
- 对真实值接近 0 的样本非常敏感(注意不要除0)
2.2. 其他常见回归评价指标
2.2.1. MAE(平均绝对误差)
MAE=1n∑i=1n∣yi−y^i∣ \text{MAE} = \frac{1}{n} \sum_{i=1}^{n} \left| y_i - \hat{y}_i \right| MAE=n1i=1∑n∣yi−y^i∣
2.2.2. MSE(均方误差)
MSE=1n∑i=1n(yi−y^i)2 \text{MSE} = \frac{1}{n} \sum_{i=1}^{n} (y_i - \hat{y}_i)^2 MSE=n1i=1∑n(yi−y^i)2
2.2.3. RMSE(均方根误差)
RMSE=1n∑i=1n(yi−y^i)2 \text{RMSE} = \sqrt{ \frac{1}{n} \sum_{i=1}^{n} (y_i - \hat{y}_i)^2 } RMSE=n1i=1∑n(yi−y^i)2
2.2.4. R² Score(判定系数)
R2=1−∑i=1n(yi−y^i)2∑i=1n(yi−yˉ)2 R^2 = 1 - \frac{ \sum_{i=1}^{n} (y_i - \hat{y}_i)^2 }{ \sum_{i=1}^{n} (y_i - \bar{y})^2 } R2=1−∑i=1n(yi−yˉ)2∑i=1n(yi−y^i)2
2.2.5. MAPE(平均绝对百分比误差)
MAPE=1n∑i=1n∣yi−y^iyi∣×100% \text{MAPE} = \frac{1}{n} \sum_{i=1}^{n} \left| \frac{y_i - \hat{y}_i}{y_i} \right| \times 100\% MAPE=n1i=1∑nyiyi−y^i×100%
- 真实值接近 0 的样本非常敏感,可能导致极大误差
SMAPE(对称 MAPE)
SMAPE=1n∑i=1n∣yi−y^i∣(∣yi∣+∣y^i∣)/2×100% \text{SMAPE} = \frac{1}{n} \sum_{i=1}^{n} \frac{ \left| y_i - \hat{y}_i \right| }{ \left( |y_i| + |\hat{y}_i| \right)/2 } \times 100\% SMAPE=n1i=1∑n(∣yi∣+∣y^i∣)/2∣yi−y^i∣×100%
- 解决mape的除零问题
三、LTV 预测中的 ZILN(Zero-Inflated Log-Normal)损失
3.1. LTV 预测背景
在互联网、游戏、电商等场景里,LTV(Lifetime Value)是指用户在整个生命周期内为企业带来的累计收入(或价值)。
LTV 数据的特点:
- 零值很多:大量用户从注册到流失,消费额为 0。
- 正值右偏(长尾):非零用户的消费额高度偏态,少数用户金额极高。
3.2. ZILN 分布简介
ZILN = Zero-Inflated + Log-Normal。
- Zero-Inflated 部分
建模是否为 0
的事件,用 Bernoulli 分布:
p0=Pr(Y=0) p_0 = \Pr(Y = 0) p0=Pr(Y=0) - Log-Normal 部分
对 Y>0Y > 0Y>0建模:
logY∼N(μ,σ2) \log Y \sim \mathcal{N}(\mu, \sigma^2) logY∼N(μ,σ2)
概率密度函数:
f(y)={p0,y=0(1−p0)⋅1yσ2πexp(−(lny−μ)22σ2),y>0 f(y) = \begin{cases} p_0, & y = 0 \\ (1 - p_0) \cdot \frac{1}{y\sigma\sqrt{2\pi}} \exp\left(-\frac{(\ln y - \mu)^2}{2\sigma^2}\right), & y > 0 \end{cases} f(y)={p0,(1−p0)⋅yσ2π1exp(−2σ2(lny−μ)2),y=0y>0
3.3. ZILN 损失公式
基于极大似然估计(MLE)的 负对数似然损失(NLL):
L=−1N∑i=1N[1{yi=0}⋅logp0,i + 1{yi>0}⋅(log(1−p0,i)+logfLN(yi;μi,σi))]
\mathcal{L} = - \frac{1}{N} \sum_{i=1}^N \left[
\mathbb{1}_{\{y_i=0\}} \cdot \log p_{0,i} \;+\;
\mathbb{1}_{\{y_i>0\}} \cdot \left( \log(1 - p_{0,i}) + \log f_{\text{LN}}(y_i; \mu_i, \sigma_i) \right)
\right]
L=−N1i=1∑N[1{yi=0}⋅logp0,i+1{yi>0}⋅(log(1−p0,i)+logfLN(yi;μi,σi))]
其中:
- p0,ip_{0,i}p0,i:预测为零值的概率(Sigmoid 输出);
- μi\mu_iμi:对数金额均值(模型输出);
- σi\sigma_iσi:对数金额标准差(模型输出并取
exp
); - fLNf_{\text{LN}}fLN:对数正态分布的 PDF。
3.4. pytrch实现
import torch
import torch.nn as nn
import torch.nn.functional as Fclass ZILNLoss(nn.Module):"""Zero-Inflated Log-Normal Loss for LTV Prediction模型输出:p0_logits: 预测零值概率的logits (未经过Sigmoid)mu: 对数金额的均值 (直接输出)log_sigma: 对数金额的标准差log(σ) (直接输出)输入:y_true: 实际的LTV值 (tensor, shape = [batch_size])返回:标量损失值"""def __init__(self, eps=1e-8):super().__init__()self.eps = epsdef forward(self, p0_logits, mu, log_sigma, y_true):# Sigmoid转为零值概率p0 = torch.sigmoid(p0_logits)sigma = torch.exp(log_sigma)# mask区分零值和非零值is_zero = (y_true <= self.eps).float()is_nonzero = 1.0 - is_zero# 对数正态分布PDFlog_y = torch.log(torch.clamp(y_true, min=self.eps))log_normal_logpdf = (-torch.log(sigma * torch.clamp(y_true, min=self.eps) * (2 * torch.pi) ** 0.5)- ((log_y - mu) ** 2) / (2 * sigma ** 2))# 零值样本损失: -log(p0)zero_loss = -is_zero * torch.log(torch.clamp(p0, min=self.eps))# 非零值样本损失: -log(1 - p0) - log(pdf)nonzero_loss = -is_nonzero * (torch.log(torch.clamp(1.0 - p0, min=self.eps)) + log_normal_logpdf)loss = zero_loss + nonzero_lossreturn loss.mean()if __name__ == "__main__":batch_size = 4# 模型输出p0_logits = torch.randn(batch_size) # logitsmu = torch.randn(batch_size) # 对数金额均值log_sigma = torch.randn(batch_size) # log标准差# 真实 LTVy_true = torch.tensor([0.0, 0.0, 100.0, 20.0])# 计算损失loss_fn = ZILNLoss()loss = loss_fn(p0_logits, mu, log_sigma, y_true)print("ZILN Loss:", loss.item())