Pesquisar

Artigo
· Out. 26 14min de leitura

 ベクトル検索のサンプルをやってみた

コミュニティの皆さんこんにちは。
 

ベクトル検索関連の処理が完全にノーマークだった私が、一先ず「やってみよう!」との事で、2つの動画のサンプルを実行してみました。
Pythonは初心者なので、アレな箇所があっても目をつぶっていただけると幸いです。

また、間違っている箇所があったら、ご指摘いただけると幸いです。



■参考にした動画

■参考にしたコミュニティ記事

 

【目的】

本記事では、動画で紹介された内容を実際にIRIS環境上で実行できるよう、具体的な環境構築とコーディングを記載致します。
コミュニティの皆さんが簡単に試せるようになれば幸いです。

またGithubにサンプルソースを配置しているので、必要な方は参考にして下さい。

 

【準備】

■作業環境

※環境作成方法に問題のない方は、読み飛ばしていただいて構いません。

 

項目 バージョン情報・他
OS WIndowsServer2019
IRIS IRIS Community 2025.2.0.227.0
Python 3.12.10
開発環境 VS Code 1.105.1

 

🔸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" # Cache保存先指定
from datasets import load_dataset

dataset = load_dataset("cc100", lang="ja", trust_remote_code=True)
#getlist = dataset["train"].select(range(100000)) # レコード数を制限する場合は開放する
output_path = "D:\\Python\\cc100_Parquet\\cc100-ja_sharded2.parquet" # parquetファイル保存先
dataset["train"].to_parquet(output_path)
#getlist.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

# parquetファイル読み込み
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()

# IRISへ接続
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()

# IRISへ接続
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>です。

 

【エラー発生直前のメモリ使用量の推移】

 

対策を行うとメモリの開放が都度行われて、エラーになることなく処理が完了しました。

 

皆様も、Pythonの関数を何度も呼び出す際は、メモリの使用量にお気を付けください。

 

 

■「テキストから画像検索」編

 

<<前程>>

Hugging faceのドキュメントを読むと、画像のエンコード方法は下記記述になるようです。

img_emb = model.encode(Image.open('two_dogs_in_snow.jpg'))

文字検索時は、引数に「normalize_embeddings=True」が追加していましたが、今回は付与されていません。

調べてみると、デフォルト値は「False」で、下記意味があるようです。

normalize_embeddings 説明
True ドット積を使用する(高速)
False コサイン類似度を使用する

 

今回は、引数無しのデフォルト値(False)なので、コサイン類似度(VECTOR_COSINE)を使用すれば良いと考えます。

 

<<テストデータの作成>>

先ずは、Hugging Faceより、画像のデータセットをダウンロードします。

import os
os.environ["HF_DATASETS_CACHE"] = "D:\\Python\\HuggingFaceCache_image" # Cacheの出力先
from datasets import load_dataset

dataset = load_dataset("recruit-jp/japanese-image-classification-evaluation-dataset")

output_path = "D:\\Python\\image\\recruit-jp.parquet" # 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 = index ];
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(batch_size = 50))
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)) # id, license, license_urll, url, category
# 画像化
def load_image(url_or_path):
    try:
        urls = url_or_path.split('_o')
        newUrl = urls[0] + '_b' +  urls[1] # #newUrl = urls[0] + '_c' +  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 = [load_image(img) for img in df['url']]
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)

# Compute cosine similarities:(コサイン類似度を計算します。)
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))

# IRIS接続
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のベクトル検索機能に触れる切っ掛けになれば幸いです。

 

 

 

後は、もっと気軽に活用できるよう、ライセンスの方も何とかして欲しいです。
触って感じましたが、素晴らしい機能だと思います。
 

Discussão (0)1
Entre ou crie uma conta para continuar
Pergunta
· Out. 25

Stream SVG to load in Logi Reports

How do I take SVG data to be an image in Logi Reports? Now I can take JPG data and render it in Logi Reports. And I can take the SVG data in ObjectScript, but when I view it in Logi Reports, it won't appear. How do I get the code in ObjectScript to appear and be read in Logi Reports?

Discussão (0)1
Entre ou crie uma conta para continuar
Pergunta
· Out. 24

How can I view the contents of the request object being sent in my BPL in Message Viewer Body?

I am looking to view the contents of the request object I am sending from my Service into my BPL in Message Viewer. My request contains references to other defined classes.

Class bd.webapp.OutboundMessageRequest Extends (%Persistent, Ens.Request)
{

Property Metadata As bd.webapp.OutboundMessageRequestMetadata;
Property MessageContent As %DynamicObject;
}



Class bd.webapp.OutboundMessageRequestMetadata Extends (%RegisteredObject, %JSON.Adaptor)
{

Property RegionSlug As %String;
Property TenantSlug As %String;
Property SystemSlug As %String;
Property MessageId As %String;
Property MessageType As %String [ InitialExpression = "JSON" ];
Property OccurredAt As %String;
Property CorrelationId As %String;
}

How can I view the contents of a property that is a custom class object in my Message Viewer Body?

I have tried to edit the %ShowContents method but have not been able to successfully showcase the "Metadata" property.

Method %ShowContents(pZenOutput As %Boolean = 0)
{
    If $ISOBJECT(..MessageContent) {
        Set json = ..MessageContent.%ToJSON()
        set formatter = ##class(%JSON.Formatter).%New()
        If pZenOutput {
            Write formatter.Format(json)
        } Else {
            Write json
        }
    } 

What I see on the Message Viewer:

Contents show the value of "MessageContent", but the value of "Metadata" is missing:

7 Comments
Discussão (7)5
Entre ou crie uma conta para continuar
Anúncio
· Out. 24

[Video] Building $ZF Modules in Rust with RZF

Hey Community!

We're happy to share a new video from our InterSystems Developers YouTube:

⏯  Building $ZF Modules in Rust with RZF @ Ready 2025
 

Noah, a developer on the DARPA team with Dave McCalden, introduces Rust ZF - a new Rust-based layer for the Iris ZF (Zero Functions) API. Traditionally, ZF allows calling in and out of IRIS using C, but it’s verbose and lacks type and memory safety.

The new Rust ZF adds an ergonomic, idiomatic Rust interface, making it easier and safer for developers to integrate Rust code with Iris. Using a simple macro (RZF), developers can define functions in Rust, build dynamic libraries, and load them into IRIS, or call IRIS methods directly from Rust. Noah demonstrates this with examples like math functions and real-time code execution.

He also shows how Rust’s ecosystem can integrate with IRIS, for example, using the Bevy game engine to create a Space Invaders demo that stores and retrieves game data through IRIS. Finally, he mentions ongoing work on an LLM MCP server that will be covered in a future presentation by Dave McCalden.

🗣 Presenter: @Noah Dohrmann, Developer DARPA team

Enjoy watching, and subscribe for more videos! 👍

3 Comments
Discussão (3)4
Entre ou crie uma conta para continuar
Pergunta
· Out. 24

EnsLib.Workflow.TaskRequest Customization Help

According to the Documentation  EnsLib.Workflow.TaskRequest has the following fields...

  • %Action
  • %Command
  • %FormFields
  • %FormTemplate
  • %FormValues
  • %Message
  • %Priority
  • %Subjext
  • %TaskHandler
  • %Title
  • %UserName

I want to be able to capture the Source, Session ID, and any other Identifiers outside of the Error so it will show up on the Task List.

I am struggling how to build a csp template for me to be able to capture additional fields to send to the Workflow Operation.

Does anyone have a good example outside of what is in the documentation on how to build out a Custom Form for this information to show up in the Task List?

Discussão (0)1
Entre ou crie uma conta para continuar