Closed6

Jina Classifier APIを試す

kun432kun432

Jinaから新しいClassifier APIが出た。

https://twitter.com/JinaAI_/status/1848734770441949416

分類は埋め込みの最も重要な下流タスクです。最近では、クエリをLLMにルーティングすることにも人気があります。新しいClassifier API https://jina.ai/classifier/ を発表できることを嬉しく思います。このAPIは、最新の jina-embeddings-v3jina-clip-v1 を使用して、テキストと画像のzero-shotおよびfew-shotのオンライン分類の両方をサポートします。

https://twitter.com/JinaAI_/status/1848734773126303823

Classifier APIは、オンラインpassive-aggressiveを基盤としており、新しいデータにリアルタイムで適応することができます。ユーザーはzero-shot分類からから始め、すぐに使用することができます。その後、新しいサンプルを送信したり、概念のドリフトが発生した際にClassifierを徐々に更新することができます。これにより、膨大な量の初期ラベル付きデータがなくても、さまざまなコンテンツタイプにわたって効率的かつスケーラブルな分類が可能になります。
👇 zero-shotおよびfew-shot分類のリリースノートと例をご覧ください。https://jina.ai/news/jina-classifier-for-high-performance-zero-shot-and-few-shot-classification/

Embeddingモデルをベースにしたものになっている。

kun432kun432

Zero-Shotのテキスト分類

まずはシンプルに公式のサンプル。みやすさのためコードは少し変えてある。

import requests
import json
from google.colab import userdata

input = [
    {"text": "Calculate the compound interest on a principal of $10,000 invested for 5 years at an annual rate of 5%, compounded quarterly."},
    {"text": "分析使用CRISPR基因编辑技术在人类胚胎中的伦理影响。考虑潜在的医疗益处和长期社会后果。"},
    {"text": "AIが自意識を持つディストピアの未来を舞台にした短編小説を書いてください。人間とAIの関係や意識の本質をテーマに探求してください。"},
    {"text": "Erklären Sie die Unterschiede zwischen Merge-Sort und Quicksort-Algorithmen in Bezug auf Zeitkomplexität, Platzkomplexität und Leistung in der Praxis."},
    {"text": "Write a poem about the beauty of nature and its healing power on the human soul."},
    {"text": "Translate the following sentence into French: The quick brown fox jumps over the lazy dog."}
]

labels = [
    "Simple task",
    "Complex reasoning",
    "Creative writing"
]

url = 'https://api.jina.ai/v1/classify'

headers = {
    'Content-Type': 'application/json',
    'Authorization': f'Bearer {userdata.get("JINA_API_KEY")}'
}
data = {
    "model": "jina-embeddings-v3",
    "input": input,
    "labels": labels,
}

response = requests.post(url, headers=headers, json=data)

print(json.dumps(response.json(), indent=2, ensure_ascii=False))

結果

{
  "usage": {
    "total_tokens": 196,
    "prompt_tokens": 196
  },
  "data": [
    {
      "object": "classification",
      "index": 0,
      "prediction": "Simple task",
      "score": 0.35216382145881653
    },
    {
      "object": "classification",
      "index": 1,
      "prediction": "Complex reasoning",
      "score": 0.34310275316238403
    },
    {
      "object": "classification",
      "index": 2,
      "prediction": "Creative writing",
      "score": 0.3487184941768646
    },
    {
      "object": "classification",
      "index": 3,
      "prediction": "Complex reasoning",
      "score": 0.35207709670066833
    },
    {
      "object": "classification",
      "index": 4,
      "prediction": "Creative writing",
      "score": 0.3638903796672821
    },
    {
      "object": "classification",
      "index": 5,
      "prediction": "Simple task",
      "score": 0.3561534285545349
    }
  ]
}

なお、分類のラベルを変えると結果も変わる。

import requests
import json
from google.colab import userdata

input = [
    {"text": "Calculate the compound interest on a principal of $10,000 invested for 5 years at an annual rate of 5%, compounded quarterly."},
    {"text": "分析使用CRISPR基因编辑技术在人类胚胎中的伦理影响。考虑潜在的医疗益处和长期社会后果。"},
    {"text": "AIが自意識を持つディストピアの未来を舞台にした短編小説を書いてください。人間とAIの関係や意識の本質をテーマに探求してください。"},
    {"text": "Erklären Sie die Unterschiede zwischen Merge-Sort und Quicksort-Algorithmen in Bezug auf Zeitkomplexität, Platzkomplexität und Leistung in der Praxis."},
    {"text": "Write a poem about the beauty of nature and its healing power on the human soul."},
    {"text": "Translate the following sentence into French: The quick brown fox jumps over the lazy dog."}
]

