#P4441. 第2题-多目标推荐排序模型优化
-
1000ms
Tried: 33
Accepted: 0
Difficulty: 9
所属公司 :
华为
时间 :2025年11月5日-AI方向
-
算法标签>机器学习算法
第2题-多目标推荐排序模型优化
No testdata at current.
解题思路
本题要求在多目标推荐系统中同时预测点击率(CTR)和转化率(CVR),使用一个共享特征权重的线性模型,通过最小化联合损失函数来优化模型参数。其核心思想是利用多任务学习(Multi-Task Learning)思想,让两个任务在共享信息的同时保留自身的差异。
整体过程可以分为以下几个步骤:
-
模型结构设计 使用一个共享的线性层(即相同的权重向量)来提取通用特征,同时为两个任务分别设置一个独立的偏置项。
- CTR 和 CVR 的预测值都由同一组权重与输入特征相乘得到,但加上不同的偏置。
- 这种结构能够让两个任务在共享信息的同时保留一定的灵活性。
-
损失函数构建 分别计算 CTR 与 CVR 的预测误差(平方误差),并按给定的权重系数 α 进行加权求和,得到联合损失值。
- 当 α 较大时,模型会更关注 CVR 的优化;当 α 较小时,模型更偏向优化 CTR。
-
参数优化方法 使用批量梯度下降法(Batch Gradient Descent)更新参数。
- 每次迭代计算预测值与真实值的差异,得到梯度方向。
- 根据学习率对权重和偏置进行反向更新。
- 所有参数初始值设为 0,按给定的学习率和迭代次数更新。
-
结果计算与输出 在完成全部迭代后,重新计算一次最终的联合损失值。
- 将最终损失值乘以 10¹⁰ 并进行四舍五入。
- 输出结果为一个整数。
-
关键思想总结
- 利用多任务共享机制,提升模型对不同目标的综合学习能力。
- 通过联合损失函数平衡 CTR 与 CVR 的优化。
- 采用标准的线性回归与梯度下降优化方式,算法逻辑简单、可解释性强。
复杂度分析
设样本数为 n,特征维度为 d,迭代次数为 N。
- 时间复杂度:每次迭代需要一次前向与一次反向,均为 O(nd)。总计 O(Nnd)。
- 空间复杂度:存储 w 与少量中间量,O(d);外加输入数据 O(nd)。整体 O(nd)(若仅按模型参数计则 O(d))。
代码实现
Python
from ast import literal_eval
from decimal import Decimal, ROUND_HALF_UP
import sys
def parse_matrix(line: str):
# 将形如 "1,2;3,4" 转换为 "[[1,2],[3,4]]" 再 literal_eval
s = '[[' + line.strip().replace(';', '],[') + ']]'
mat = literal_eval(s)
# 转为 float
return [[float(v) for v in row] for row in mat]
def train_and_loss(X, Y, iters, lr, alpha):
n = len(X)
d = len(X[0])
# 参数初始化为 0
w = [0.0] * d
b_ctr = 0.0
b_cvr = 0.0
for _ in range(iters):
# 前向计算
yhat_ctr = [sum(w[j] * X[i][j] for j in range(d)) + b_ctr for i in range(n)]
yhat_cvr = [sum(w[j] * X[i][j] for j in range(d)) + b_cvr for i in range(n)]
e_ctr = [yhat_ctr[i] - Y[i][0] for i in range(n)]
e_cvr = [yhat_cvr[i] - Y[i][1] for i in range(n)]
# 梯度计算
grad_w = [0.0] * d
for j in range(d):
s = 0.0
for i in range(n):
s += (e_ctr[i] + alpha * e_cvr[i]) * X[i][j]
grad_w[j] = (2.0 / n) * s
grad_b_ctr = (2.0 / n) * sum(e_ctr)
grad_b_cvr = alpha * (2.0 / n) * sum(e_cvr)
# 参数更新
for j in range(d):
w[j] -= lr * grad_w[j]
b_ctr -= lr * grad_b_ctr
b_cvr -= lr * grad_b_cvr
# 最终损失
yhat_ctr = [sum(w[j] * X[i][j] for j in range(d)) + b_ctr for i in range(n)]
yhat_cvr = [sum(w[j] * X[i][j] for j in range(d)) + b_cvr for i in range(n)]
mse_ctr = sum((yhat_ctr[i] - Y[i][0]) ** 2 for i in range(n)) / n
mse_cvr = sum((yhat_cvr[i] - Y[i][1]) ** 2 for i in range(n)) / n
loss = mse_ctr + alpha * mse_cvr
return loss
def main():
lines = [line.rstrip('\n') for line in sys.stdin if line.strip() != '']
fea_line = lines[0]
lbl_line = lines[1]
iters = int(lines[2])
lr = float(lines[3])
alpha = float(lines[4])
X = parse_matrix(fea_line)
Y = parse_matrix(lbl_line) # 每个样本形如 [ctr, cvr]
loss = train_and_loss(X, Y, iters, lr, alpha)
# 四舍五入到整数(HALF_UP)
val = Decimal(str(loss)) * Decimal('10000000000')
ans = val.to_integral_value(rounding=ROUND_HALF_UP)
print(f"{ans}")
if __name__ == "__main__":
main()
Java
import java.io.*;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.*;
public class Main {
// 训练与计算损失的外部函数
static double trainAndLoss(double[][] X, double[][] Y, int iters, double lr, double alpha) {
int n = X.length;
int d = X[0].length;
double[] w = new double[d];
double bCtr = 0.0;
double bCvr = 0.0;
for (int t = 0; t < iters; t++) {
double[] yhatCtr = new double[n];
double[] yhatCvr = new double[n];
double[] eCtr = new double[n];
double[] eCvr = new double[n];
for (int i = 0; i < n; i++) {
double dot = 0.0;
for (int j = 0; j < d; j++) dot += w[j] * X[i][j];
yhatCtr[i] = dot + bCtr;
yhatCvr[i] = dot + bCvr;
eCtr[i] = yhatCtr[i] - Y[i][0];
eCvr[i] = yhatCvr[i] - Y[i][1];
}
double[] gradW = new double[d];
for (int j = 0; j < d; j++) {
double s = 0.0;
for (int i = 0; i < n; i++) s += (eCtr[i] + alpha * eCvr[i]) * X[i][j];
gradW[j] = (2.0 / n) * s;
}
double gradBCtr = 0.0, gradBCvr = 0.0;
for (int i = 0; i < n; i++) {
gradBCtr += eCtr[i];
gradBCvr += eCvr[i];
}
gradBCtr = (2.0 / n) * gradBCtr;
gradBCvr = alpha * (2.0 / n) * gradBCvr;
for (int j = 0; j < d; j++) w[j] -= lr * gradW[j];
bCtr -= lr * gradBCtr;
bCvr -= lr * gradBCvr;
}
// 最终损失
double mseCtr = 0.0, mseCvr = 0.0;
for (int i = 0; i < n; i++) {
double dot = 0.0;
for (int j = 0; j < d; j++) dot += w[j] * X[i][j];
double e1 = (dot + bCtr) - Y[i][0];
double e2 = (dot + bCvr) - Y[i][1];
mseCtr += e1 * e1;
mseCvr += e2 * e2;
}
mseCtr /= n;
mseCvr /= n;
return mseCtr + alpha * mseCvr;
}
static double[][] parseMatrix(String line) {
// 将 "1,2;3,4" 切分为二维数组
String[] rows = line.trim().split(";");
int n = rows.length;
String[] firstCols = rows[0].split(",");
int d = firstCols.length;
double[][] mat = new double[n][d];
for (int i = 0; i < n; i++) {
String[] cols = rows[i].trim().split(",");
for (int j = 0; j < d; j++) {
mat[i][j] = Double.parseDouble(cols[j]);
}
}
return mat;
}
public static void main(String[] args) throws Exception {
Scanner sc = new Scanner(System.in);
String feaLine = sc.nextLine().trim();
String lblLine = sc.nextLine().trim();
int iters = Integer.parseInt(sc.nextLine().trim());
double lr = Double.parseDouble(sc.nextLine().trim());
double alpha = Double.parseDouble(sc.nextLine().trim());
double[][] X = parseMatrix(feaLine);
double[][] Y = parseMatrix(lblLine); // 每行 [ctr, cvr]
double loss = trainAndLoss(X, Y, iters, lr, alpha);
// 四舍五入到整数(HALF_UP),输出 Loss * 1e10
BigDecimal bd = BigDecimal.valueOf(loss).multiply(BigDecimal.valueOf(1e10));
bd = bd.setScale(0, RoundingMode.HALF_UP);
System.out.println(bd.toPlainString());
}
}
C++
#include <bits/stdc++.h>
using namespace std;
// 训练与损失计算函数(批量梯度下降)
double trainAndLoss(const vector<vector<double>>& X,
const vector<vector<double>>& Y,
int iters, double lr, double alpha) {
int n = (int)X.size();
int d = (int)X[0].size();
vector<double> w(d, 0.0);
double bCtr = 0.0, bCvr = 0.0;
for (int t = 0; t < iters; ++t) {
vector<double> yhatCtr(n, 0.0), yhatCvr(n, 0.0);
vector<double> eCtr(n, 0.0), eCvr(n, 0.0);
for (int i = 0; i < n; ++i) {
double dot = 0.0;
for (int j = 0; j < d; ++j) dot += w[j] * X[i][j];
yhatCtr[i] = dot + bCtr;
yhatCvr[i] = dot + bCvr;
eCtr[i] = yhatCtr[i] - Y[i][0];
eCvr[i] = yhatCvr[i] - Y[i][1];
}
vector<double> gradW(d, 0.0);
for (int j = 0; j < d; ++j) {
double s = 0.0;
for (int i = 0; i < n; ++i) s += (eCtr[i] + alpha * eCvr[i]) * X[i][j];
gradW[j] = (2.0 / n) * s;
}
double gradBCtr = 0.0, gradBCvr = 0.0;
for (int i = 0; i < n; ++i) {
gradBCtr += eCtr[i];
gradBCvr += eCvr[i];
}
gradBCtr = (2.0 / n) * gradBCtr;
gradBCvr = alpha * (2.0 / n) * gradBCvr;
for (int j = 0; j < d; ++j) w[j] -= lr * gradW[j];
bCtr -= lr * gradBCtr;
bCvr -= lr * gradBCvr;
}
// 最终损失
double mseCtr = 0.0, mseCvr = 0.0;
for (int i = 0; i < n; ++i) {
double dot = 0.0;
for (int j = 0; j < d; ++j) dot += w[j] * X[i][j];
double e1 = (dot + bCtr) - Y[i][0];
double e2 = (dot + bCvr) - Y[i][1];
mseCtr += e1 * e1;
mseCvr += e2 * e2;
}
mseCtr /= n;
mseCvr /= n;
return mseCtr + alpha * mseCvr;
}
static vector<vector<double>> parseMatrix(const string& line) {
// 将 "a,b;c,d" 解析为二维 double 数组
vector<vector<double>> mat;
stringstream ssRow(line);
string row;
while (getline(ssRow, row, ';')) {
stringstream ssCol(row);
string val;
vector<double> r;
while (getline(ssCol, val, ',')) {
r.push_back(stod(val));
}
mat.push_back(r);
}
return mat;
}
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
string feaLine, lblLine, s3, s4, s5;
getline(cin, feaLine);
getline(cin, lblLine);
getline(cin, s3);
getline(cin, s4);
getline(cin, s5);
int iters = stoi(s3);
double lr = stod(s4);
double alpha = stod(s5);
vector<vector<double>> X = parseMatrix(feaLine);
vector<vector<double>> Y = parseMatrix(lblLine); // 每个样本 [ctr, cvr]
double loss = trainAndLoss(X, Y, iters, lr, alpha);
// 四舍五入输出 Loss * 1e10
long long ans = llround(loss * 1e10);
cout << ans << "\n";
return 0;
}
疑似题目有问题,尝试多种思路依然无法通过样例
题目内容
多目标学习的推荐排序模型需同时预测点击率(CTR,Click−Through Rate)和转化率(CVR,Conversion Rate),可采用线性回归的方式完成多目标建模,常见方法包括共享特征权重但保留任务特定偏置;在此使用联合损失函数:$\text{Loss} = \text{MSE}_{\text{CTR}} + \alpha \cdot \text{MSE}_{\text{CVR}}$ 优化共享权重和两个偏置,其中,MSE 表示标准均方误差损失,α 表示加权系数,权重和偏置初始值从 0 开始,返回迭代 N 次后的平均联合损失值乘以 10 的 10 次方后的四舍五入结果(注意是损失值结果,而非梯度的结果)。
输入描述
第一行,输入特征集合,1,2;3,4;5,6
第二行,预测的 ctr/cvr 指标集合,0.1,0.01;0.5,0.05;0.9,0.09
第三行,迭代次数 (iteration),1000
第四行,学习率,0.01
第五行,加权系数,0.5
符号解释: 分号前后隔开不同的样本,逗号隔开样本内的不同值
输出描述
输出联合损失值 ∗10 的 10 次方的结果为 1301069
样例1
输入
1,1,1;2,2,2;3,3,3
1,0.5;2,1.0;3,1.5
500
0.01
0.5
输出
27356237
说明
输出联合损失值 10 的 10 次方的结果为 27356237
样例2
输入
1,2;3,4;5,6
0.1,0.01;0.5,0.05;0.9,0.09
1000
0.01
0.5
输出
1301069
说明
输出联合损失值 10 的 10 次方 的结果为 1301069