基于语义搜索假装图像生成

经过一个月的休息,我回来了。我一直在刚果完成一个石油天然气项目。在这段休息时间里,我有了一些想法,现在我有时间把它们写下来分享给大家。

在这篇文章中,我将继续之前开始的内容。

我将向你展示如何调整一个已发布的现有项目。

它叫做 Fake Image Generation(伪造图像生成)

这个名字可能会产生误导:我们并不是在创建伪造的图像……我们是在模拟图像生成过程,而不是真正地进行生成!

我是一个PoorGPUguy,所以图像生成是可行的,但生成一张1024x640像素的图片大约需要30分钟

但是,如果我们能使用一个足够大的图像数据集,然后在我们期望的提示词和现有提示词(来自图像数据集)之间使用余弦相似度搜索呢?

我们将获得许多与我们需求足够接近的结果!同时,我们还可以从新的提示词(它们的风格、颜色、构图、相机角度)中获得一些灵感。

如果你感兴趣……让我们继续吧!

1、前提条件

阅读我之前的文章(这里没有付费墙):

Fake Image Generator Model — part 1 — 如何使用数据集来模拟图像生成并获得灵感。

Fake image generator model — part 2 — 图书管理员和表演者:AI搜索如何假装魔术。从相似度搜索到图像检索。

阅读并测试之后……回到这里。

我将向你展示如何使用不同的图像数据集(来自HuggingFace)并做同样的事情。

下周我们将介绍合并初始的kaupane/nano-banana-pro-gen和今天博文中的新数据集open-image-preferences-v1-binarized的过程。

这是我的硬件配置……不是很好对吧?

注意:我在一台旧的Lenovo X260上测试所有项目,Intel Core i5,16GB内存。如果我能做到,你也能做到!

1、准备工具

如果你已经跟进了Medium上的文章……你几乎已经拥有了所需的一切。如果你直接跳到这里,让我们回顾一下。

1.1 Python虚拟环境

创建一个新的项目目录(我的叫FIG2)。在终端中打开它并创建一个新的虚拟环境

python -m venv venv

然后激活它:

# in powershell
.\venv\Scripts\activate

# in CMD
venv\Scripts\activate.bat

现在我们需要安装一些Python包:我已经移除了对重型包(如torch)的需求,因为我们将使用numpy来计算余弦相似度,而不是sentence-transformers库。

pip install datasets requests tqdm huggingface_hub ipython pandas pyarrow gradio numpy faiss-cpu

注意这次我添加了ipython:我们将使用这个有用的交互式Python shell在步骤1中探索数据集。

接下来是llama.cpp二进制文件:我们使用llama-server来托管我们将通过requests库调用的嵌入端点。嵌入模型是一样的:BAAI/bge-m3,它是完美的、轻量级的,而且支持多语言。

在同一个主目录中下载llama.cpp的ZIP存档,然后在那里解压。

在同一个目录中下载嵌入模型的GGUF(量化权重)文件。

一切就绪。

额外建议:还可以下载一个模型来帮助你调整现有项目,使其适应新的数据集。我建议下载Qwen_Qwen3.5–2B-Q5_K_L.gguf。这是最新的Qwen 3.5模型,20亿参数,在CPU上运行速度快,同时几乎保持与原始模型相同的精度。

Qwen_Qwen3.5–2B-Q5_K_L.gguf

这个模型是Qwen的推理/非推理模型,以小格式打包了惊人的能力!

稍后我将向你展示如何将其用作编程助手。

图片来自HF仓库

3、探索数据集

这是必不可少的部分。

并非所有数据集都是一样的。不同仓库之间的表头和记录可能会有很大差异。

你可以在Hugging Face上通过模态 = image来筛选数据集进行探索:

链接在这里

浏览了一页又一页之后,我找到了open-image-preferences-v1-binarized

