返回列表

5th place solution - VTracer and DiffVG optimization

650. Drawing with LLMs | drawing-with-llms

开始: 2025-02-25 结束: 2025-05-27 AIGC与多模态 AI大模型赛
第 5 名解决方案 - VTracer 和 DiffVG 优化

第 5 名解决方案 - VTracer 和 DiffVG 优化

作者: matheus (tomirol) | 发布时间: 2025-05-28 | 竞赛排名: 第 5 名

我要感谢组织者提供了如此有趣且具有挑战性的比赛,尤其是因为评估指标真的非常棘手,需要投入大量的思考。

我的最终解决方案基于使用 SDXL-turbo 生成大量图像,将它们转换为 SVG,然后添加一个优化的补丁以显著提高美学分数。之后,我使用 VQA 模型的代理函数来选择最终使用的最佳 SVG。

将此添加到 SVG 文件末尾将把平均美学分数提高到 0.7 左右,几乎不受背景影响:

美学补丁 - 0.7 分数

添加此补丁会降低 VQA 分数,部分原因是它使用了大约 3.5k 字节,这些字节无法再用于原始图像转换,同时也因为它是图像中的一大块噪声。然而, overall 最终分数会高出不少,尤其是当你能够选择使用哪张图像以不过分损害 VQA 分数时。

最终解决方案 在此,包含所有代码库的 Github 在此

示例:

有无美学补丁分数对比

生成图像

这里没什么特别的,我只是使用 SDXL-turbo 生成了一批图像。我尝试了多个其他模型,使用比赛分数将 SDXL-turbo 微调到偏好数据集,使用 image-reward 进行 REFL 微调,使用 VQA 作为奖励进行 DPO 等。

我能得到的最佳整体生成结果是使用 SDXL-turbo 配合一组通过生成数据集中的束搜索 (beam search) 选择的提示词。基本上,我在数据集中运行了大约 200 个不同的提示词,并选择了集成在一起时生成最佳图像的子集。我添加的提示词越多,结果越好,即使使用了 20+ 个提示词。

大多数提示词专注于简单、类似 SVG 的图像,可以使用 Vtracer 轻松转换为 SVG。此外,在负面提示词中显式添加 no textno other colors 始终能稍微改善结果。

光栅转矢量

最初,我使用 primitive 将图像转换为 SVG。我仍然相信,如果调整得当,primitive 生成的图像可能比 Vtracer 好得多,但 Kaggle 上的 CPU 不足以按时运行它:使用所有核心为单个图像生成 50 个 SVG <paths> 需要超过 30 秒(primitive 是用 GO 编写的并且高度并行化)。

另一方面,Vtracer 能够使用单个核心在不到 2 秒的时间内转换图像,而结果几乎一样好。在添加了一些多边形简化、尺寸裁剪并调整了一些参数后,我能够生成每个图像大约 6k 字节的足够好的 SVG。最后,我添加了一个带有黑色背景的小白色 OCR 诱饵,这样它就不会被遗漏。

选择最佳图像

生成所有 SVG 后,使用 VQA 分数的代理函数来选择最佳图像。我只是遵循了这篇论文中描述的方法,并使用此问题的 "Yes""No" 概率:"f<image>answer en Question: Does this image contain {description}? Answer Yes or No.\\nAnswer:"

这实际上比使用 Qwen-2.5 生成问题、选项和答案并运行原始 VQA 分数效果更好。我相信这是因为当我们只能评估少数问题时,代理函数更加 robust,但我可能是错的,也许我的问题生成模型只是很差。

虽然使用其他模型版本 (3b 224, 10b 224 和 3b 448) 也与主要指标相关,但原始 VQA 模型 (10b 448) 始终更好,即使它慢得多且因此使用更少的提示词。

优化 pipeline

在我注意到集成 (ensemble) 会非常强大之后,我必须找到一种有效利用所有 Kaggle 资源的方法。我严重未充分利用 GPU,因为 Vtracer 是 CPU 绑定的,而 Kaggle 机器的 CPU **真的** 很慢。例如,使用 SDXL-turbo 生成图像实际上比在单线程上运行 Vtracer 更快。

最后,我能够通过后台运行三个进程来利用几乎 100% 的资源:

  • 扩散模型在 cuda:0
  • Vtracer 在 cpu
  • 代理函数在 cuda:1

然后在主 notebook 内控制流程。例如,每当 SDXL 准备好几张图像时,我们可以开始转换,同时继续并行生成更多图像。当几张 SVG 准备好时,评分也是如此。

通过这种方式,我能够运行 28 个不同的 SVG,但我相信我可以再推进一点,因为我的解决方案平均每个示例运行大约 55 秒。

寻找美学补丁

为了找到补丁,我构建了预处理、美学模型和 VQA 每一步的完全可微分版本。有了这个可微分 pipeline,我尝试了很多事情:

  • 直接使用 DiffVG 优化完整分数 (SVG -> 分数直接)
  • 向图像添加文本并降低 VQA OCR 分数以隐藏
  • 寻找一小部分路径,与数据集一起优化美学分数
  • 使用 VQA 模型优化代理函数 (yes 概率) 以找到最佳使用的 SVG
  • 将完整函数 (VQA + 美学) 插入到像 ReNO 这样的方法中,直接优化来自 SDXL-turbo 的图像
  • 使用类似于 image-reward 的方法直接微调扩散模型,但使用可微分 VQA 损失

所有这些方案确实有效,但它们要么太慢,要么需要太多 GPU 内存才能在 Kaggle 上运行,要么生成的 SVG 远远超过 10kb,要么只是太不稳定以确保每次都能工作。最后,我尝试做类似其他人直接在生成的 SVG 上优化的事情,但不幸的是无法让 VQA 分数的代理函数很好地工作 =/

我能找到的最佳解决方案只是搜索一小部分路径,当添加到实际场景的背景上时,将提高数据集中的平均美学分数。由于所有预处理特别是裁剪,美学分数在不同图像之间变化很大,但在大量图像上的平均值将可测量地提高。此外,由于裁剪,补丁的位置不能太靠近边缘;否则,分数会低得多。

因为补丁使用了很多字节,我不得不平衡补丁大小和 VQA 分数。补丁越大,VTracer 可用于转换 SVG 的信息就越少。我能够通过使用更多字节获得超过 0.8 美学分数的补丁,但那样 VQA 的打击会太高。

我的最终补丁大约 100x100 像素,是通过对 150 个小图像数据集的美学分数进行简单的梯度上升找到的。我从之前的提交中选择了一些背景,从头开始以 96x96 像素的网格模式启动 SVG,然后运行梯度上升。这个 notebook 展示了该过程,找到一个好的美学补丁花费了几个小时,并且它对图像中的位置极其敏感。

Bonus

我认为我尝试过的最棒的事情是将可微分 pipeline 插入到 ReNO 中,并直接针对完整损失优化扩散过程。它生成了一些非常好的图像,实际上比单次运行 SDXL-turbo 要好得多,但它效率很低,最终效果不如我做的简单集成。此外,我只能使用 VQA 的 3B-224 版本,因为较大的版本在具有 16GB 内存的 T4 上无法启用梯度运行,所以损失与比赛分数并不完全相关。

这个 notebook 展示了从 SDXL 生成的第一张图像到中间图像的改进,同时在扩散过程本身中优化指标。

同比赛其他方案