- 王紹宇
- 約 3,700 文字
- 700 View
夏の自由研究連載 2024の 2 日目です。
Slack のカスタム絵文字を AI で遊びました。
前書き
夏の自由研究連載 2023 の「Sentence-Transformers を使って YouTube 動画のセリフを検索する」では、テキストのセマンティック検索や文字資料に対して Q&A に注目してデモしました。しかし Sentence-Transformers は、テキストだけではなく、画像や音声など異なる情報の組み合わせ、いわゆる マルチモーダル(multi-modal) AI のモデルです。
そのため、同一のモデルで、文字と画像を意味的な関連性を利用して、同一のベクトル空間に埋め込み(Embedding)することで、数学の計算(ベクトルの cosine 距離など)で近似度の評価が実現できます。
テキストと画像を検索のインプットとアウトプットの組み合わせで、以下の応用場面を例として挙げられます。
| 検索キーワード | 検索結果 | 応用場面 |
|---|---|---|
| テキスト | テキスト | 意味的近似の文章検索(前回の文字のセマンティック検索はここ) |
| テキスト | 画像 | リギャラリーから文字で画像を検索 |
| 画像 | テキスト | 写真やイラストから、知らない動植物や星座の分類を検索 |
| 画像 | 画像 | 近似画像の検索、関連度分析 |
今回は、前回割愛した画像に関するセマンティック検索について実践します。入力として Slack のカスタム絵文字を扱います。
仕事上でもよく使われる Slack では、ユーザが小さな画像もしくは画像化した文字をカスタム絵文字として自由にアップロードできます。ドアが開けっぱなしに思えますが、現在我々使っている Slack ワークスペースに、5000 個以上のカスタム絵文字が存在し、また日々増え続けており、重複や類似の絵文字がたくさん存在します。
また、アップロード時に絵文字の名称を指定する必要がありますが、一意であれば良いので少し雑な命名も多いです。名前での検索しても、使いたい絵文字はなかなか見つからないこともしばしばです。
例えば、↓ 最初のaから始まる絵文字を紹介します。具体例を見れば雰囲気がわかると思います。
| Name | Icon |
|---|---|
| applied | ![]() |
| approve_done | ![]() |
| ara | ![]() |
| arara | ![]() |
| arch_alert_mentioin | ![]() |
| arch_todo | ![]() |
| ari | ![]() |
| ari_1 | ![]() |
| ari_あり | ![]() |
| ari2 | ![]() |
| arigataya | ![]() |
| arigatogozaimasu | ![]() |
| arigatou | ![]() |
| ariyori | ![]() |
| aroha | ![]() |
| aruaru | ![]() |
| arunomi | ![]() |
| asaka_朝霞 | ![]() |
| asakai | ![]() |
| aserazudeok | ![]() |
| ashita | ![]() |
| ashita_hennshinshimasu | ![]() |
| ashitore | ![]() |
| asu | ![]() |
| asu_onegai_shimasu | ![]() |
| at_mark | ![]() |
| atchannel_yamero | ![]() |
| atesaki_saisettei_alert_mentioin | ![]() |
| atode | ![]() |
| atodekakuninsimasu | ![]() |
| atsui | ![]() |
| attention | ![]() |
それになんとかしたい気持ちで、絵文字の重複検知、便利に使いたい絵文字を意味で検索できるために、セマンティック検索の AI 技術を応用してみました。
データの準備
まずは、ローカルでデータを扱うように、Slack カスタム絵文字をクローリングでダウンロードします。
絵文字を表示するときブラウザから投げだした API を fetch 句で拾って、以下になります。
(クレデンシャル情報やヘッダーなどを xxxx に書き換えています)。
fetch("https://edgeapi.slack.com/cache/xxxx/emojis/list?fp=ba&_x_num_retries=0", { |
レスポンスがページネーションされて、100 件ずつ取っていることが分かりました。
その挙動をシミュレートする Python のスクリプトを作って、全量の絵文字名と URL を yaml ファイルに保存します。
from requests import Session |
保存した yaml ファイルは以下のような形になります。
(うちの場合、5000+要素、約 770 KB)
- name: _doing |
yaml に記載してあった value を URL として、name を保存先のファイル(拡張子は URL を尊重する)として、ローカルにダウンロードします。
import os |
ファイルを正しくダウンロードできたことが確認できました。
画像データのベクトル化
Sentence-Transformers のこのページをベースとして参考して、画像をベクトルに Embedding しておきます。
※ ファイル数が多すぎますので、デモ用で 1000 ファイルのみ処理します。
import os |
実行結果です。
python3 visualize.py |
“clip-ViT-B-32”モデルで、1000 個の画像データをそれぞれ 512 次元のベクトルに変換できました。
そのベクトルを使って、画像間の距離計算、分類、検索など、色々なことができます。
その多次元のベクトルを主要要素を抽出して 2 次元に圧縮して、画面上に描画する方法を ChatGPT 先生に教えてもらいました。
たくさんオプションが教えられていますが、1 個目の TSNE でも良いでしょう。
早速描画してみましょう。
import matplotlib.pyplot as plt |
うんん、なるほどの感じですね。多すぎてみにくいですね。
ある程度集団化(クラスタ)になっている傾向が見えます。
空間的分類クラスタ
plotly を利用して、3D でも描画しようと思います。
今回は、クラスタを明らかにするため、K-means を使って、適当に 7 色でクラスタに塗りましょう。
from sklearn.cluster import KMeans |
さっきのポケモンシリーズのクラスタを左になるように回転しておきました。
結果はブラウザ上で閲覧しています。ズームや回転の操作はこんな感じです。
ファイル名の表示を隠して、細かく 20 分類(num_clusters = 20)にしてみたら、こんな感じです。
画像で画像を検索
近似画像を検索する手法は、データと同じくクエリ用の画像の特徴ベクトルを計算(Embedding)し、既存データのベクトルの類似度(今回のモデルは cosine 距離)を計算し、類似度高い順で結果を取得するという流れです。
query_emb = model.encode(Image.open("images/白旗.png")) |
結果例 1
クエリ画像:images/白旗.png
ヒットした Top5 の画像:
Rank 1: Image Name: images/pose_seiza_man.png, Similarity Score 0.8798 |
今回のクエリ画像は、全体の 5000+の画像から選択した 1000 件に含まれていないため、Top1 がクエリ画像自身をヒットしていないです。
上位の 3 個のヒットは同一キャラクタのことが分かりますね。
4 番目のヤンキー少年と 5 番目のしゃちくさんは混入していますね。
真面目の青い服の青年の B サイド のことでしょう。ww
結果例 2
クエリ画像:images/天才.png
ヒットした Top5 の画像:
Rank 1: Image Name: images/さすが.png, Similarity Score 0.9758 |
近似度みんな高い(0.95 以上)です。画像の中の文字の意味が理解していない様子です。
印鑑系丸くて赤い文字が特徴でしょう。ただ、最後の「作業中」は丸くもないですけど。
肩トレの「レ」や「肉」文字の外側、ちょっと丸くなっている感が見えているのでしょう。
文字で画像を検索
上記画像から画像の検索と似たように、同じモデルで、文字列も同様な Embedding 処理ができます。
ちょっと微修正したら、文字で画像を検索することが実現可能です(マルチモーダルのスゴさ)
ちなみに、日本語でも検索できるようにしたい場合は、多言語対応の“clip-ViT-B-32-multilingual-v1”モデルを使ったほうがいいそうです。
ただ、画像の Embedding は“clip-ViT-B-32”のままを使用するのが正しいです。
model2 = SentenceTransformer("clip-ViT-B-32-multilingual-v1") |
ただ、文字が画像の中身を抽象的な名称では、あまりヒットしなく、画像の形状や色、物体の表現のほうがヒットしやすいです。
例えば、以下具体の結果例:
クエリ文字: "ポケモンボール" |
以下の色や形状の表現ならヒットします(ただ、近似度 0.26 は高くないです)。
クエリ文字: "上は赤、下は白、球体" |
ただし、下記の特定な名称では、またヒットします。
おそらく、当時の訓練用のデータに依存していることでしょう。
クエリ文字: "ビーチボール" |
最後に、みんなが大好きな「ビール」でテストしてみましょう。
クエリ文字: "ビール" |
また笑い話になりますが、トップでヒットしたのは「ビー」の部分でした!
2 番目はひよこちゃんがビール持っているし、「あ缶」はまあまあ缶ビールに関連しているでしょう。
4 番目は芸能人の手のジェスチャーが間違えてビールを持っているように間違えたかなと思います。最後のやっつけは? 言われてみれば、その背景がビールのと泡の色や状態にちょっと似ているかもしれません。
また、今回のデモでは、1 個のクエリベクトルに対して、全データ(1000 個)のベクトルとの近似度(Cosine Similarity)が計算して、ソートをかけてしまいました。もし事前にインデックスを立てたり、近似な結果も許されたりする場合、k-近傍法(k-NN)のもっと効率的なアルゴリズムもあります(割愛)
まとめ
2023年の記事の続きとして、事前訓練された(pre-trained)マルチモーダル(multi-modal)の AI モデルを使った画像処理を実践してみました。
Slack のカスタム絵文字のデータを利用して、関連性によってクラスタ化して、可視化グラフを書いてみました。
人間の好みが関与したデータとして、ベクトル空間上で、均一にランダムの分散するではなく、集団化(クラスタ化)で分布していることが直感的に分かりました。
また、セマンティック検索について、検証の結果は指標化までできていませんが、直感的に、
- 「画像 → 画像」の検索は、なんとなく精度が悪くなく
- 「文字 → 画像」の検索はすぐには実用化とはちょっと微妙
そして、近似データや重複データの検知や排除する手法として、他の領域へも応用できそうだと思います。































