返回列表

16th place solution - SDXL-Flash + Siglip/AES Reranking + Proxy VQA

650. Drawing with LLMs | drawing-with-llms

开始: 2025-02-25 结束: 2025-05-27 AIGC与多模态 AI大模型赛
第 16 名解决方案 - SDXL-Flash + Siglip/AES 重排序 + 代理 VQA

第 16 名解决方案 - SDXL-Flash + Siglip/AES 重排序 + 代理 VQA

作者: Geremie Yeo (及团队成员)

排名: 第 16 名

发布时间: 2025 年 6 月 6 日

首先,我们要感谢主办方持续努力改进评估指标,以确保顶级解决方案与比赛目标保持一致!

团队成员:

代码: https://github.com/bogoconic1/16th-place-kaggle-drawing-with-llms

Solution Overview

总之,我们分配了 36 秒用于 SVG 生成和美学分数计算,剩余 27 秒用于候选选择。

图像生成

我们尝试了许多开源 SD 模型(例如 Sana, SD 3.5 等),最终坚持使用 SDXL-Flash,因为它是能够生成符合我们预期的“好看”图像的最快模型。

使用默认设置 (1024x1024),SDXL-Flash 生成一张图像需要 8 秒。然而,我们通过生成较低分辨率的图像将速度提高了高达 80%。对于 512x512,我们能够在 30 秒内生成 18 张候选图像,且质量没有太大妥协。本地验证表明,512x512 或 640x640 在速度和准确性之间提供了最佳权衡。在 LB 上,704x704 表现也不错,但由于验证分数较低,我们没有选择它。

此外,我们使用了提示词 f'Cartoon style of "{description}".',因为我们注意到 DOODL 论文的封面显示图像在美学优化后似乎具有卡通风格。在检查 SVG 输出时发现,与普通图像相比,转换过程中的质量损失较小。

Cartoon Style Example

位图转 SVG

公开分享的 bitmap_to_svg_layered 技术每张图像运行约需 4.5 秒,并且在压缩到 10k 字节时有显著的信息丢失。

在进行函数剖析时,我们注意到 cv2 的 KMeans 是主要瓶颈。因此,我们使用 NVIDIA 的 GPU 版 cuML KMeans 加快了速度,这样每张图像转换为 SVG 仅需约 0.3 秒(15 倍加速)。

此外,为了在 10k 字节中塞入更多元素,我们嵌入了以下技术。

  • 将所有坐标四舍五入到最近的整数
  • 将所有 <polygon> 转换为 <path>
  • 应用颜色分组(将相同颜色的所有路径嵌入到 <g> 标签内)
  • 移除信息字节(在 OCR 诱饵函数中)
  • 使用 scour 进一步修剪最终 SVG 的大小

这使我们能够容纳 60 -> 168 个元素(平均),与公开分享的函数相比提高了 280%,且没有任何质量损失

使用了 @richolson 的 OCR 诱饵来减轻幻觉。我们没有做任何更改,因为 1/150 的失败率被认为是可接受的

候选选择

为了加快本地美学计算,我们使用 cv2 加快了 apply_median_filter,并使用 NVIDIA CuPy 加快了 ImageProcessor 中的 apply_fft_low_pass(0.5 -> 0.3 秒/图像)

class FasterImageProcessor(metric.ImageProcessor):

    def __init__(self, image: Image.Image, seed=None):
        super().__init__(image, seed)

    def apply_median_filter(self, size=9):
        """Fast median filter using OpenCV (≈10-20× PIL speed)."""
        # Pillow → NumPy (BGR order is okay; cv2 works on uint8 directly)
        img_array = np.asarray(self.image)
        # OpenCV expects odd kernel sizes ≥3
        if size % 2 == 0:
            size += 1
        filtered = cv2.medianBlur(img_array, ksize=size)
        self.image = Image.fromarray(filtered)
        return self

    def apply_fft_low_pass(self, cutoff_frequency=0.5, device=0):
        x = cp.asarray(self.image, dtype=cp.float32)      # H×W×3
        f = cp.fft.fftshift(cp.fft.fft2(x, axes=(0,1)), axes=(0,1))
    
        rows, cols = x.shape[:2]
        crow, ccol = rows // 2, cols // 2
        r = int(min(crow, ccol) * cutoff_frequency)
    
        y, xg = cp.ogrid[:rows, :cols]
        mask = ((y - crow) ** 2 + (xg - ccol) ** 2) <= r * r
        f *= mask[..., None]                              # broadcast over channels
    
        img_back = cp.fft.ifft2(cp.fft.ifftshift(f, axes=(0,1)), axes=(0,1)).real
        img_back = cp.clip(img_back, 0, 255).astype(cp.uint8).get()
        self.image = Image.fromarray(img_back)
        return self

我们通过问一个问题来估计 VQA 分数:'Does <image> portray "{}"? Answer yes or no.'

鉴于

方法 每张图像耗时
美学分数 0.3s
VQA + OCR 分数 3s

我们没有估计所有图像的 VQA,而是采用了一种重排序方法来估计哪些图像更有可能在比赛指标上获得更高分数,这使我们在时间限制内能够生成更多候选 SVG。

  • 我们首先生成 N 张图像(N 取决于图像分辨率),将它们转换为 SVG 并计算所有 N 张图像的美学分数,时间限制为 36 秒。
  • 由于仅凭美学分数无法告诉我们图像与提示词的相关性,我们使用 siglip 模型计算 SVG 与提示词之间的对齐分数——将它们相乘得到重排序分数。这在 384x384 和 512x512 上显著改善了结果,但在 640x640 上改善不大
  • SVG 按重排序分数降序排列,并尝试进行 VQA/OCR,直到时间超过 63 秒(平均可以运行 7 次此类评估)。
  • 选择具有最高代理保真度分数的 SVG。

备注

  • 我们在 500 个示例的验证集上测试了我们的流程(数据集稍后分享),发现它在 CV 和 LB 上都相对稳定,公共分数约为 0.705-0.715,私有分数约为 0.700-0.705。CV 与 LB 分数在 0.7 之前相关性很好。
  • 然而,有一个提交脱颖而出(私有 0.717,金牌区),它与我们选择的提交只是很小的更改 😢 - 验证和公共 LB 分数无法解释这一点(可能是幸运的 OCR?)。编辑:这可能是一个巧合,重新提交后得分为 0.709
  • 我们没有尝试 vtracer 或任何其他方法来优化 SVG,如果还有几天时间可能会这样做。
  • 我们没有尝试微调任何稳定扩散模型。

失败的想法

  • 使用相似性匹配的额外去重步骤
  • 训练 LLM 在推理期间生成实时问题
  • 向图像添加背景以期提高美学分数
  • 使用更复杂的重排序技术而不估计 VQA 分数。我们在不估计 VQA/OCR 的情况下的最佳公共/私有分数是 0.701/0.698(512x512 分辨率下 25 张候选图像)
  • 使用 3b PaliGemma 模型估计 VQA 和 OCR
同比赛其他方案