当商品目录从几百条增长到几万条,人工打标签很快会变成一个昂贵、缓慢且不一致的流程。不同运营人员可能为相似商品选择不同标签,规则变化后还要重新检查历史数据。
一种更可控的做法是:使用带标签的商品样本对 Qwen3-8B 进行监督微调(SFT),再通过带可验证奖励的强化学习(RLVR)约束输出格式和标签准确性,最后在 Amazon SageMaker 上部署模型,并通过异步推理处理商品目录。这样可以把一次性训练成本与按需推理成本分开管理,也适合批量处理大规模商品数据。
先把商品标签任务定义成可验证的问题
商品标签系统不只是让模型“描述商品”。生产系统通常需要模型返回一组有限、稳定、可以被程序检查的标签,例如:
{
"product_id": "SKU-1001",
"title": "Women's waterproof hiking jacket",
"category": "outdoor clothing",
"tags": ["women", "hiking", "waterproof", "jacket"]
}
训练数据应明确以下约束:
- 标签必须来自允许的标签集合,不能临时创造新标签。
- 输出必须是 JSON,不能混入解释性文字。
- 同一商品的标签顺序和大小写最好保持一致。
- 无法确认的属性应省略,而不是猜测。
- 可以为标签设置层级,例如
category、material、use_case和audience。
这些约束很重要,因为它们同时服务于三个环节:SFT 学习示例,RLVR 检查奖励,以及线上服务解析结果。如果奖励函数只能判断“像不像正确答案”,模型仍可能生成无法被下游系统使用的文本。
SFT 与 RLVR 如何分工
SFT 适合教模型基本任务模式。训练样本可以包含商品标题、描述和标准标签,让模型学习输入字段、输出格式以及常见分类规则。
RLVR 则适合把可以程序化验证的要求编码成奖励函数。例如:
- JSON 是否可以成功解析;
- 是否包含必需字段;
- 标签是否全部来自允许集合;
- 是否出现重复标签;
- 与人工标注的标签集合有多高的 F1 分数;
- 是否生成了额外的自然语言内容。
这类奖励不依赖人工逐条写偏好描述,适合商品标签这种有明确答案或明确约束的任务。不过,RLVR 不会自动修复错误的标签体系。如果标签本身互相重叠、标注人员标准不一致,强化学习只会更积极地学习这些矛盾。
生成训练 JSONL 数据
下面的 Python 示例把简单的商品标注数据转换成适合指令微调的 JSONL 文件,并在写入前检查标签是否位于允许集合中。示例中的字段和标签体系可以替换成自己的目录数据。
# build_dataset.py
import json
from pathlib import Path
ALLOWED_TAGS = {
"women", "men", "hiking", "running", "waterproof",
"jacket", "shoes", "backpack", "cotton", "summer"
}
PRODUCTS = [
{
"product_id": "SKU-1001",
"title": "Women's waterproof hiking jacket",
"description": "Lightweight shell jacket for hiking in rainy weather.",
"tags": ["women", "hiking", "waterproof", "jacket"]
},
{
"product_id": "SKU-1002",
"title": "Men's cotton summer running shirt",
"description": "Breathable cotton shirt for warm-weather training.",
"tags": ["men", "cotton", "summer", "running"]
}
]
SYSTEM = (
"You tag catalog products. Return only valid JSON with keys "
"product_id and tags. tags must be a JSON array selected from the allowed list."
)
output = Path("product_tags.jsonl")
with output.open("w", encoding="utf-8") as f:
for product in PRODUCTS:
unknown = set(product["tags"]) - ALLOWED_TAGS
if unknown:
raise ValueError(f"Unknown tags for {product['product_id']}: {unknown}")
record = {
"messages": [
{"role": "system", "content": SYSTEM},
{
"role": "user",
"content": json.dumps(
{
"product_id": product["product_id"],
"title": product["title"],
"description": product["description"],
"allowed_tags": sorted(ALLOWED_TAGS),
},
ensure_ascii=False,
),
},
{
"role": "assistant",
"content": json.dumps(
{
"product_id": product["product_id"],
"tags": product["tags"],
},
ensure_ascii=False,
),
},
]
}
f.write(json.dumps(record, ensure_ascii=False) + "\n")
print(f"Wrote {output} with {len(PRODUCTS)} records")
运行:
python build_dataset.py
head -n 1 product_tags.jsonl
实际训练时,还应拆分训练集、验证集和测试集,并按商品类型、品牌、语言和长尾标签分层抽样。不要随机把同一商品的近似标题同时放进训练集和测试集,否则测试结果会过于乐观。
在 SageMaker 上组织训练和部署流程
一个可落地的流程可以分成四段:
- 将 JSONL 训练集和验证集上传到 Amazon S3。
- 使用 SageMaker serverless model customization 对 Qwen3-8B 执行 SFT。
- 使用带验证器的奖励函数执行 RLVR,重点约束 JSON、标签集合和标签准确率。
- 将定制后的模型部署到推理端点,通过异步推理读取 S3 输入并将结果写回 S3。
“无服务器模型定制”减少了团队自行管理训练基础设施的工作,但仍然需要准备训练数据、选择合适的超参数、配置 IAM 权限并监控作业。具体 API 参数和可用区域应以当前 SageMaker 文档和账户中的模型支持情况为准;不要把某个示例中的资源名称、实例规格或区域直接复制到生产环境。
RLVR 的验证器可以采用类似下面的逻辑。这里是一个最小的伪代码示例,实际运行时需要接入训练框架要求的奖励函数接口:
import json
def tag_reward(model_text: str, expected_tags: set[str], allowed_tags: set[str]) -> float:
try:
result = json.loads(model_text)
tags = result["tags"]
if not isinstance(tags, list) or not all(isinstance(x, str) for x in tags):
return -1.0
except (json.JSONDecodeError, KeyError, TypeError):
return -1.0
if len(tags) != len(set(tags)):
return -0.5
if not set(tags).issubset(allowed_tags):
return -0.8
predicted = set(tags)
precision = len(predicted & expected_tags) / max(len(predicted), 1)
recall = len(predicted & expected_tags) / max(len(expected_tags), 1)
f1 = 2 * precision * recall / max(precision + recall, 1e-8)
return f1
实践中可以把格式奖励和标签质量奖励加权组合。例如,非法 JSON 直接给负奖励;合法 JSON 再根据标签集合 F1 计算分数。权重应通过验证集调节,避免模型为了追求格式而牺牲分类质量,或者为了追求召回率而输出过多标签。
使用异步推理处理商品目录
商品目录通常不要求用户在几百毫秒内拿到结果,因此异步推理比同步逐条请求更适合批处理。准备一个 JSONL 文件,每一行代表一个待标注商品:
{"product_id":"SKU-2001","title":"Waterproof trail shoes","description":"Light shoes designed for wet mountain paths."}
{"product_id":"SKU-2002","title":"Cotton summer backpack","description":"Small cotton backpack for daily travel."}
将文件上传到 S3 后,可以使用下面的 Python 脚本提交异步请求。运行前设置已经部署好的 SageMaker 端点名称,以及输入、输出 S3 URI。端点中的模型提示模板应与训练数据格式保持一致。
# submit_async.py
import os
import boto3
runtime = boto3.client("sagemaker-runtime", region_name=os.environ["AWS_REGION"])
response = runtime.invoke_endpoint_async(
EndpointName=os.environ["SAGEMAKER_ENDPOINT_NAME"],
InputLocation=os.environ["INPUT_S3_URI"],
ContentType="application/jsonlines",
Accept="application/jsonlines",
InferenceId="catalog-batch-20250101"
)
print("InferenceId:", response.get("InferenceId"))
print("OutputLocation:", response.get("OutputLocation"))
print("FailureLocation:", response.get("FailureLocation"))
运行示例:
export AWS_REGION=us-east-1
export SAGEMAKER_ENDPOINT_NAME=product-tagging-endpoint
export INPUT_S3_URI=s3://my-catalog-bucket/input/products.jsonl
python submit_async.py
异步结果落盘后,还需要一个结果校验步骤。生产管道不应直接把模型输出写入商品主数据,而应先检查 JSON 可解析性、标签白名单、标签数量和置信度策略。失败记录可以进入人工审核队列,成功记录再更新搜索索引或商品数据库。
成本、质量与运维边界
这种架构的主要收益是把大批量目录处理变成可排队的异步任务,并通过模型定制减少每次请求都重复描述业务规则的需要。但它并不意味着成本和运维问题消失了。
建议重点观察以下指标:
- 每批商品的总推理时间和失败率;
- 每个商品的平均输入、输出 token 数;
- 合法 JSON 比例;
- 白名单外标签比例;
- 与人工抽检结果相比的 precision、recall 和 F1;
- 人工审核比例以及长尾类别的错误率;
- S3、训练作业、端点和日志产生的实际费用。
异步推理尤其需要幂等设计。为每个批次和商品保留稳定的 product_id,输出写入带版本或批次号的路径;重试时不要因为网络超时而重复覆盖已经审核过的结果。
采用前的检查清单
- [ ] 标签集合已经去重,并定义了层级、别名和废弃标签。
- [ ] 训练、验证、测试数据按商品和近似文本正确隔离。
- [ ] SFT 样本包含合法 JSON 的正例。
- [ ] RLVR 验证器能检查 JSON、白名单和标签质量。
- [ ] 推理端点使用与训练数据一致的提示模板。
- [ ] 异步输入和输出都放在受控的 S3 路径中。
- [ ] 失败结果、低质量结果和人工审核流程已经定义。
- [ ] 已用真实目录规模测量延迟、成本和错误率。
SFT 负责让 Qwen3-8B 学会商品标签任务,RLVR 负责把可验证的业务规则变成训练信号,SageMaker 异步推理则负责承载批量处理。三者组合起来,才是一套可以从实验走向目录生产的商品标签系统;单纯把模型微调后直接接入数据库,通常会把格式错误、标签漂移和重试问题留到线上才暴露出来。