- Goal: This project aims to create 10K text-to-image preference pairs. 
These pairs can be used to evaluate the performance of image generation models across 
a wide variety of common image categories, based on prompt with varying levels of difficulty.
- How: We use the prompts from fal/imgsys-results, these prompts are evolved based on 
complexity and quality for various image categories. We then asked the community to 
annotate the preference between two generated images for each prompt.
 - Result: We achieved to annotate 10K preference pairs.

尽管数据集的目标与我们的目标不一致……我们仍然获得了数千张生成的图像(来自不同的模型)及其初始提示词。

而这正是我们需要的!

要查看仓库的结构,你可以:

  • 在Hugging Face上探索数据集
  • 下载一个小样本到本地以了解如何使用

我们将选择方案2,使用ipython

在终端中,激活venv后,运行:

ipython

Ipython是一个令人惊叹的交互式shell。你可以按单元格运行代码(如果你听说过Google Colab Notebooks或Jupyter Notebook……它们都来自ipython)。好处是每个单元格可以多次运行或修改。如果你打错了什么,不必从头运行整个Python程序。跟着我来:

你可以在这里免费获取iPython速查表

回到探索:我们将使用文章第一部分中相同的代码:

from datasets import load_dataset
import pandas as pd
from PIL import Image
import io

# Load just a small sample
def explore_sample_streaming(dataset_name, split_name="train", num_samples=100):
    """Stream a small sample of the dataset"""
    print(f"Loading {num_samples} samples from {dataset_name}...")
    # Use streaming to avoid downloading everything
    dataset = load_dataset(dataset_name, split=split_name, streaming=True)
    # Take a small sample
    sample_data = []
    for i, example in enumerate(dataset.take(num_samples)):
        sample_data.append(example)
        if i % 10 == 0:
            print(f"Loaded {i+1} samples...")
    # Convert to DataFrame for exploration
    df = pd.DataFrame(sample_data)
    print(f"\nDataset Schema:")
    print(f"Columns: {list(df.columns)}")
    print(f"Sample size: {len(df)}")
    print("\nFirst few rows:")
    print(df.head())
    return df
# Use it
repo = input("Paste the repo name: ") #e.g. cerealt/open-image-preferences-v1-binarized
samples = input("Num of samples: ")
num_sample = int(samples) #ex 50
sample_df = explore_sample_streaming(repo, num_samples=num_sample)

将此代码粘贴到ipython中,当被要求输入仓库名称时,使用新的:cerealt/open-image-preferences-v1-binarized

在这个例子中我只放了10个样本:结果需要理解……

我们可以看到一个叫做prompt的字段,这很好。但是图像呢?

在在线仓库中,它们在chosenrejected字段中。

让我们探索这些字段,了解它们是什么数据类型(字符串、URL还是PIL图像对象):

first_sample = sample_df.iloc[1]
first_sample

看起来chosen/rejected是字节对象(文件)。让我们看看是否可以用Pillow加载它们。

image_data = first_sample['chosen']['bytes']
image = Image.open(io.BytesIO(image_data))
image.show()

你应该会看到爱丽丝梦游仙境的图像 😂

注意:顺便说一句,被拒绝的图像也可以显示
image_data1 = first_sample['chosen']['bytes']
image_data2 = first_sample['rejected']['bytes']
image1 = Image.open(io.BytesIO(image_data1))
image2 = Image.open(io.BytesIO(image_data2))
image1.show()
image2.show()

现在我们知道:

  • 提示词prompt字段中
  • 图像chosen字段中,它是一个可以用Pillow加载的字节对象

我们现在可以继续从仓库下载.parquet文件了。

文件在Files and versions标签下的data子文件夹中

4、下载图像和提示词

在这里,我们要构建我们的json数据库,包含新图像,准备好在本地计算机上使用。

在我这个项目的原始仓库中,我有一个脚本来完成这个任务。但那个Python应用程序是为初始的kaupane/nano-banana-pro-gen数据集量身定制的。

