Avatar

Harper Reed's Blog

I accidentally built a meme search engine

· 350 単語 · 2 分 ·

他の言語で読む: English 🇺🇸

別名:CLIP/SigLIP と画像ベクトルエンコーディングを学ぶ方法

tl;dr: SigLIP/CLIP を使ってミーム検索エンジンを作った。めちゃ楽しかったし、学びも山盛りだった。

ここしばらく実用寄りの AI ツールを作りまくっているが、その中でもいちばん “魔法” 感があるのがベクトル埋め込みだ。Word2Vec を初めて触ったときなんて本当にブッ飛んだ。

先日、Hacker News で見かけたシンプルなアプリマジで凄かった。誰かが Tumblr の画像をかき集めて SigLIP で埋め込みを作り、「画像をクリックすると似た画像が並ぶ」だけのアプリを公開していた。どうやればいいか皆目見当もつかなかったけど、手が届きそうにも見えた。

この勢いで「全部理解してやるか」と走り始めた。

wut(何それ?)

ベクトル埋め込み、CLIP/SigLIP などのマルチモーダル埋め込み、ベクトルデータベース……初見でもビビる必要なし。

あの HN のハックを見るまでは、俺もベクトルのことなんて深く考えたことがなかった。FAISS(Facebook 製のシンプルなベクトル DB)や Pinecone($$)を「動けば OK」で使った程度。テストが通ったら「はい終わり」。

ぶっちゃけ今でも「ベクトルって何だよ」って思ってる。Lol. RAG とか LLM 以外でどう使うかも、今回作るまでピンと来ていなかった。

俺は「作りながら学ぶ」派だ。結果が面白ければ燃えるし、今回はほんと魔法だった。

WTF 用語集

公開前に友人に読んでもらったら「X って何?」が多発したので、当時ほぼ初見だったキーワードをざっくりまとめておく。

シンプルにいこうぜ、harper

これはただの簡単ハックだ。テキトーにいじってるだけだからスケールなんて気にしない。でも あなた が再現できることは重視した。

目標その 1:ぜんぶ Mac ローカルで回す。せっかくの Apple シリコン GPU をフル回転させよう。

まずは画像ディレクトリをクロールするクソ雑なクローラを書く。Apple Photos 派なので手元に写真フォルダがない。そこで秘密のミームチャットから 1 万枚超のミームをエクスポートしてテスト画像にした。

クローラ

世界一ひどいクローラを書いた。いや正確には、俺の指示で Claude が書いた “世界一ひどいクローラ” だ。

流れはこんな感じ:

  1. 対象ディレクトリのファイル一覧を取得
  2. その一覧を msgpack に保存
  3. msgpack を読みつつ全画像を走査し、SQLite にメタデータを突っ込む
    • ハッシュ
    • ファイルサイズ
    • パス(location)
  4. SQLite を回しながら CLIP で各画像をエンコード
  5. 得たベクトルをまた SQLite に書き戻す
  6. さらに SQLite を回し、ベクトル+画像パスを ChromaDB に投入
  7. 完了

ムダ多すぎるのは百も承知。画像→埋め込み→ChromaDB に一発で突っ込めばいい。でもこんな構成にした理由は:

多少ゴチャついてても結果は優秀。20 万枚超えてもトラブルなしでクロールできた。

埋め込みシステム

画像エンコードは楽しい。

まず SigLIP で シンプルな Web サービス を立てた。スタジオの GPU マシンで回したら速いとは言えないが、ローカルの OpenCLIP よりははるかに速かった。

それでもローカル完結させたい。そこで Apple の ml-explore リポジトリを思い出す。中にある CLIP 実装 が爆速。大きめモデルでも RTX 4090 より速い。意味がわからんほど速い。

あとはスクリプトから簡単に呼び出せれば OK。

MLX_CLIP

Claude と一緒にサンプルをいじって、どの Mac でも動く小さな Python クラスにした。モデルがなければ自動で DL&変換、即推論。

リポジトリはこちら: https://github.com/harperreed/mlx_clip

出来栄えにけっこう満足。言わずもがなだけど、Apple シリコンはマジで速い。

使い方はこんな感じ:

import mlx_clip

# モデルを初期化
clip = mlx_clip.mlx_clip("openai/clip-vit-base-patch32")

# 画像をエンコード
image_embeddings = clip.image_encoder("assets/cat.jpeg")
print(image_embeddings)

# テキストをエンコード
text_embeddings = clip.text_encoder("a photo of a cat")
print(text_embeddings)

