随着电商行业的快速发展,用户对于在线购物体验的要求也在不断提高。特别是在服装领域,用户希望能够更加直观地了解商品的实际穿着效果。为此,淘宝试衣项目应运而生,通过AI技术为用户提供个性化的试衣体验。本文将详细介绍淘宝试衣项目的背景、现状、合作场景以及技术实现,特别是如何通过定时任务驱动的试衣素材高效写入IC(商品中心)拓展结构,提升用户体验和转化率。
▐ 现状
服饰属于非标商品,用户无法直观的通过模特上身图去预估自己的真实上身结果;在详情页内,用户主要通过卖家秀、问大家等获取真实参考;因此部分数据不全的商品内,用户难以快速决策发生购买。
▐ 淘宝试衣已经做了什么
扩大女装商品覆盖:例如连衣裙、上装等
支持同商品的不同sku的试穿,提供分类目的不同上身效果的衣长指南
优化多版模特,使其更加真实自然;支持多种身型模特以及用户照片试穿
优化试衣效果及清晰度
▐ 淘宝试衣在其他合作场景
LAZADA详情:为没有模特上身图的商家替换东南亚模特试穿的详情主图
淘宝详情、购物车等:试衣标志,为有需求的用户提供试衣体验
BC消息:咨询界面为客户实时提供试穿入口,让用户看到真实试穿效果
▐ 淘宝试衣x详情场景的承接演变:
原沉浸式试衣间形态
在详情场景下不适应的问题:
1. 沉浸式试衣间要做页面跳转,跳出详情,阻断交易链路
2. 沉浸式试衣间内的商品来自推荐和衣橱,不适用于详情场景
3. 用户实时试穿,需要等待,影响用户体验,受限于显卡资源无法承受详情这么大的qps
为了让更多的用户能够直面接触淘宝试衣以及带给用户更好的试衣体验;我们与详情进行合作:
1. 在详情页增加ai试衣锚点,让用户在主图区域直观看到ai试衣效果;
2. 算法预跑不同身形下的试衣效果数据,服务端使用离线任务将其写入ic拓展结构,用于锚点试衣效果展示,解决详情场景下请求过多导致显卡资源不足问题
3. 用户同样可在商品主图区域直接触发试衣浮层,使用不同的模特或使用自身照片,选择不同的sku进行试穿;
淘宝试衣 x 详情页
关于模特的位置以及外观形态等方面正在优化中,近期上线新模特。
▐ 目标
试衣视角:
详情渠道来访用户提升xw(依赖于与详情的商议条件人群,建议卖家秀及评价数据为0的女装商品)
详情渠道用户人均试穿n次
详情视角:
通过在商品主图为用户提供更个性化的试穿效果图,帮助用户快速下决策,提升该部分用户的在详情的笔数转化率a%
当然在上线后我们也是发现了很多不足,无论是模特还是页面的排版上都有很多不合理的地方,我们正在对这一部分进行优化,下面先看一下本次合作相关链路,稍后进入正题:网格化任务写ic。
淘宝试衣 x 详情合作主链路
此次完成的离线任务主要支持的工作是将商品的ai试衣信息写入ic拓展结构。
试衣素材写ic实现方式:odps数据预处理 + ScheduleX网格化任务
▐ 1. 高性能(高效)目标
性能可横向扩展(在不考虑上下游依赖的情况下,增加机器可提升性能),现阶段目标实现百万级商品在小时内完成
保障打标成功率达到99.9%
任务失败重跑断点续传
任务执行进度及结果可视化
目标实现于效率优化部分 4子任务分发以及处理部分效率优化
▐ 2. odps数据预处理
多行数据整合:
由于上游提供的数据为:商品下的每一张图片对应一条数据,每个商品平均5张图片,则一个商品会产生约5条数据。
不进行预处理的情况且假设请求速率不变的情况下,在调用离线任务时,每条数据去进行ic写入,请求量级上,qps会被放大5倍;另外在代码逻辑上不能直接采用覆盖更新ic试衣拓展结构的方式,而是每条数据都需要先查询ic进行校验后追加数据,这种情况下,qps又被放大2倍;
代码内存中聚合实现成本高且灵活性差,那么在现实场景下会导致每秒的商品处理量会急剧降低,因此我在对ic进行数据写入之前对odps同商品数据进行了整合操作。
在上游提供的原数据表中一个item存在多条试穿数据,我需要先将该数据按item维度进行Group BY后,再使用WM_CONCAT()与CONCAT()将其中每条数据的相应字段整合到一个名为extend_info的字段中进行汇总,其中包含多条试穿数据,为了在离线任务中能够更好的进行对象格式的转换,需要将其组装为json格式;最终,实现数据的正确获取以及整合,存储至新表中。
▐ 3. 实现数据的正确获取及处理(ScheduleX网格化任务)
关于离线任务写ic拓展结构的主要流程如下:
在网格化任务处理过程中,主要包含三大部分:子任务的分发、子任务的正确处理、执行结果汇总。
@Overridepublic ProcessResult process(final JobContext jobContext) throws Exception {// 处理master任务if (isRootTask(jobContext)) {return processRootTask(jobContext);}// 处理分发子任务if (StringUtils.equals(jobContext.getTaskName(), SUB_TASK_NAME)) {return processDressOfflineDataWritingIcTask(jobContext);}return new ProcessResult(true);}// 执行结果汇总@Overridepublic ProcessResult reduce(final JobContext jobContext) throws Exception {}
子任务分发
在dts平台进行任务运行时,选择了关键信息参数传递的方式:
/** * 组装离线任务上下文 * * @param jobContext 任务基本信息 * @param context离线任务上下文 */public void assembleContextParam(final JobContext jobContext, final DressWritingIcTaskContext context) {final JSONObject params;try {params = JSON.parseObject(jobContext.getInstanceParameters());} catch (Exception e) {throw new RuntimeException(e);}context.setUpdateType(TaskUpdateTypeEnum.parse(params.getString(UPDATE_TYPE)));context.setTableName(params.getString(ODPS_TABLE));context.setProjectName(params.getString(ODPS_PROJECT));context.setPartition(params.getString(PARTITION));context.setTaskId(jobContext.getTaskId());context.setJobInstanceId(jobContext.getJobInstanceId());}子任务分发主流程:
子任务处理
@Overridepublic ProcessResult process(final JobContext jobContext) throws Exception {// 处理master任务if (isRootTask(jobContext)) {return processRootTask(jobContext);}// 处理分发子任务if (StringUtils.equals(jobContext.getTaskName(), SUB_TASK_NAME)) {return processDressOfflineDataWritingIcTask(jobContext);}return new ProcessResult(true);}
关于子任务的处理部分,在任务分发的时候就会指定分发子任务的标识:
这样在子任务处理流程中,拿到对应标识的任务上下文处理即可;
/*** 子任务处理主流程** @param jobContext 子任务基本信息* @return 子任务处理结果*/private ProcessResult processdressWritingIcTask(final JobContext jobContext) {// 1.获取子任务上下文final DressWritingIcTaskContext dataWritingIcTask = (DressWritingIcTaskContext)jobContext.getTask();// 2.处理子任务final TaskUpdateResult taskUpdateResult = processRecordsByPage(dataWritingIcTask);// 3.返回任务处理结果return new ProcessResult(true, JSONObject.toJSONString(taskUpdateResult));}
执行结果汇总
/*** 结果汇总发送钉钉以及群机器人通知主流程** @param jobContext 任务相关数据*/@Overridepublic ProcessResult reduce(final JobContext jobContext) {final TaskUpdateResult processResult = dressWritingIcProcessManager.getSuccessCountFromProcessResult(jobContext);// 数据组装...// 发送钉钉通知}/*** 子任务处理结果统计** @param jobContext reduce中获取任务基本信息及结果容器* @return 汇总更新结果*/public TaskUpdateResult getSuccessCountFromProcessResult(JobContext jobContext) {TaskUpdateResult taskUpdateResult = new TaskUpdateResult();for (String value : jobContext.getTaskResults().values()) {if (StringUtils.isNotBlank(value)) {try {// 对子任务执行结果中需要采集的数据进行整合// ...} catch (Exception e) {LoggerUtil.error(logger, e, "Parse taskUpdateResult failed,value:", value);}}}return taskUpdateResult;}
通过reduce方法,获取各个子任务的执行结果,进行汇总后组装数据发送钉钉通知等。