# ラベルを日本語で
labels = [
    "シンプルなタスク",
    "複雑な推論",
    "クリエイティブなライティング"
]

url = 'https://api.jina.ai/v1/classify'

headers = {
    'Content-Type': 'application/json',
    'Authorization': f'Bearer {userdata.get("JINA_API_KEY")}'
}
data = {
    "model": "jina-embeddings-v3",
    "input": input,
    "labels": labels,
}

response = requests.post(url, headers=headers, json=data)

print(json.dumps(response.json(), indent=2, ensure_ascii=False))

結果

{
  "usage": {
    "total_tokens": 207,
    "prompt_tokens": 207
  },
  "data": [
    {
      "object": "classification",
      "index": 0,
      "prediction": "複雑な推論",
      "score": 0.34926360845565796
    },
    {
      "object": "classification",
      "index": 1,
      "prediction": "複雑な推論",
      "score": 0.34087464213371277
    },
    {
      "object": "classification",
      "index": 2,
      "prediction": "クリエイティブなライティング",
      "score": 0.34699109196662903
    },
    {
      "object": "classification",
      "index": 3,
      "prediction": "複雑な推論",
      "score": 0.3575187623500824
    },
    {
      "object": "classification",
      "index": 4,
      "prediction": "クリエイティブなライティング",
      "score": 0.355049729347229
    },
    {
      "object": "classification",
      "index": 5,
      "prediction": "シンプルなタスク",
      "score": 0.3428526818752289
    }
  ]
}

例文の1つ目の"Calculate the compound interest on a principal..."が、日本語ラベルに変えたことで、"Simple Task"から「複雑な推論」("Complex reasoning")に変わっている。

日本語だけでやってみる。

import requests
import json
from google.colab import userdata

input = [
    {"text": "美味しい料理には、新鮮な食材選び、適切な火加減や調理時間を守ることで、素材の旨味を最大限に引き出せます。"},
    {"text": "日本の伝統的な和食は、「一汁三菜」を基本とし、主食、汁物、主菜、副菜で構成されています。"},
    {"text": "天気予報の信頼性は、短期・長期などの予報期間によって大きく異なり、局地的な現象の予測は特に難しいです。"},
    {"text": "気象観測には、気温、湿度、気圧、風向風速、雨や雪の分布、雲の動き、大気の状態など様々なデータを使用します。"}
]

labels = [
    "料理",
    "気象",
]

url = 'https://api.jina.ai/v1/classify'

headers = {
    'Content-Type': 'application/json',
    'Authorization': f'Bearer {userdata.get("JINA_API_KEY")}'
}
data = {
    "model": "jina-embeddings-v3",
    "input": input,
    "labels": labels,
}

response = requests.post(url, headers=headers, json=data)

print(json.dumps(response.json(), indent=2, ensure_ascii=False))

結果

{
  "usage": {
    "total_tokens": 154,
    "prompt_tokens": 154
  },
  "data": [
    {
      "object": "classification",
      "index": 0,
      "prediction": "料理",
      "score": 0.5745312571525574
    },
    {
      "object": "classification",
      "index": 1,
      "prediction": "料理",
      "score": 0.5665366053581238
    },
    {
      "object": "classification",
      "index": 2,
      "prediction": "気象",
      "score": 0.5758487582206726
    },
    {
      "object": "classification",
      "index": 3,
      "prediction": "気象",
      "score": 0.5707066059112549
    }
  ]
}
kun432kun432

イメージ・テキスト混在のZero-Shot分類

入力に、イメージとテキストを混在させることができる。モデルに"jina-clip-v1"を使う。ただし、ドキュメントを見る限り、"jina-clip-v1"はマルチリンガルモデルではなさそうなので、恐らく日本語は使えないと思われる。サンプルでは英文で書かれているので、そのまま試す。