SigLIP 版も動かしたい(CLIP よりはるかに良いと思っている)が、これは POC だし保守する気はない。SigLIP で動かすヒントがあったら連絡ちょうだい(hmu)。OpenCLIP を再発明したくはないし、理論上 Apple シリコンでも走るはずなんだよね。

さて次は

画像ベクトルを ChromaDB にブチ込んだら、あとは UI。基準画像のベクトルを抜いて ChromaDB に投げる → 類似度順に画像 ID が返る。

これらを Tailwind と Flask で包んで一気に Web 化した。これが自分でも驚くほどイケてた。
2015 年なら何人月かかったかわからんが、今回はトータル 10 時間。笑うしかない。

結果はガチで魔法。

ミーム概念検索

最初のデータセットはミーム。約 12,000 枚。

まずこれ:

So true

エンコードして ChromaDB に投げると……

もう一例:

返ってくるのがこれ:

クリックしてるだけで無限に遊べる。

「Namespaces?」って何だよ、みたいな話

「画像クリック→類似画像」はクールだけど、俺の脳を吹き飛ばしたのは “同じモデルで検索テキストもベクトル化し、似た画像を返せる” ことだ。テキスト→画像のマルチモーダル検索は完全に手品。

いくつか例を挙げよう。

money で検索:

AI で検索:

red で検索(色? ライフスタイル? それともロシア?):

延々と遊べる。忘れてたお宝ミームもザクザク。
「ブログ執筆のミーム欲しいな」と思ったら──

(自覚はあるけど気にしない w)

写真ライブラリで試すと?

めちゃくちゃいい感じに動く。

自分のフォトライブラリで試すのを激しくオススメする。俺は Google Photos の Takeout を落として外付けディスクに展開。重複だらけだからスクリプトで整理し、ミームフォルダの代わりにそのディレクトリをクロール。

写真 140k 枚で約 6 時間。悪くない。結果は最高。

例いろいろ

そっくり写真(Google Photos の重複問題はご愛敬)

うちのプードルたち

ランドマーク検索:飛行機から撮った富士山に自分でも気づいてなかった

そこから類似の富士山写真

場所もラクラク

感情系。「驚き顔」がこんなにあったとは

ニッチなテーマ、ローライダーのクルマ(渋谷で撮影)

検索しづらい「ボケ味」も一発

忘れてた名ショットも救出。2017 年に撮った Baratunde:

これはそのうち全部のアプリに入る

近いうちに主要フォトアプリは絶対この機能を積む。Google Photos にはもう入ってるかもしれないけど、Google がゴチャゴチャ手を入れすぎて誰も気づいてないだけかも。

画像を扱うプロダクトを持ってるなら、今すぐパイプライン組んでエンコードを始めた方がいい。

無料で使えるヨ!(原文見出し: “YOU CAN USE THIS FOR THE LOW PRICE OF FREE”)

ソースはここ: https://github.com/harperreed/photo-similarity-search

ぜひ触ってみてくれ。ちょっとハック感あるけど動く。
conda などで環境を分けると楽。UI は Tailwind、サーバは Flask、コードは Python。ホストは harper reed でした。

キミへのチャレンジ!

俺のフォトライブラリを快適にカタログ化できる Mac ネイティブアプリを作ってくれ。クラウドに上げるのはイヤ。ライブラリを指定して「クロール開始」押すだけ、みたいなのが理想。

全部ローカルで動いて、シンプルで強力。Lightroom、Capture One、Apple Photos と連携できたら最高だ。

これ、マジで欲しい。誰か作って!

追加課題:Lightroom プレビュー JPEG 復旧

ハック仲間の Ivan もこの魔法を見て即参戦。彼の写真は外付け HDD にあるが、Lightroom のプレビューファイルはローカルにあった。そこでサムネとメタデータを抜き出して外付けに保存するスクリプトを書いた。

そのあと画像ベクトルクローラを回したら類似検索も問題なく動いた。BAM──完璧。

Lightroom の写真を救出 — サムネだけでも

Ivan のシンプルスクリプトは超便利。もし本体ライブラリが吹っ飛んでも .lrprev が残っていれば低解像度版だけでも救出できる。
リポジトリ: https://github.com/ibips/lrprev-extract
(訳注: 原文では “Light Room” と表記されているが、正しくは “Lightroom”。)

Thanks for reading.

いつでも連絡ちょうだい(hmu: harper@modest.com)―AI、EC、写真、Hi-Fi、ハック、何でも語ろう。
シカゴにいるならぜひ遊びに来て。

この投稿は98%人間が執筆しました。