你可以在我的GitHub仓库中找到原始文件

https://github.com/fabiomatricardi/Fake-Image-Generator

如何修复它?

让我们看看原始文件1.big_dset_parquet_Ask.py,找出需要修改的地方。

到第164行,我们有了下载数据集的辅助函数。我们需要在主脚本中找到,我们读取统一parquet文件并提取提示词和图像的位置。

在第271行,我们从统一的parquet文件加载数据集:我们要找的就是那之后的部分!

在第320行,我们找到了关键位置:

我们之前已经测试了如何访问图像:它不在一个叫做'image'的字段中。我们还知道这是一个字节对象,而不是PIL实例(就像原始数据集中那样)。

让我们结合我们的知识来修复这个问题:

try:
    # Save image for new dataset cerealt/open-image-preferences-v1-binarized
    ########################################################################
    if 'chosen' in example and example['chosen']['bytes'] is not None:
        image_data1 = example['chosen']['bytes']
        image1 = Image.open(io.BytesIO(image_data1))
        image1.save(full_path)

        # Get prompt
        prompt = example.get('prompt', '')
        if not prompt and 'json' in example and isinstance(example['json'], dict):
            prompt = example['json'].get('prompt') or example['json'].get('caption') or str(example['json'])

我们验证chosen字段存在且bytes对象不为空:然后我们使用与之前完全相同的代码来读取字节流并保存它。

注意:在我们的原始文件中,io库没有被导入。记得在顶部添加导入

5、更改编号策略

如果我们按原样运行代码,即使做了这些小修改,我们也会遇到另一个问题:新图像将从0开始编号。

但我们已经有了这些数字(至少在nano-banana数据集中有):如果将来我们想把图像合并到一个文件夹中怎么办?

许多图像将被覆盖。

我们需要添加一个增量,一个不同的起始点,这样不同的json索引在合并时不会冲突。

我是这样做的:

我们可以创建一个从2000开始的起始点(我们的原始数据集大约有1k张图像,所以我们可以避免重叠)。

mydelta = 2000

然后我们将mydelta数字添加到所有相关项目:

  • 图像filename* 最终JSON文件中的ID
  • 最终JSON文件中的index
有了这些小技巧,我们可以在将来(下周)合并不同的.json索引时使用新的数据集。

我们将借助AI助手的帮助来完成(我真的不知道如何自己做)

如果你迷路了或代码在某处出错,我把新文件命名为11.new_dataset.py,你可以从我的GitHub仓库下载

让我们运行新的Python应用程序,指向新仓库cerealt/open-image-preferences-v1-binarized

在终端中,激活venv后,运行

python .\11.new_dataset.py

这是我的运行结果

6、测试生成模拟

下一步是验证生成模拟。按照文章第一部分,我们将使用一个随机函数(使用我们新的范围……)。

我把新的Python Gradio应用命名为12.FAKE_Image_Generator.py:你需要调整的原始文件是2.FAKE_Image_Generator.py

我们的图像(如果你按照视频中的相同设置)从2800开始,到3699结束。

让我们调整代码:从原始文件来看,修改非常直观

在第21行,我们需要将randint函数的起始/终止改为类似这样:

# Open the image file
    random_int = random.randint(2800, 3699) # Random int between 2800 and 3699

保存文件,然后在终端中激活venv后运行:

gradio .\12.FAKE_Image_Generator.py

7、结束语

希望到目前为止你喜欢这些内容。

调整现有的应用很酷,因为你需要理解代码以及在哪里修改它。

有时候AI生成代码的最大问题……是你根本不知道那里发生了什么。如果你不得不亲自动手修复它,那就变成了一项不可能完成的任务

下次我们将调整最终应用并为新数据集生成FAISS数据库

接下来,如何合并现有图像和json索引以增加我们伪造生成的样本量。


原文链接: Image generation without Generation: the power of semantic search — part 3

汇智网翻译整理,转载请标明出处