入力データは以下のようなものとなっている。

  • A sleek smartphone with a high-resolution display and multiple camera lenses(「高解像度のディスプレイと複数のカメラレンズを搭載したスタイリッシュなスマートフォン」)
  • Fresh sushi rolls served on a wooden board with wasabi and ginger(「木の板に盛り付け、わさびと生姜を添えて提供された,新鮮な巻き寿司」)
  • Vibrant autumn leaves in a dense forest with sunlight filtering through(「鬱蒼とした森に、木漏れ日が差し込んで、色鮮やかなに浮かび上がる紅葉」)

これを以下のラベルで分類する。

  • Food and Dining(「料理と食事」)
  • Technology and Gadgets(「テクノロジーとガジェット」)
  • Nature and Outdoors(「自然とアウトドア」)
  • Urban and Architecture(「都市と建築」)
import requests
import json
from google.colab import userdata

input = [
    {"text": "A sleek smartphone with a high-resolution display and multiple camera lenses"},
    {"text": "Fresh sushi rolls served on a wooden board with wasabi and ginger"},
    {"image": "https://picsum.photos/id/11/367/267"},
    {"image": "https://picsum.photos/id/22/367/267"},
    {"text": "Vibrant autumn leaves in a dense forest with sunlight filtering through"},
    {"image": "https://picsum.photos/id/8/367/267"}
]

labels = [
    "Food and Dining",
    "Technology and Gadgets",
    "Nature and Outdoors",
    "Urban and Architecture"
]

url = 'https://api.jina.ai/v1/classify'

headers = {
    'Content-Type': 'application/json',
    'Authorization': f'Bearer {userdata.get("JINA_API_KEY")}'
}
data = {
    "model": "jina-clip-v1",
    "input": input,
    "labels": labels,
}

response = requests.post(url, headers=headers, json=data)

print(json.dumps(response.json(), indent=2, ensure_ascii=False))

結果

{
  "usage": {
    "total_tokens": 12065,
    "prompt_tokens": 12065
  },
  "data": [
    {
      "object": "classification",
      "index": 0,
      "prediction": "Technology and Gadgets",
      "score": 0.30329811573028564
    },
    {
      "object": "classification",
      "index": 1,
      "prediction": "Food and Dining",
      "score": 0.2765541970729828
    },
    {
      "object": "classification",
      "index": 2,
      "prediction": "Nature and Outdoors",
      "score": 0.29503118991851807
    },
    {
      "object": "classification",
      "index": 3,
      "prediction": "Urban and Architecture",
      "score": 0.2648046910762787
    },
    {
      "object": "classification",
      "index": 4,
      "prediction": "Nature and Outdoors",
      "score": 0.31330636143684387
    },
    {
      "object": "classification",
      "index": 5,
      "prediction": "Technology and Gadgets",
      "score": 0.27474141120910645
    }
  ]
}

なお、画像はURL以外にBASE64文字列でもOK。

Jina Readerと組み合わせた、スクレイピング結果の判定

Jina Readerと組み合わせて、Readerが取得したコンテンツがブロックされているかいないかを判定する。ユースケースとして、ペイウォールされている・レート制限にかかっている・サーバダウンなどを判定するというのが紹介されている。面白い。

import requests
import json
from google.colab import userdata

response1 = requests.get('https://r.jina.ai/https://jina.ai')

url = 'https://api.jina.ai/v1/classify'
headers = {
    'Content-Type': 'application/json',
    'Authorization': f'Bearer {userdata.get("JINA_API_KEY")}'
}
data = {
    'model': 'jina-embeddings-v3',
    'labels': ['ブロックされている', 'アクセスできる'],
    'input': [{'text': response1.text[:8000]}]
}
response2 = requests.post(url, headers=headers, data=json.dumps(data))

print(json.dumps(response2.json(), indent=2, ensure_ascii=False))

結果

{
  "usage": {
    "total_tokens": 3858,
    "prompt_tokens": 3858
  },
  "data": [
    {
      "object": "classification",
      "index": 0,
      "prediction": "アクセスできる",
      "score": 0.5085554718971252
    }
  ]
}

Zero-Shot分類についての注意

