コミュニティの皆さんこんにちは。
ベクトル検索関連の処理が完全にノーマークだった私が、一先ず「やってみよう!」との事で、2つの動画のサンプルを実行してみました。
Pythonは初心者なので、アレな箇所があっても目をつぶっていただけると幸いです。
また、間違っている箇所があったら、ご指摘いただけると幸いです。
■参考にした動画
■参考にしたコミュニティ記事
【目的】
本記事では、動画で紹介された内容を実際にIRIS環境上で実行できるよう、具体的な環境構築とコーディングを記載致します。
コミュニティの皆さんが簡単に試せるようになれば幸いです。
またGithubにサンプルソースを配置しているので、必要な方は参考にして下さい。
【準備】
■作業環境
※環境作成方法に問題のない方は、読み飛ばしていただいて構いません。
🔸IRISのライセンス
ベクトル検索を行うので、ライセンス関連からCommunityを選択しました。入手先はココです。
🔸VS Code
IRIS 2025.2は、既にApache Webサーバとスタジオが廃止されています。
そのため、IISとVS Codeをインストールする必要があります。
VS Codeの入手先はココからで、設定方法はコミュニティの記事を参照してください。
🔸Python
IRIS 2025.2.0.227.0でPython3.14は動作しません(この先のバージョンに期待です)。
Python3.13はインストールするライブラリが動作しないようで、3.12系を使用する事になりました。
Python 3.12.10の入手先はココからで、設定方法はコミュニティの記事を参照してください。
■Pythonライブラリのインストール
Pythonの実行に使用したライブラリをインストールします。
コマンドプロンプトにて実行して下さい。
rem データセット用
pip install datasets==2.19.0
pip install tensorflow
pip install tensorflow-datasets==4.8.3
rem 検索・IRISデータ作成用
pip install pandas
pip install sentence_transformers
pip install tf-keras
pip install requests
pip install sqlalchemy-iris
rem 両方で使用
pip install pyarrow
インストールしたライブラリの中に、IRISと接続する「sqlalchemy-iris」があります。
詳細はコミュニティの記事を参照してください。
■ IRIS側はライブラリの追加は不要です。
もし動かない場合は、「python -m pip –-target <iris_dir>\mgr\pytyon <module>」でインストールしてください。
【実行】
■「ベクトル検索のご紹介」編
<<テストデータの作成>>
先ずは、Hugging Faceより、cc100のデータセットをダウンロードします。
import os
os.environ["HF_DATASETS_CACHE"] = "D:\\Python\\HuggingFaceCache"
from datasets import load_dataset
dataset = load_dataset("cc100", lang="ja", trust_remote_code=True)
output_path = "D:\\Python\\cc100_Parquet\\cc100-ja_sharded2.parquet"
dataset["train"].to_parquet(output_path)
cc100はかなりファイルが大きく、HuggingFaceCacheは160GB parquetファイルは44.5GBになり、合わせて204.5GBの空き容量が必要になります。
また、作成時間も余裕の10時間オーバーなので、気長に覚悟を持ってお待ちください。
※2回目からはCacheが効いているため、多少早くなります。
後々、2.02GBのparquetファイルの存在を知りました。
ココからダウンロードしても問題ないと思います。
<<IRISデータ作成>>
先ほど作成したparquetファイルから10万件読み込み、モデル「stsb-xlm-r-multilingual」を使いベクトル化してIRISに保存しています。
import pandas as pd
from sentence_transformers import SentenceTransformer
from sqlalchemy import create_engine, text
from pyarrow.parquet import ParquetFile
import pyarrow as pa
pf = ParquetFile(r"D:\Python\cc100_Parquet\cc100-ja_sharded.parquet")
first_rows = next(pf.iter_batches(batch_size = 100000))
df = pa.Table.from_batches([first_rows]).to_pandas()
df = df.replace("\n", "", regex=True)
model = SentenceTransformer('stsb-xlm-r-multilingual')
embeddings = model.encode(df['text'].tolist(), normalize_embeddings=True)
df['text_vector'] = embeddings.tolist()
username = '_system'
password = 'SYS'
hostname = 'localhost'
port = 1972
namespace = 'USER'
CONNECTION_STRING = f"iris://{username}:{password}@{hostname}:{port}/{namespace}"
engine = create_engine(CONNECTION_STRING)
with engine.connect() as conn:
with conn.begin():
sql = f"""
CREATE TABLE vectortest(
contents VARCHAR(4096),
contents_vector VECTOR(DOUBLE, 768)
)
"""
result = conn.execute( text(sql) )
with engine.connect() as conn:
with conn.begin():
for index, row in df.iterrows():
sql = text(
"""
INSERT INTO vectortest
(contents, contents_vector)
VALUES (:contents, TO_VECTOR(:contents_vector))
"""
)
conn.execute(sql, {
'contents': row['text'],
'contents_vector': str(row['text_vector'])
})
<<動作検証(Python)>>
ベクトル検索する際は、検索したい文字列を同じモデルでベクトル化し、ドット積(VECTOR_DOT_PRODUCT)を求めます。
import sys
import pandas as pd
from sentence_transformers import SentenceTransformer
from sqlalchemy import create_engine, text
args = sys.argv
contents_search = args[1]
model = SentenceTransformer('stsb-xlm-r-multilingual')
search_vector = model.encode(contents_search, normalize_embeddings=True).tolist()
username = '_system'
password = 'SYS'
hostname = 'localhost'
port = 1972
namespace = 'USER'
CONNECTION_STRING = f"iris://{username}:{password}@{hostname}:{port}/{namespace}"
engine = create_engine(CONNECTION_STRING)
with engine.connect() as conn:
with conn.begin():
sql = text("""
SELECT TOP 5 contents, VECTOR_DOT_PRODUCT(contents_vector, TO_VECTOR(:search_vector, double, 768)) as sim FROM vectortest
ORDER BY sim DESC
""")
results = conn.execute(sql, {'search_vector': str(search_vector)}).fetchall()
result_df = pd.DataFrame(results, columns=['contents', 'sim'])
pd.set_option('display.max_colwidth', None)
print(result_df)
使い方は、コマンドプロンプトにて下記コマンドを実行します。
python vectorsearch.py 大都市での生活は便利な反面、混雑や環境の悪さなどの問題もある。
<<動作検証(IRIS)>>
Pythonライブラリは、XDataブロックで読み込みました。
※各関数でライブラリを読み込むのが面倒だっただけです
XData %import [ MimeType = application/python ]
{
import iris
import pandas as pd
from sentence_transformers import SentenceTransformer
from pyarrow.parquet import ParquetFile
import pyarrow as pa
import datetime
}
ベクトル検索はObjectScriptで記述しています。
また、検索文字列をベクトル化する関数「Comvert()」はPythonで記述しています。
ObjectScriptとPythonを混合して利用できるのは便利ですよね。
ClassMethod Search(txt As %String = "")
{
q:($g(txt)="")
s txt = ..Convert(txt)
s txt = $tr(txt, "[]")
s query(1) = "select top 5 VECTOR_DOT_PRODUCT(contents_vector, TO_VECTOR(?, DOUBLE, 768)) as sim, contents"
, query(2) = "from vectortest order by sim desc"
, query = 2
w !,"実行"
s rset = ##class(%SQL.Statement).%ExecDirect(.stmt, .query, .txt)
while rset.%Next() {
w !,rset.%Get("contents")
}
}
ClassMethod Convert(text As %String) As %String [ Language = python ]
{
model = SentenceTransformer('stsb-xlm-r-multilingual')
search_vector = model.encode(text, normalize_embeddings=True).tolist()
return str(search_vector)
}
実行結果は下記になります。

