Back to Articles

今日、AIを活用した機能、特に大規模言語モデル(LLM)を活用した機能が、テキスト生成、分類、画像からテキスト、画像から画像への変換など、さまざまなタスクに広く普及しています。

開発者はこれらのアプリケーションの潜在的な利点をますます認識しており、特にスクリプト作成、Web開発、そして現在はデータとのインターフェースといったコアタスクの強化に注目しています。歴史的に、データ分析のための洞察に富んだSQLクエリの作成は、主にデータアナリスト、SQL開発者、データエンジニア、または関連分野の専門家の領域であり、SQL方言の構文の微妙な違いを理解しながら進められていました。しかし、AIを活用したソリューションの登場により、状況は変化しています。これらの先進的なモデルは、データとの対話に新しい道を開き、プロセスを効率化し、より高い効率と深みで洞察を発見する可能性を提供します。

コーディングを深く掘り下げずにデータセットから魅力的な洞察を解き放つことができたらどうでしょうか。価値ある情報を得るためには、どの列を表示するか、ソーステーブル、選択した行のフィルタリング条件、集計方法、ソートの設定などを考慮して、特殊な SELECT 文を作成する必要があります。この従来のアプローチでは、SELECTFROMWHEREGROUPORDER の一連のコマンドを実行します。

しかし、熟練した開発者ではなく、それでもデータの力を活用したい場合はどうでしょうか。そのような場合、SQLの専門家に助けを求める必要があり、アクセシビリティと使いやすさのギャップが浮き彫りになります。

ここで、AIとLLM技術の画期的な進歩がそのギャップを埋める役割を果たします。平易な言葉で情報ニーズを述べ、モデルがそのリクエストをクエリに翻訳することで、データを簡単に会話できることを想像してみてください。

ここ数ヶ月、この分野で大きな進展が見られました。MotherDuckNumbers Stationは最新のイノベーションであるDuckDB-NSQL-7Bを発表しました。これはDuckDB SQL専用に設計された最先端のLLMです。このモデルの使命は何でしょうか。ユーザーがデータを簡単に操作して洞察を得られるようにすることです。

DuckDB-NSQL-7Bは、MetaのオリジナルモデルLlama-2–7bを、一般的なSQLクエリをカバーする広範なデータセットでファインチューニングし、さらにDuckDBのテキスト-to-SQLペアで改良されました。特に、その能力はSELECT文の作成を超え、公式ドキュメントや拡張機能を含む幅広い有効なDuckDB SQL文を生成できるため、データ探索と分析のための汎用ツールとなっています。

この記事では、DuckDB-NSQL-7Bモデル、Hugging Face dataset viewer API(parquetファイル用)、およびデータ取得のためのduckdbを使用して、text2sqlタスクの処理方法を学びます。

text2sql flow
text2sql flow

モデルの使用方法

  • Hugging Face transformersパイプラインを使用
from transformers import pipeline

pipe = pipeline("text-generation", model="motherduckdb/DuckDB-NSQL-7B-v0.1")
  • transformersのtokenizerとmodelを使用
from transformers import AutoTokenizer, AutoModelForCausalLM

tokenizer = AutoTokenizer.from_pretrained("motherduckdb/DuckDB-NSQL-7B-v0.1")
model = AutoModelForCausalLM.from_pretrained("motherduckdb/DuckDB-NSQL-7B-v0.1")
  • llama.cppを使用してGGUF形式でモデルを読み込む
from llama_cpp import Llama

llama = Llama(
       model_path="DuckDB-NSQL-7B-v0.1-q8_0.gguf", # Path to local model
       n_gpu_layers=-1,
)

llama.cppの主な目的は、さまざまなハードウェア上で最小限のセットアップと最先端のパフォーマンスでLLM推論を可能にすることです — ローカルおよびクラウド環境で。私たちはこのアプローチを使用します。

12万以上のデータセット向けHugging Face Dataset Viewer API

データはあらゆる機械学習の取り組みにおいて重要な要素です。Hugging Faceは、CSV、Parquet、JSON、オーディオ、画像ファイルなど、さまざまな形式にわたる12万以上の無料でオープンなデータセットへのアクセスを提供する貴重なリソースです。

Hugging Faceがホストする各データセットには、包括的なデータセットビューアが付属しています。このビューアは、統計的洞察、データサイズの評価、全文検索機能、効率的なフィルタリングオプションなど、ユーザーに不可欠な機能を提供します。この機能豊富なインターフェースにより、ユーザーはデータセットを簡単に探索・評価でき、機械学習ワークフロー全体を通じて情報に基づいた意思決定を促進します。

このデモでは、world-cities-geoデータセットを使用します。

dataset viewer
world-cities-geoデータセットのデータセットビューア

舞台裏では、ハブ内の各データセットはHugging Face dataset viewer APIによって処理され、有用な情報を取得し、次のような機能を提供します:

  • データセットの分割、列名、データ型をリストする
  • データセットのサイズを取得する(行数またはバイト単位)
  • データセット内の任意のインデックスの行をダウンロードして表示する
  • データセット内の単語を検索する
  • クエリ文字列に基づいて行をフィルタする
  • データに関する洞察に富んだ統計を取得する
  • お気に入りの処理または分析フレームワークで使用するために、データセットをparquetファイルとしてアクセスする

このデモでは、最後の機能である自動変換されたparquetファイルを使用します。

テキスト指示からSQLクエリを生成する