Zero-Shot分類を行う場合の注意、というかプラクティスが書かれている。

  1. セマンティックなラベルを使う
    • Zero-Shot分類では、「記述的」なラベルを使うほうが効果的
      • 埋め込みモデルは、意味的な関係を理解するため
      • 例1:
        • BAD
          • 「クラス1」「クラス2」「クラス3」「0」「1」「2」
        • GOOD
          • 「テクノロジー」「自然」「食べ物」
      • 例2: 感情分析の場合
        • BAD
          • 「ポジティブ」「ネガティブ」
        • GOOD
          • 「ポジティブな感情」
    • ラベルを変えると30%精度が向上するという記事
  2. ステートレスな性質を理解する
    • Zero-Shot分類は、同じ入力・同じモデルであれば、結果は常に同じ。
      • つまり「ステートレス」
      • テスト等の場合は入力データ(ラベル)を変えるだけ。
    • Few-Shot分類やオンライン学習アプローとは逆
      • 新しい例に適応するため、時間の経過やユーザー間で異なる結果になる場合がある
kun432kun432

Few-Shotによるテキスト分類

Few-Shotでは、ラベル付きデータから学習させて、分類を行う。2つのエンドポイントがある

  • train
    • テキストとラベルのデータセットを元に学習させると分類器IDが返される
  • classify
    • 上記の分類器IDを元に分類する
    • ラベルはすでに学習済みのため、リクエストに含める必要はない

ということでまずは学習(train)させてみる。サンプルとして挙げられているのはサポートチケットの内容からどのチームにアサインするかを分類するというもの。英語で書かれている部分を日本語に置き換えてみた。

import requests
import json
from google.colab import userdata

input = [
    {
      "text": "最新アプリのアップデート後、自分のアカウントにログインできません。",
      "label": "認証チーム"
    },
    {
      "text": "クレジットカードの有効期限が切れていたため、購読の更新に失敗しました。",
      "label": "課金チーム"
    },
    {
      "text": "プラットフォームからデータをエクスポートする方法を教えてください。",
      "label": "UIチーム"
    }
]

data = {
    "model": "jina-embeddings-v3",
    "access": "private",
    "num_iters": "10",
    "input": input,
}

url = 'https://api.jina.ai/v1/train'
headers = {
    'Content-Type': 'application/json',
    'Authorization': f'Bearer {userdata.get("JINA_API_KEY")}'
}

response = requests.post(url, headers=headers, json=data)

print(json.dumps(response.json(), indent=2, ensure_ascii=False))

入力データは、テキストとそれを分類するラベルのセットになる。入力データから学習させる分類器の設定は以下の2つがある

  • access
    • 分類器を公開するか。
    • privateなら自分しか使えない。publicなら分類器IDを知っていれば誰でも使える(APIキーは当然必要)
  • num_iters
    • 分類器が入力データからどの程度学習するか。
    • デフォルト値10でほとんどのケースは問題ない。
    • 入力データの信頼度が高ければ上げる・信頼度が低ければ下げる、といった感じで、学習の影響度を考慮できる。
    • 繰り返し学習することが可能?になっており、その場合は古い知識を維持しながら学習する。
      • よって、より新しい例がある場合はこの数値を上げるとよいらしい。

結果はサクッと返ってきた。このclassifier_idが入力データで学習させた分類器IDになる。

{
  "classifier_id": "XXXXXXXXXXXX",
  "num_samples": 3,
  "usage": {
    "total_tokens": 490,
    "prompt_tokens": 490
  }
}

ではこの分類器を使って分類してみる。

import requests
import json
from google.colab import userdata

input = [
    {"text": "新機能のせいで、ダッシュボードの読み込みが遅くなっています。"},
    {"text": "税務上の請求情報を更新する必要があります。"},
    {"text": "新しいAIチャットボット機能にアクセスしようとすると、404エラーが表示されます。"},
    {"text": "最新のセキュリティパッチが私の会社のファイアウォールと衝突しています。"},
    {"text": "組織アカウントのSSO設定について教えてください。"},
]

url = 'https://api.jina.ai/v1/classify'

headers = {
    'Content-Type': 'application/json',
    'Authorization': f'Bearer {userdata.get("JINA_API_KEY")}'
}
data = {
    "classifier_id": "XXXXXXXXXXXX",
    "input": input,
}

response = requests.post(url, headers=headers, json=data)

print(json.dumps(response.json(), indent=2, ensure_ascii=False))
{
  "usage": {
    "total_tokens": 80,
    "prompt_tokens": 80
  },
  "data": [
    {
      "object": "classification",
      "index": 0,
      "prediction": "認証チーム",
      "score": 0.39823853969573975
    },
    {
      "object": "classification",
      "index": 1,
      "prediction": "課金チーム",
      "score": 0.3757244646549225
    },
    {
      "object": "classification",
      "index": 2,
      "prediction": "認証チーム",
      "score": 0.4416896104812622
    },
    {
      "object": "classification",
      "index": 3,
      "prediction": "認証チーム",
      "score": 0.40543636679649353
    },
    {
      "object": "classification",
      "index": 4,
      "prediction": "認証チーム",
      "score": 0.3571796715259552
    }
  ]
}