<<IRISからのベクトルデータ登録>>
‼ IRISからPythonの関数を繰り返し実行する際は、Python処理に「gc.collect()」を加える必要がありました。
今回は、ループ処理の最後に追記しています。
ClassMethod makeData(count As %Integer = 100000) [ Language = python ]
{
pf = ParquetFile(r"D:/Python/cc100_Parquet/cc100-ja_sharded.parquet")
first_rows = next(pf.iter_batches(batch_size = count))
df = pa.Table.from_batches([first_rows]).to_pandas()
df = df.replace("\n", "", regex=True)
for index,row in df.iterrows():
txt = row['text']
iris.cls('dev.Vector').saveData(txt)
gc.collect()
}
ClassMethod saveData(text As %String)
{
s cnvTxt = $tr(..Convert(text), "[]")
&sql(insert into dev.SearchData (txt, vec) values(:text, TO_VECTOR(:cnvTxt, double, 768)))
}
この処理(gc.collect)を入れないと、Python関数を呼び出す度に使用メモリ量が増加していき、最終的にはエラーが発生して処理が終了しました。
→ 16GBのメモリ量では、30件のレコード登録すら行えませんでした。
→ エラーの内容は、<Session disconnected>です。
【エラー発生直前のメモリ使用量の推移】
.png)
対策を行うとメモリの開放が都度行われて、エラーになることなく処理が完了しました。
.png)
皆様も、Pythonの関数を何度も呼び出す際は、メモリの使用量にお気を付けください。
■「テキストから画像検索」編
<<前程>>
Hugging faceのドキュメントを読むと、画像のエンコード方法は下記記述になるようです。
img_emb = model.encode(Image.open('two_dogs_in_snow.jpg'))
文字検索時は、引数に「normalize_embeddings=True」が追加していましたが、今回は付与されていません。
調べてみると、デフォルト値は「False」で、下記意味があるようです。
今回は、引数無しのデフォルト値(False)なので、コサイン類似度(VECTOR_COSINE)を使用すれば良いと考えます。
<<テストデータの作成>>
先ずは、Hugging Faceより、画像のデータセットをダウンロードします。
import os
os.environ["HF_DATASETS_CACHE"] = "D:\\Python\\HuggingFaceCache_image"
from datasets import load_dataset
dataset = load_dataset("recruit-jp/japanese-image-classification-evaluation-dataset")
output_path = "D:\\Python\\image\\recruit-jp.parquet"
dataset["train"].to_parquet(output_path)
cc100とは異なり容量の暴力性は少ないです。Cacheで2.36MB、parquetファイルで285KBしかありません。
データセットの中身は画像のurl等で、文字列のみです。
<<IRISデータクラスの作成>>
Class dev.ImageData Extends %Persistent [ SqlRowIdPrivate ]
{
Parameter USEEXTENTSET = 1
Index DDLBEIndex [ Extent, SqlName = "%%DDLBEIndex", Type = bitmap ]
Index HNSWIndex On (imgvec) As %SQL.Index.HNSW(Distance = "Cosine") [ SqlName = HNSWIndex, Type = bitmap ]
Property url As %String(MAXLEN = 256) [ SqlColumnNumber = 2 ]
Property imgvec As %Vector(DATATYPE = "float", LEN = 512) [ SqlColumnNumber = 3 ]
}
<<IRISデータ作成>>
先ほど作成したparquetファイルを読み込み、urlから画像を取得し、モデル「clip-ViT-B-32」でベクトル化してIRISに保存しています。
また、コサイン類似度を使用する為、encode()時の引数にnormalize_embeddingsは指定していません(default=False)。
📒いくつかのurlは画像が存在していませんでした。
そのため、画像が存在しないurlは除外する処理を入れました。
from pyarrow.parquet import ParquetFile
import pyarrow as pa
from sentence_transformers import SentenceTransformer
from sqlalchemy import create_engine, text
from PIL import Image
import requests
pf = ParquetFile(r"D:\Python\image\recruit-jp.parquet")
first_rows = next(pf.iter_batches())
df = pa.Table.from_batches([first_rows]).to_pandas()
df = df.replace("\n", "", regex=True)
print(df.head(5))
def load_image(url_or_path):
try:
urls = url_or_path.split('_o')
newUrl = urls[0] + '_b' + urls[1]
if url_or_path.startswith("http://") or url_or_path.startswith("https://"):
return Image.open(requests.get(newUrl, stream=True).raw)
else:
return Image.open(url_or_path)
except Exception as e:
print(repr(e) +":"+ url_or_path)
return ''
imgModel = SentenceTransformer('clip-ViT-B-32')
images = []
urlList = []
imgVec = []
count = 0
for img in df['url']:
count += 1
imgObj = load_image(img)
if not imgObj == '':
images.append(imgObj)
urlList.append(img)
else:
print(str(count))
embeddings = imgModel.encode(images)
imgVec = embeddings.tolist()
print(df.head(5))
username = '_system'
password = 'SYS'
hostname = 'localhost'
port = 1972
namespace = 'TESTAI'
CONNECTION_STRING = f"iris://{username}:{password}@{hostname}:{port}/{namespace}"
engine = create_engine(CONNECTION_STRING)
with engine.connect() as conn:
with conn.begin():
for vec, url in zip(imgVec, urlList):
sql = text(
"""
INSERT INTO dev.ImageData (url, imgvec)
VALUES (:url, TO_VECTOR(:imgvec))
"""
)
conn.execute(sql, {
'url': url,
'imgvec': str(vec)
})
<<動作検証(Python)>>
こちらもhuggingface のドキュメントを参照すると、使い方が記載されています。
エンコード方法と検索方法は「コサイン類似度(VECTOR_COSINE)」を使用すると記載があります。
text_model = SentenceTransformer('sentence-transformers/clip-ViT-B-32-multilingual-v1')
text_embeddings = text_model.encode(texts)
cos_sim = util.cos_sim(text_embeddings, img_embeddings)
ドキュメントに沿って文字列をモデル「sentence-transformers/clip-ViT-B-32-multilingual-v1」でベクトル化し、コサイン類似度で検索を行います。
import sys
import pandas as pd
from sentence_transformers import SentenceTransformer
from sqlalchemy import create_engine, text
args = sys.argv
contents_search = args[1]
txtModel = SentenceTransformer('sentence-transformers/clip-ViT-B-32-multilingual-v1')
search_vector = txtModel.encode(contents_search).tolist()
print(len(search_vector))
username = '_system'
password = 'SYS'
hostname = 'localhost'
port = 1972
namespace = 'TESTAI'
CONNECTION_STRING = f"iris://{username}:{password}@{hostname}:{port}/{namespace}"
engine = create_engine(CONNECTION_STRING)
with engine.connect() as conn:
with conn.begin():
sql = text("""
SELECT TOP 5 url, VECTOR_COSINE(imgvec, TO_VECTOR(:txtvec, float, 512)) as sim FROM dev.ImageData
ORDER BY sim DESC
""")
results = conn.execute(sql, {'txtvec': str(search_vector)}).fetchall()
result_df = pd.DataFrame(results, columns=['sim', 'url'])
print(result_df)
使い方は、コマンドプロンプトにて下記コマンドを実行します。
python imagesearch.py 黄色い花
<<動作検証(IRIS)>>
文字列から「画像」を検索する試みです。
検索した画像をブラウザで参照したいと思い、RESTサービスを使ってブラウザに結果を返却するようにしました。
ブラウザ側へ検索結果を返すため、%DynamicArrayを利用しています。
ClassMethod SearchImg(txt As %String) As %DynamicArray
{
q:($g(txt)="")
s txt = ..ConvertImg(txt)
s txt = $tr(txt, "[]")
s query(1) = "SELECT TOP 5 url, VECTOR_COSINE(imgvec, TO_VECTOR(?, float, 512)) as sim"
, query(2) = "FROM dev.ImageData order by sim desc"
, query = 2
s ary = []
s rset = ##class(%SQL.Statement).%ExecDirect(.stmt, .query, .txt)
while rset.%Next() {
s url = $replace(rset.%Get("url"),"_o.jpg","_b.jpg")
d ary.%Push({
"imgid":(url),
"sim":(rset.%Get("sim"))
})
}
q ary
}
文字列をベクトル化する関数はPythonで記述します。
ClassMethod ConvertImg(txt As %String) As %String [ Language = python ]
{
text_model = SentenceTransformer('sentence-transformers/clip-ViT-B-32-multilingual-v1')
text_embeddings = text_model.encode(txt).tolist()
return str(text_embeddings)
}
RESTクラス(抜粋)
XData UrlMap
{
<Routes>
<Route Url="/sample" Method="GET" Call="GetImage" Cors="true"/>
</Routes>
}
ClassMethod GetImage() As %Status
{
s search = $g(%request.Data("searchTxt",1))
s start = $zh
, getImgs = ##class(dev.Vector).SearchImg(search)
, end = $zh
d ##class(%REST.Impl).%WriteResponse({
"image": (getImgs),
"time" : (end-start)
})
q $$$OK
}
後はブラウザを起動して確認します。
検索文字列は、「黄色い花」「中華料理」「インド料理」「赤い花」「山」で試しました。
検索結果の画像に、「何故それが検索されたのか?」ってのが、紛れていますね🤣
一先ず再現が出来たっぽいので、良しとさせて下さい。
【最後に】
今回は「やってみよう!」の精神で、IRISのベクトル検索に挑戦してみました。
初めて触れると少し複雑に感じる部分もありましたが、一度動作が確認できるとその仕組みの面白さが実感できます。
また、今回の記事を通じて、Pythonや各種モデルのドキュメントにも触れることができ、多くの学びを得ることができました。
この記事を通して、IRISのベクトル検索機能に触れる切っ掛けになれば幸いです。
後は、もっと気軽に活用できるよう、ライセンスの方も何とかして欲しいです。
触って感じましたが、素晴らしい機能だと思います。