# Lance

[Lance](https://lance.org)は、AI向けのオープンなマルチモーダルなレイクハウステーブル形式です。Hugging Face パス（`hf://`）を使用して、Hub 上の Lance データセットにアクセスできます。これにより、Hugging Face Hub 上の大規模なデータセットをローカルにコピーすることなく、スキャンや検索を行うことができます。

## はじめる

まずはじめに、`pylance`と`pyarrow`をpipでインストールします:

```bash
pip install pylance pyarrow
```

## なぜLanceなのか

- ML/AIワークロード向けに最適化: Lanceは、スキャン性能を犠牲にすることなく高速なランダムアクセスを実現するために設計されたモダンなカラムナフォーマットであり、検索、分析、学習、特徴量エンジニアリングなど、多くのユースケースで有用です。
- マルチモーダルアセットは、Lanceにおいて埋め込みベクトルや従来のスカラーデータとともに、バイト列（バイナリオブジェクト、いわゆる「[ファイルとしてのブロブ](https://lance.org/guide/blob/)」）として保存されます。これにより、Hubを介して大規模なデータセットを管理・共有・配布しやすくなります。
- インデックスは第一級市民（フォーマット自体にネイティブ）です: Lanceには、高速でディスク上にスケーラブルな[ベクトル](https://lance.org/quickstart/vector-search)インデックスとFTSインデックスが同梱されており、Hub上のデータセットのすぐ隣に配置されるため、ユーザーが再計算する必要なく、データだけでなく埋め込みやインデックスも共有できます。
- 柔軟なスキーマと[データの進化](https://lance.org/guide/data_evolution)により、テーブル全体を書き直す必要が**なく**して、新しい機能やカラム（モデレーションタグ、埋め込みなど）を段階的に追加できます。

## すべてのデータを1つの場所に保存

Lanceでは、マルチモーダルなデータ資産（画像、音声、動画）が、スカラー型のメタデータや埋め込みベクトルとともに生バイトとして保存されます。これにより、複数のストレージシステムを組み合わせる必要なく、1つの場所でデータセットのスキャンとフィルタリングを簡単に実行できます。

## `datasets`でHubからストリーミング

`load_dataset(..., streaming=True)` を使用すると、データをローカルにダウンロードせずにスキャンして反復処理できます。

```python
from datasets import load_dataset

# Return as a Hugging Face dataset
ds = load_dataset(
    "lance-format/laion-1m",
    split="train",
    streaming=True
)
# Take first three rows
for row in ds.take(3):
    print(row["caption"])
```

ストリーミングは、データの内容を理解するためのメタデータのサンプリングに最適です。ベクトル検索や大規模なバイナリブロブの処理には、以下で説明する Lance の `dataset` API を使用できます。

> [!WARNING]
> ストリーミングは、単純なスカラーメタデータのサンプリングには高速ですが、埋め込みベクトルや大規模なマルチモーダルアセットの取得にはそれほど速くありません。大規模なデータセットを扱う場合、メタデータをスキャンして必要なサブセットを特定し、Hub のレート制限に抵触しないよう、その部分のみをローカルにダウンロードすることをお勧めします: `hf download lance-format/laion-1m --repo-type dataset --local-dir ./laion`

## `lance.dataset`でHubからストリーミング

Hugging Face Hubに保存されているLanceデータセットを、`hf://`パス指定子を使用してスキャンすることもできます。これにより、ローカルにダウンロードすることなく、リモートのデータセットをスキャンできます。Lanceの`dataset` APIを使用すると、必要なデータのみを取得するために、制限、フィルタ、および射影を設定することが非常に簡単です。

```python
import lance

# Return as a Lance dataset
ds = lance.dataset("hf://datasets/lance-format/laion-1m/data/train.lance")

scanner = ds.scanner(
    columns=["caption", "url", "similarity"],
    limit=5
)

rows = scanner.to_table().to_pylist()
for row in rows:
    print(row)
```

## バイナリアセットを扱う

以下の例では、Lance データセットから `image` 列として生 JPEG バイトが取得され、下流でどのように使用されるかを示しています。`ds.take` を使用してバイトを取得し、ディスクに書き出すことで、他の場所で利用できます。

```python
import lance
from pathlib import Path

ds = lance.dataset("hf://datasets/lance-format/laion-1m/data/train.lance")

dir_name = "laion_samples"
Path(dir_name).mkdir(exist_ok=True)

rows = ds.take([0, 1], columns=["image", "caption"]).to_pylist()
for idx, row in enumerate(rows):
    with open(f"{dir_name}/{idx}.jpg", "wb") as f:
        f.write(row["image"])
        print(f"Wrote image with caption: {row['caption']}")
```

## サブセットを新しいLanceデータセットに書き込む

大規模なデータセットを扱っていますか？Hubから行のサブセットを選択するためのフィルタ付きスキャンを実行し、それをローカルのLanceデータセットとしてマテリアライズするのは簡単です。

```python
import lance

ds = lance.dataset("hf://datasets/lance-format/laion-1m/data/train.lance")
scanner = ds.scanner(
    columns=["image", "caption", "width", "height"],
    filter="width >= 200 AND height >= 100",
    limit=10,
)
subset = scanner.to_table()

lance.write_dataset(subset, "./laion_subset")
```

## インデックスの作成

データセットにインデックスが関連付けられていない場合、ローカルにダウンロードした後にインデックスを作成できます。

```python
# ds is a local Lance dataset
ds.create_index(
    "img_emb",
    index_type="IVF_PQ",
    num_partitions=256,
    num_sub_vectors=96,
    replace=True,
)
```

ベクトルインデックスの作成に関するより詳細な例については、[Lanceのドキュメント](https://lance.org/quickstart/vector-search/)を参照してください。ベクトルインデックスを作成した後、埋め込み（embeddings）を介してデータに対して類似度検索を実行できます。

## ベクトル検索

Lanceではインデックスが第一級市民として扱われるため、データだけでなく、埋め込みベクトルやインデックスも一緒に保存し、**Hub上で直接**クエリを実行できます。`describe_indices()`メソッドを使用すると、データセットのインデックス情報を一覧表示できます。データセットにインデックスが存在しない場合は、`lance.write_dataset()`を使用してデータセットのローカルバージョンを書き込み、[LanceDataset.create_index](https://lance-format.github.io/lance-python-doc/all-modules.html#lance.dataset.LanceDataset.create_index)を使用して必要に応じてインデックスを作成できます。

以下の例は、`img_emb`フィールドにベクトルインデックスが既に存在するデータセットを示しています。

```python
import lance

ds = lance.dataset("hf://datasets/lance-format/laion-1m/data/train.lance")

print(ds.list_indices())

# Returns
# [
#   IndexDescription(
#       name=img_emb_idx,
#       type_url=/lance.table.VectorIndexDetails,
#       num_rows_indexed=1209588,
#       fields=[15],
#       field_names=["img_emb"],
#       num_segments=1
#.   )
# ]
```

リモートデータセットをダウンロードせずに、ベクトル検索クエリを直接実行できます（または、ローカルにデータセットをダウンロードして新しいインデックスを作成することもできます）。以下の例では、画像埋め込みをクエリベクトルとして使用して、ベクトルインデックス上で最近傍検索を実行する方法を示しています。

```python
import lance
import pyarrow as pa

ds = lance.dataset("hf://datasets/lance-format/laion-1m/data/train.lance")

emb_field = ds.schema.field("img_emb")
ref = ds.take([0], columns=["img_emb"]).to_pylist()[0]["img_emb"]
query = pa.array([ref], type=emb_field.type)

neighbors = ds.scanner(
    nearest={
        "column": emb_field.name,
        "q": query[0],
        "k": 6,
        "nprobes": 16,
        "refine_factor": 30,
    },
    columns=["caption", "url", "similarity"],
).to_table().to_pylist()
```

> [!NOTE]
> 大きな`k`または`nprobes`の値を設定したり、一度に大量のクエリを送信したりすると、Hubのレート制限に抵触する可能性があります。大量の処理を行う場合は、データセット（またはその一部）をローカルにダウンロードし、Lanceをローカルパスに向けることで、スロットリングを回避してください。

## Datasetの進化

Lanceの最も強力な機能の一つは、柔軟でコストゼロのデータ進化です。これは、元のテーブルを書き換える**ことなく**、派生カラムを容易に追加できることを意味します。非常に大規模なテーブルや、大きなブロブが多数含まれるテーブルでは、I/Oの削減効果が非常に大きくなります。ML/AIエンジニアリングのタスクでデータを試行錯誤し、新しい特徴量、埋め込み、または派生メタデータを頻繁に追加している場合、この機能は非常に重要です。

以下の例では、既存のスコアカラムに基づいて画像を`NSFW`としてマークする派生カラム`moderation_label`を追加する方法を示しています。この変更を行う場合、新しいカラムのバックフィルは**のみ**新しいカラムのデータを書き込み、元の画像ブロブや他のカラムのデータには一切触れません。また、データのバックフィルを行わずに新しいカラムのスキーマのみを追加することも選択できます。

```python
import lance
import pyarrow as pa

# Assumes you ran the export to Lance example above to store a local subset of the data
local_ds = lance.dataset("./laion_subset")

# schema only (data to be added later)
local_ds.add_columns(pa.field("moderation_label", pa.string()))

# with data backfill
local_ds.add_columns(
    {
        "moderation_label": "case WHEN \"NSFW\" > 0.5 THEN 'review' ELSE 'ok' END"
    }
)
```

Lance データセットの列を変更・削除する方法については、Lance のドキュメントの [データ進化](https://lance.org/guide/data_evolution/) を参照してください。

## 動画ブロブを扱う

Lanceテーブルは、大きなインラインの動画ブロブもサポートしています。`OpenVid-1M`データセット（[この論文](https://arxiv.org/abs/2407.02371)より）には、高品質で表現力豊かな動画とそのキャプションが含まれています。動画データは、Hub上の以下のLanceデータセットの`video_blob`カラムに保存されています。

```python
import lance

lance_ds = lance.dataset("hf://datasets/lance-format/Openvid-1M/data/train.lance")
blob_file = lance_ds.take_blobs("video_blob", ids=[0])[0]
video_bytes = blob_file.read()
```

他のデータ形式とは異なり、Lanceでは大規模なマルチモーダルバイナリオブジェクト（blob）が第一級市民として扱われます。[blob API](https://lance.org/guide/blob/)は、Lanceデータセット内の大規模なblobを保存および取得するための高レベルAPIを提供します。次の例では、より重い動画blobを読み込まずにメタデータを効率的に参照し、その後、必要な動画blobをオンデマンドで取得する方法を示しています。

```python
import lance

ds = lance.dataset("hf://datasets/lance-format/Openvid-1M/data/train.lance")

# 1. Browse metadata without loading video blobs.
metadata = ds.scanner(
    columns=["caption", "aesthetic_score"],
    filter="aesthetic_score >= 4.5",
    limit=2,
).to_table().to_pylist()

# 2. Fetch a single video blob by row index.
selected_index = 0
blob_file = ds.take_blobs("video_blob", ids=[selected_index])[0]
with open("video_0.mp4", "wb") as f:
    f.write(blob_file.read())
```

## 学習用のデータを準備する

Trainingは、Lanceの高速なランダムアクセスとスキャン性能が活きるもう一つの領域です。Lanceデータセットを学習データのストレージ機構として使用し、シャッフルやバッチへの読み込みを学習パイプラインの一部として実行できます。

Lanceのblob APIは`torchcodec`と互換性があるため、`torch`テンソルとしてビデオのblobを簡単にデコードできます。

```python
from torchcodec.decoders import VideoDecoder
decoder = VideoDecoder(blob_file)
tensor = decoder[0]  # uint8 tensor of shape [C, H, W]
```

効率的に動画をデコードするためのその他の関数については、[torchcodecのドキュメント](https://docs.pytorch.org/torchcodec/stable/generated/torchcodec.decoders.VideoDecoder.html)を参照してください。

さらに、独自の画像モデルの学習のために`torchvision`へ画像データを読み込むことに関する追加の例については、[Lanceのドキュメント](https://lance.org/examples/python/clip_training/)もご参照ください。

## Lanceのデータセットをさらに探索する

Lanceは、従来のタブラーデータと並んでマルチモーダルなブロブをネイティブでサポートするオープンフォーマットです。Hugging Face Hubとの統合により、画像、音声、動画、テキスト、埋め込み、スカラーメタデータをすべて1つの場所で簡単に扱えます。

[Hugging Face Hub](https://huggingface.co/datasets?format=format:lance) でさらに多くの Lance データセットを探し、コミュニティの他のメンバーと独自の Lance データセットを共有しましょう。[lance.org](https://lance.org/integrations/huggingface/) では、さらに多くのコードスニペットや例を確認できます。