一般的に見て分類がおかしいが、十分な入力データではないため。ということで、追加で学習させてみる。

import requests
import json
from google.colab import userdata

input = [
    {
        "text": "新機能のせいで、ダッシュボードの読み込みが遅くなっています。",
        "label": "UIチーム"
    },
    {
        "text": "税務上の請求情報を更新する必要があります。",
        "label": "課金チーム"
    },
    {
        "text": "新しいAIチャットボット機能にアクセスしようとすると、404エラーが表示されます。",
        "label": "インフラチーム"
    },
    {
        "text": "最新のセキュリティパッチが私の会社のファイアウォールと衝突しています。",
        "label": "インフラチーム"
    },
    {
        "text": "組織アカウントのSSO設定について教えてください。",
        "label": "認証チーム"
    }
]

data = {
    "classifier_id": "XXXXXXXXXXXX",
    "num_iters": "10",
    "input": input,
}

url = 'https://api.jina.ai/v1/train'
headers = {
    'Content-Type': 'application/json',
    'Authorization': f'Bearer {userdata.get("JINA_API_KEY")}'
}

response = requests.post(url, headers=headers, json=data)

print(json.dumps(response.json(), indent=2, ensure_ascii=False))
{
  "classifier_id": "XXXXXXXXXXXX",
  "num_samples": 5,
  "usage": {
    "total_tokens": 800,
    "prompt_tokens": 800
  }
}

再度分類を試してみると学習内容が反映されている。

{
  "usage": {
    "total_tokens": 80,
    "prompt_tokens": 80
  },
  "data": [
    {
      "object": "classification",
      "index": 0,
      "prediction": "UIチーム",
      "score": 0.7180126309394836
    },
    {
      "object": "classification",
      "index": 1,
      "prediction": "課金チーム",
      "score": 0.8152154684066772
    },
    {
      "object": "classification",
      "index": 2,
      "prediction": "インフラチーム",
      "score": 0.7570258975028992
    },
    {
      "object": "classification",
      "index": 3,
      "prediction": "インフラチーム",
      "score": 0.7939446568489075
    },
    {
      "object": "classification",
      "index": 4,
      "prediction": "認証チーム",
      "score": 0.7711507081985474
    }
  ]
}

あと、学習させたFew-Shot分類器の一覧や削除もできる。Usageを参照。

Few-Shot分類についての注意

  1. Few−Shot分類の学習はワンパスオンライン学習
    • 使用したトレーニングデータを使ってその場でパラメータを更新し、そのデータは破棄される。
    • メリット: プライバシーやリソース効率の観点
    • デメリット: 履歴を取り出すことはできない
  2. Zero−Shot分類を上回るためにはウォームアップ期間が必要
    • トレーニング用の例は200〜400あれば良いパフォーマンスが得られる
    • ラベルを最初から全て用意する必要はなく、後から追加して少ない例でも学習させることはできる
    • ただし、すでに学習されたラベルの例よりも、追加したラベルの例が少ない場合、正しく分類できない場合がある
      • これを「クラス不均衡」「コールドスタート」と呼んでいるっぽい
      • トレーニング用の例を追加していくとよいみたい
kun432kun432

まとめ

ちょっと個人的に分類タスクをやろうと思っているところだったので、タイムリーに試せてよかった。学習もとても簡単にできて良い。地味ではあるけど、活用できるユースケースは多いと思う。

なお、Jina Readerの場合はEmbeddingモデルをラップしたAPIという感じだけど、他社や他の方法については、自分はこんな感じで認識してる。

Embeddingを使うのはコストやレスポンスの観点でメリットがあると思っているのだけど、専用のAPIが用意されているととても手軽に使いやすい。ただ、専用の分類APIで提供しているモデルプロバイダーはそれほど多くない印象があって、もしかするとニーズがそれほどないということなのかもしれないが、Jinaはこういうかゆいところに手が届くようなものを出してくれるイメージがあって、好印象である。

とはいえ、他の実装などについてもいろいろ試してみたいと思う。

このスクラップは1ヶ月前にクローズされました