まず、DuckDB-NSQL-7B-v0.1の量子化モデルバージョンをダウンロードします

download model
モデルのダウンロード

または、次のコードを実行することもできます:

huggingface-cli download motherduckdb/DuckDB-NSQL-7B-v0.1-GGUF DuckDB-NSQL-7B-v0.1-q8_0.gguf --local-dir . --local-dir-use-symlinks False

次に、必要な依存関係をインストールします:

pip install llama-cpp-python
pip install duckdb

text-to-SQLモデルでは、次の構造のプロンプトを使用します:

   ### Instruction:
   Your task is to generate valid duckdb SQL to answer the following question.
   ### Input:
   Here is the database schema that the SQL query will run on:
   {ddl_create}
  
   ### Question:
   {query_input}
   ### Response (use duckdb shorthand if possible):
  • ddl_createはSQL CREATEコマンドとしてのデータセットスキーマになります
  • query_inputは自然言語で表現されたユーザー指示になります

したがって、Hugging Faceデータセットのスキーマをモデルに伝える必要があります。そのため、jamescalam/world-cities-geoデータセットの最初のparquetファイルを取得します:

GET https://huggingface.co/api/datasets/jamescalam/world-cities-geo/parquet
{
   "default":{
      "train":[
         "https://huggingface.co/api/datasets/jamescalam/world-cities-geo/parquet/default/train/0.parquet"
      ]
   }
}

parquetファイルはHugging Face viewerのrefs/convert/parquetリビジョンでホストされています:

parquet file
Parquet file

  • parquetファイルの最初の行からDuckDBテーブル作成をシミュレートする
import duckdb
con = duckdb.connect()
con.execute(f"CREATE TABLE data as SELECT * FROM '{first_parquet_url}' LIMIT 1;")

result = con.sql("SELECT sql FROM duckdb_tables() where table_name ='data';").df()
ddl_create = result.iloc[0,0]
con.close()

CREATEスキーマDDLは次のとおりです:

CREATE TABLE "data"(
    city VARCHAR, 
    country VARCHAR, 
    region VARCHAR,
    continent VARCHAR, 
    latitude DOUBLE, 
    longitude DOUBLE, 
    x DOUBLE, 
    y DOUBLE, 
    z DOUBLE
);

そしてご覧のとおり、データセットビューアの列と一致しています:

dataset columns
Dataset columns

  • 次に、ddl_createquery入力でプロンプトを作成できます
prompt = """### Instruction:
   Your task is to generate valid duckdb SQL to answer the following question.
   ### Input:
   Here is the database schema that the SQL query will run on:
   {ddl_create}
  
   ### Question:
   {query_input}
   ### Response (use duckdb shorthand if possible):
   """

ユーザーがAlbania国の都市を知りたい場合、プロンプトは次のようになります:

query = "Cities from Albania country"
prompt = prompt.format(ddl_create=ddl_create, query_input=query)

したがって、LLMに送信される展開されたプロンプトは次のようになります:

### Instruction:
Your task is to generate valid duckdb SQL to answer the following question.

### Input:
Here is the database schema that the SQL query will run on:
CREATE TABLE "data"(city VARCHAR, country VARCHAR, region VARCHAR, continent VARCHAR, latitude DOUBLE, longitude DOUBLE, x DOUBLE, y DOUBLE, z DOUBLE);
  
### Question:
Cities from Albania country

### Response (use duckdb shorthand if possible):
  • プロンプトをモデルに送信する時間です
from llama_cpp import Llama

llm = Llama(
       model_path="DuckDB-NSQL-7B-v0.1-q8_0.gguf",
       n_ctx=2048,
       n_gpu_layers=50
   )
pred = llm(prompt, temperature=0.1, max_tokens=1000)
sql_output = pred["choices"][0]["text"]

出力されるSQLコマンドはdataテーブルを指しますが、実際のテーブルではなくparquetファイルへの参照しかないため、dataの出現箇所をすべてfirst_parquet_urlに置き換えます:

sql_output = sql_output.replace("FROM data", f"FROM '{first_parquet_url}'")

そして最終的な出力は次のようになります:

SELECT city FROM 'https://huggingface.co/api/datasets/jamescalam/world-cities-geo/parquet/default/train/0.parquet' WHERE country = 'Albania'
  • 次に、生成されたSQLをデータセットで直接実行する時間です。もう一度DuckDBの機能を使用しましょう:
con = duckdb.connect()
try:
   query_result = con.sql(sql_output).df()
except Exception as error:
   print(f"❌ Could not execute SQL query {error=}")
finally:
   con.close()

そして結果が得られます(100行):

sql command result
Execution result (100 rows)

この結果を、データセットビューアでAlbania国を「検索機能」で検索した結果と比較してみましょう。同じになるはずです:

search result
Search result for Albania country

検索APIまたはフィルタAPIを直接呼び出しても同じ結果が得られます:

import requests
API_URL = "https://datasets-server.huggingface.co/search?dataset=jamescalam/world-cities-geo&config=default&split=train&query=Albania"
def query():
    response = requests.get(API_URL)
    return response.json()
data = query()
import requests
API_URL = "https://datasets-server.huggingface.co/filter?dataset=jamescalam/world-cities-geo&config=default&split=train&where=country='Albania'"
def query():
    response = requests.get(API_URL)
    return response.json()
data = query()

最終的なデモは、次のようなHugging Face spaceになります:

コードを含むノートブックはこちらでご覧いただけます。

Hugging Face Spaceはこちら