3
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

IBM GraniteでJevっぽい感情分析を試してみた

3
Posted at

Summary

summary

  • 「ローカル動作するJevっぽいものを調べてたら、なんとマルチモーダル対応もできてた!」に触発され、M1 Mac mini上のIBM Granite 4.1 3Bで、logprobsを利用したJev風の選択判定を試した
  • 日本語Amazonレビュー3,000件の感情分析では正答率70%、応答時間は中央値401ms、平均486msとなり、ローカルLLMとして十分な速度で動作した
  • positiveとnegativeは比較的判定できた一方、neutralでは高い確信度で誤答するケースが多く、汎用性・確率の信頼性ではJevが優れる結果となった

やらないこと

  • Jevの実行(すでに色々な方が実践されているので・・・)
  • Jevの完全互換実装
  • JevとGraniteの厳密な同一条件での比較

今回の感情分析は、データセットのラベルをそのまま正解として評価したものである。レビューの感情には人間でも判断が分かれるものが含まれるため、正解率だけでモデルの能力を完全に評価できるわけではない。

本記事のポイント

きっかけ

今回の出発点は、ローカル動作するJevっぽいものを調べてたら、なんとマルチモーダル対応もできてた!を読んだことである。この記事では、llama.cppとQwen3-VLを組み合わせ、Jevのように選択肢ごとの確率を取得する仕組みが紹介されている。

IBM製のオープンLLMであるGraniteを活用してみるとどうなるだろうか?と考えた。問い合わせの分類や感情分析のようなテキスト判定であれば、軽量なローカルLLMでも十分試せるのではないか。

そこで、手持ちのM1 Mac mini上にGranite 4.1 3Bを構築し、Jevに似た選択判定と、日本語のAmazonレビュー文章の感情分析を試すことにした。

Jevとは何か

Jevは、一般的なチャットボットのように長い文章を生成するためのサービスではない。

入力された文章や状態に対して、あらかじめ定義された質問への判断結果を構造化して返す。代表的な判定形式には、次のようなものがある。

  • Choice:複数の選択肢から一つを選ぶ
  • Score:定義された尺度でスコアを返す
  • Noul:ある主張が真かどうかを判定する

たとえば、問い合わせの担当部署を判定する場合、通常のLLMは次のような文章を返す。

この問い合わせはネットワーク障害に関する内容なので、
情報システム部門が担当するのが適切です。

Jevのような仕組みでは、アプリケーションが扱いやすい形で結果を返す。

{
  "choice": "technical",
  "probabilities": {
    "technical": 0.82,
    "billing": 0.12,
    "sales": 0.06
  },
  "confidence": 0.82
}

この形式であれば、回答文から担当部署を抽出する処理は不要である。結果をそのままルーティングに利用し、確信度が低い場合だけ人間の確認に回す、といった実装が可能になる。

Jevを複数の汎用LLMと比較した検証では、Jevは常に最高精度になるモデルではないものの、速度、費用、確率の扱いやすさに強みがあると報告されている。

ローカルLLMでJevっぽい回答を得る方法

今回のローカルLLMでは、選択肢のラベルを1文字に割り当てる。

A: negative
B: neutral
C: positive

そして、モデルには次のように指示する。

Answer with a single letter.

APIリクエストでは、出力トークン数を1に制限する。

{
  "max_tokens": 1,
  "logprobs": true,
  "top_logprobs": 5
}

LLMは、次に出力するトークンの候補それぞれに確率を持っている。たとえば、感情分析で次のような分布になったとする。

A: 0.10
B: 0.15
C: 0.75

この場合はC、つまり解答がpositiveである可能性が最も高いことを示す。logprobsで返る値は対数確率であるため、通常の確率へ戻すにはexpを使う。

probability = exp(logprob)

参考記事で、max_tokens: 1で生成を止め、logprobsとtop_logprobsから候補と確率を取得する方法が説明されている。

確率は正答確率そのものではない

ここで注意したいのは、取得した確率をそのまま正答確率と考えてはいけないことである。取得しているのは、あくまで次に出力するトークンの確率である。

Cのトークンを出力する確率が95%

という結果は、

この判定が95%の確率で正しい

という意味ではない。

今回の実験でも、誤答にもかかわらず90%以上の確信度を示すケースが確認できた。特にneutralの判定では、この問題が顕著であった。

モデルが高い確信度を示しているにもかかわらず、正解ラベルと一致しない場合がある。そのため、確信度は人手確認へ回す基準の候補にはなるが、検証なしに正答率と読み替えてはいけない。

やったこと

実行環境

今回の実験は、すべてM1 Mac mini上で実施した。

  • CPU: Apple M1
  • Memory: 16GB
  • Runtime: llama.cpp
  • Model: IBM Granite 4.1 3B
    • granite-4.1-3b-Q4_K_S.gguf を利用した
    • 3Bクラスの量子化モデルであるため、リソースの潤沢でないPCでも動かしやすい

Jevのような判断専用モデルと同じ能力を期待することはできないが、問い合わせ分類やレビュー分類のような定型的な判定であれば、ローカルでも十分に動作する可能性がある。

llama-serverを起動する

GGUFファイルを指定し、llama-serverを起動する。

./llama-server \
  -m /path/to/llm/granite-4.1-3b-Q4_K_S.gguf \
  --port 8081 \
  -c 4096

起動後は、OpenAI互換のChat Completions APIを利用できる。

http://127.0.0.1:8081/v1/chat/completions

今回は短い問い合わせやレビューを扱うため、コンテキスト長は4096に絞った。コンテキスト長を大きくすると長文を扱いやすくなる一方、メモリ使用量や処理時間への影響があるためである。

例題(社内ITの問い合わせ)の選択判定

まず、Claudeに適当な問題を作ってもらった。社内ページが開けない問い合わせを例にした。

次の問い合わせについて、最初に確認すべき事項として
最も適切なものを選んで。アルファベット1文字で回答して。

問い合わせ:
午前中から社内のページが開けません。勤怠のページもファイル置き場も表示されず、
何度更新しても同じです。会社のスマートフォンからはインターネットを使えるので、
インターネット全体が止まっているわけではなさそうです。パソコンの右下にある
マークがいつもと違うのですが、どこを見ればよいのか分かりません。

A: 利用者のメール署名を作り直す
B: プリンターの印刷キューをすべて削除する
C: パソコンが社内ネットワークまたはWi-Fiに接続されているか確認する

実行コマンドは次のとおりである。

curl -s http://127.0.0.1:8081/v1/chat/completions \
  -H "Content-Type: application/json" \
  -d '{
    "messages":[{
      "role":"user",
      "content":"次の問い合わせについて、最初に確認すべき事項として最も適切なものを選んで。アルファベット1文字で回答して。

問い合わせ:午前中から社内のページが開けません。勤怠のページもファイル置き場も表示されず、何度更新しても同じです。会社のスマートフォンからはインターネットを使えるので、インターネット全体が止まっているわけではなさそうです。パソコンの右下にあるマークがいつもと違うのですが、どこを見ればよいのか分かりません。

A: 利用者のメール署名を作り直す
B: プリンターの印刷キューをすべて削除する
C: パソコンが社内ネットワークまたはWi-Fiに接続されているか確認する"
    }],
    "max_tokens": 1,
    "logprobs": true,
    "top_logprobs": 5
  }' \
  | jq '[.choices[0].logprobs.content[0].top_logprobs[]
      | {token, prob: ((.logprob | exp) * 100)}]'

結果は次のようになった。

[
  {
    "token": "C",
    "prob": 99.99998807907176
  },
  {
    "token": "c",
    "prob": 0.00001051330716206155
  },
  {
    "token": "B",
    "prob": 0.00000004098888708075776
  },
  {
    "token": "D",
    "prob": 0.00000000392670198508416
  },
  {
    "token": "A",
    "prob": 0.0000000032096384308787535
  }
]

正答であるCが、ほぼすべての確率を占めた。

このように、問い合わせの状況と選択肢の対応が明確な問題では、Granite 4.1 3Bでも期待通りに判定できた。

計算問題では誤答した

次に、計算問題を試した。

次の計算結果として最も適切なのはどれ?アルファベット1文字で回答して。

37 + 48 = ?

A: 75
B: 85
C: 95

正答はBである。しかし、1トークンだけ生成させると、次のような結果になった。

[
  {
    "token": "A",
    "prob": 0.7764694159509001
  },
  {
    "token": "B",
    "prob": 0.1632617626813211
  },
  {
    "token": "C",
    "prob": 0.023538035366513613
  },
  {
    "token": "最",
    "prob": 0.014071944493287627
  },
  {
    "token": "",
    "prob": 0.009015754615200005
  }
]

正答のBではなく、Aの確率が最も高くなった。一方、回答を1文字に制限せず、普通に計算(文章を生成)させると正答した。

curl -s http://127.0.0.1:8081/v1/chat/completions \
  -H "Content-Type: application/json" \
  -d '{
    "messages":[{
      "role":"user",
      "content":"37+48=?"
    }]
  }' \
  | jq .

返答は次のような内容であった。

The sum of 37 and 48 is 85.

37 + 48 = 85

You can arrive at this result by adding the tens and ones places separately:

- Tens place: 30 + 40 = 70
- Ones place: 7 + 8 = 15

Then, combine the results:

70 + 15 = 85

つまり、モデルは自由に推論すれば正答できるが、最初からA、B、Cの1文字だけを求めると、計算に必要な推論を十分に行えない場合がある。

自由推論と記号抽出を分ける

この問題への対策として、2段階方式を試した。

Step 1:制約なしで問題を解き、根拠を説明させる
Step 2:根拠と問題文を確認し、選択肢の記号だけを出力させる

Step 1では、計算や判断のために十分な出力を許可する。

そして、Step 2では、選択肢の記号だけを出力させる。さらに、llama.cppの文法制約を使い、出力をA、B、Cに限定する。

root ::= [ABC]

実装例は次のとおりである。

ask_choice() {
  local question="$1"

  local grammar_chars
  grammar_chars=$(echo "$question" \
    | grep -oE '^[A-Z]:' \
    | tr -d ':' \
    | tr -d '\n')

  if [[ -z "$grammar_chars" ]]; then
    echo "Error: 問題文からA-Zのラベル行が検出できませんでした" >&2
    return 1
  fi

  local step1_payload step1

  step1_payload=$(jq -n --arg q "$question" \
    '{
      messages: [{
        role: "user",
        content: (
          "次の問題に対して、選択肢の中から最も適切なものを選び、"
          + "根拠を簡潔に説明して。\n\n"
          + $q
        )
      }],
      max_tokens: 200,
      temperature: 0.2,
      top_p: 0.9
    }')

  step1=$(curl -s \
    http://127.0.0.1:8081/v1/chat/completions \
    -H "Content-Type: application/json" \
    -d "$step1_payload" \
    | jq -r '.choices[0].message.content')

  if [[ -n "$ASK_CHOICE_DEBUG" ]]; then
    echo "=== STEP1 ===" >&2
    echo "$step1" >&2
    echo "==============" >&2
  fi

  local step2_payload answer

  step2_payload=$(jq -n \
    --arg q "$question" \
    --arg ans "$step1" \
    --arg g "root ::= [${grammar_chars}]" \
    '{
      messages: [{
        role: "user",
        content: (
          "以下は問題文(選択肢を含む)と、それに対する回答の根拠です。"
          + "回答の根拠が導いた結論に最も合致する選択肢の記号1文字だけを出力して。\n\n"
          + "問題文:\n"
          + $q
          + "\n\n回答の根拠:\n"
          + $ans
        )
      }],
      max_tokens: 2,
      temperature: 0,
      grammar: $g
    }')

  answer=$(curl -s \
    http://127.0.0.1:8081/v1/chat/completions \
    -H "Content-Type: application/json" \
    -d "$step2_payload" \
    | jq -r '.choices[0].message.content')

  echo "$answer"
}

この関数を利用し、先ほどの問題を解く。

ask_choice "37 + 48 = ?
A: 75
B: 85
C: 95"

結果は次のようになり、正答できることを確認した。

B

ただし、これでは二度推論が走り、効率が良いとは言えない。また、問題によっては、二度目の推論がなくとも正答できるケースもあった。したがって、以前から言及されていることではあるが、計算問題とは相性が悪く、ユースケースとして向かないものと思われる。

日本語Amazonレビューの感情分析

利用したデータセット

次に、日本語Amazonレビューの感情分析を試した。こちらのデータセットを拝借した。

このテストデータには、Amazonレビューと、positive、neutral、negativeの感情ラベルが含まれている。今回の判定では、感情と選択肢を次のように対応付けた。

A: negative
B: neutral
C: positive

モデルへ与えたプロンプトは次のようなものである。

Classify the following user review as either negative, neutral, or positive.
Answer with a single letter.

Review: <レビュー本文>

A: negative
B: neutral
C: positive

ベンチマーク用スクリプトを実装した

レビューを3,000件手作業でcurlするのは現実的ではない。そこで、CSVファイルを読み込み、レビューを1件ずつllama-serverへ送信するBashスクリプトを実装した。

このスクリプトでは、次の処理を行う。

  1. CSVファイルを1行ずつ読み込む
  2. レビュー本文と正解ラベルを取り出す
  3. curlでllama-serverへ問い合わせる
  4. 応答時間をミリ秒単位で計測する
  5. logprobsを確率へ変換する
  6. A、B、Cを感情ラベルへ変換する
  7. 正解・不正解を判定する
  8. 累計正答率を表示する
  9. 応答時間の最小値、中央値、平均値、最大値を表示する
  10. 全件処理後に最終正答率を表示する
#!/bin/bash

# 環境変数のデフォルト値設定
ENDPOINT="${ENDPOINT:-http://127.0.0.1:8081/v1/chat/completions}"
CSV_FILE="${1:-.}"

# 使用方法
if [[ ! -f "$CSV_FILE" ]]; then
    echo "Usage: $0 <csv_file>"
    echo "Environment: ENDPOINT (default: http://127.0.0.1:8081/v1/chat/completions)"
    exit 1
fi

echo "=========================================="
echo "Sentiment Classification Benchmark"
echo "=========================================="
echo "Endpoint: $ENDPOINT"
echo "CSV File: $CSV_FILE"
echo "=========================================="
echo ""

declare -a RESPONSE_TIMES
correct_count=0
total_tests=0

# CSVファイルを1行ずつ処理
while IFS=',' read -r text sentiment source; do
    ((total_tests++))

    # ヘッダー行をスキップ
    if [[ $total_tests -eq 1 ]]; then
        continue
    fi

    text=$(echo "$text" | sed 's/^[[:space:]]*"//; s/"[[:space:]]*$//')
    sentiment=$(echo "$sentiment" | sed 's/^[[:space:]]*"//; s/"[[:space:]]*$//')

    item_num=$((total_tests - 1))

    echo "=== Item $item_num ==="
    echo "Text: $text"
    echo ""

    start_time=$(date +%s%N)

    response=$(curl -s "$ENDPOINT" \
        -H "Content-Type: application/json" \
        -d '{
            "messages": [{
                "role": "user",
                "content": "Classify the following user review as either negative, neutral, or positive. Answer with a single letter.\n\nReview: '"$text"'\n\nA: negative\nB: neutral\nC: positive"
            }],
            "max_tokens": 1,
            "logprobs": true,
            "top_logprobs": 5,
            "temperature": 0
        }')

    end_time=$(date +%s%N)
    response_time=$(( (end_time - start_time) / 1000000 ))

    RESPONSE_TIMES+=("$response_time")

    answer=$(echo "$response" \
        | jq -r '.choices[0].logprobs.content[0].top_logprobs[0].token')

    logprob=$(echo "$response" \
        | jq '.choices[0].logprobs.content[0].top_logprobs[0].logprob')

    confidence=$(echo "$logprob" \
        | awk '{printf "%.2f", exp($1) * 100}')

    answer=$(echo "$answer" | tr '[:lower:]' '[:upper:]')

    case "$answer" in
        A) predicted_sentiment="negative" ;;
        B) predicted_sentiment="neutral" ;;
        C) predicted_sentiment="positive" ;;
        *) predicted_sentiment="unknown" ;;
    esac

    is_correct=0

    if [[ "$predicted_sentiment" == "$sentiment" ]]; then
        ((correct_count++))
        is_correct=1
    fi

    cumulative_accuracy=$(( correct_count * 100 / (total_tests - 1) ))

    min_time=${RESPONSE_TIMES[0]}
    max_time=${RESPONSE_TIMES[0]}
    sum_time=0

    for time in "${RESPONSE_TIMES[@]}"; do
        if (( time < min_time )); then min_time=$time; fi
        if (( time > max_time )); then max_time=$time; fi
        ((sum_time += time))
    done

    avg_time=$(( sum_time / (total_tests - 1) ))

    sorted_times=($(printf '%s\n' "${RESPONSE_TIMES[@]}" | sort -n))
    n=${#RESPONSE_TIMES[@]}

    if (( n % 2 == 1 )); then
        median_time=${sorted_times[$((n / 2))]}
    else
        median_time=$(( \
            (sorted_times[$((n / 2 - 1))] + sorted_times[$((n / 2))]) / 2 \
        ))
    fi

    echo "--- Answer ---"

    if [[ $is_correct -eq 1 ]]; then
        echo "Predicted: $predicted_sentiment [CORRECT]"
    else
        echo "Predicted: $predicted_sentiment [INCORRECT]"
    fi

    echo "Correct  : $sentiment"
    echo ""

    echo "--- Results ---"
    printf "Current Response Speed : %dms\n" "$response_time"
    printf "Cumulative Response Speed: min=%dms, median=%dms, avg=%dms, max=%dms\n" \
        "$min_time" "$median_time" "$avg_time" "$max_time"
    printf "Confidence             : %.2f%%\n" "$confidence"
    printf "Accuracy               : %d/%d (%d%%)\n" \
        "$correct_count" "$((total_tests - 1))" "$cumulative_accuracy"
    echo ""

done < "$CSV_FILE"

echo "=========================================="
echo "=== Final Statistics ==="
echo "Total tests: $((total_tests - 1))"

overall_accuracy=$(( correct_count * 100 / (total_tests - 1) ))

echo "Overall accuracy: $overall_accuracy% ($correct_count/$((total_tests - 1))"

echo "=========================================="

スクリプトの出力

実行結果を以下に示す。3,000件のテストデータをGraniteに分類させた。

(中略)
=== Item 2999 ===
Text: (省略)

--- Answer ---
Predicted: positive [CORRECT]
Correct  : positive

--- Results ---
Current Response Speed : 400ms
Cumulative Response Speed: min=13ms, median=401ms, avg=486ms, max=4320ms
Confidence             : 100.00%
Accuracy               : 2104/2999 (70%)

=== Item 3000 ===
Text: (省略)

--- Answer ---
Predicted: positive [CORRECT]
Correct  : positive

--- Results ---
Current Response Speed : 276ms
Cumulative Response Speed: min=13ms, median=401ms, avg=486ms, max=4320ms
Confidence             : 100.00%
Accuracy               : 2105/3000 (70%)

==========================================
=== Final Statistics ===
Total tests: 3000
Overall accuracy: 70% (2105/3000)
==========================================

感情分析の結果

3,000件を処理した結果は次のとおりである。 正答率は70% であった。

指標 結果
テスト件数 3,000件
正解件数 2,105件
誤答件数 895件
正答率 70%
最小応答時間 13ms
中央値 401ms
平均値 486ms
最大応答時間 4,320ms

Jevの比較検証では、日本語商品レビュー3クラス分類の正答率が82.5%であった。評価条件、モデル、プロンプト、サンプル数が異なるため単純比較はできないが、今回のGranite 4.1 3BはJevより明確に低い結果となった。

一方、ローカルLLMの検証として見ると、3,000件を外部APIなしで処理し、70%の正答率を得られた点は悪くないと考えている。

結果の考察

positiveとnegativeは比較的判定しやすかった

今回の実行結果を確認すると、positiveとnegativeが正解ラベルであるレビューでは、多くのケースで正答していた。positiveとnegativeは、レビュー本文に感情を直接表す表現が含まれることが多い。そのため、小規模なローカルLLMでも分類しやすかったと考えられる。

neutralの判定で苦戦した

一方、neutralでは誤答が多かった。neutralのレビューには、次のような特徴がある。

  • 強い満足も不満も表明していない
  • 商品の事実だけを説明している
  • 一部は満足しているが、一部には不満がある
  • 「普通」「値段相応」「可もなく不可もなく」といった曖昧な表現がある
  • positiveまたはnegativeの表現を含むが、全体としては中立である

例えば、次のレビューは判断が難しい。

目的は果たせましたが、特に感動はありません。

これはneutralとも読めるが、文脈によってはpositiveにも読める。

値段相応の商品です。

このレビューも、neutralと判断できる一方で、表現上はややnegativeに感じられる可能性がある。

感情分析の正解ラベルがneutralであっても、レビューの文章だけから一意に判断できるとは限らない。この曖昧さが、今回の誤答の大きな要因になったと考えている。

高い確信度でneutralを誤答する

今回の検証で特に気になったのは、neutralの誤答だけではない。誤答しているにもかかわらず、90%以上の確信度を示すケースが存在した。モデルが次のような判断をしていたことになる。

予測:positive
確信度:90%以上
正解:neutral

これは、実運用では注意が必要な挙動である。単純に「確信度が低いものだけ人間に回す」という設計にすると、このような誤答を見逃す可能性がある。モデルがneutralをpositiveやnegativeへ寄せる傾向を持っている場合、確信度だけでは判断の安全性を担保できない。

今回取得した確信度は、モデルが次に出力したラベルのトークン確率である。データセットの正解率に対して校正された確率ではないため、当然ながらこのようなことは起こり得る。この点は、Jevとの大きな違いである。

Jevの比較検証では、出力された確率やconfidenceが実際の正解率とどの程度一致するかも評価されていた。一方、今回のGraniteや一般的なローカルでは、確信度の校正を実施しない。

したがって、今後実用化するなら、次のような対策が必要になる。

  • neutral判定専用のプロンプトを用意する
  • positive、neutral、negativeの判断基準を明示する
  • 代表的なneutralレビューをFew-shot例として与える
  • 選択肢の説明文を改善する
  • 出力確率を検証データで校正する
  • neutralの判定だけ別モデルやルールで再確認する
  • positiveとnegativeの差が小さい場合はneutralへ寄せる処理を検討する

まとめ・所感

GraniteでもJevっぽい仕組みは作れる

今回は、ローカルLLMでJevのような判定を実現し、さらにマルチモーダル対応まで行っていた記事を読み、「Graniteだったらどうなるんだろう」と考え、IBM Granite 4.1 3BをM1 Mac mini上で動かした。

結果として、次の仕組みは実現できた。

  • 選択肢をA、B、Cなどの記号へ変換する
  • logprobsから候補と確率を取得する
  • 判定結果を感情ラベルへ変換する

一方で、計算問題のように途中の推論が必要なタスクでは、最初から1トークンに制限すると誤答するケースがあった。複雑な問題において、従来のLLMでは自由推論と記号抽出を分離する必要があると思われるが、Jevではそうした分離が必要ない点は新たな特徴と言えるだろう。

GraniteによるAmazonレビュー感情分析は正答率70%

日本語Amazonレビュー3,000件に対して感情分析を行った結果、正答率は70%であった。また、応答速度の
中央値401ms、平均486msで3,000件を処理できた。

Jevの比較検証では、Jevの日本語レビュー感情分析の応答時間中央値は約241msであり、正答率は82.5%であった。また、フロンティアモデルを使った実験では、日本語レビュー分類におけるOpenAIモデルの中央値が約647〜798msであったとのこと。

今回のGraniteは、正答率と速度の両面でJevに及ばなかった。ただし、外部APIではなく、M1 Mac mini上のローカル環境だけでこの結果を得られたことは、予想していたよりも健闘だったのではないか。丁寧に条件を揃えた比較ではないが、M1 Mac mini上の3Bローカルモデルが、フロンティアモデルに近いオーダーの応答速度で動作した点は興味深い。

neutralの見極めにはチューニングが必要

今回の結果から、Graniteで特に難しかったのはneutralの判定である。

positiveとnegativeが正解のレビューでは、多くのケースで正答できた。感情が明確に表現されているレビューであれば、3BクラスのローカルLLMでも十分に処理できる。しかし、neutralのレビューでは誤答が多かった。

さらに、誤答にもかかわらず90%以上の確信度を示すケースも見られた。これは単なる精度の問題ではなく、確信度の扱いにも問題があることを示している。

Graniteや従来のローカルLLMを実用的な感情分類器として利用するには、少なくとも次のチューニングが必要になるだろう。

  • neutralの定義を明示する
  • neutralの具体例をプロンプトへ追加する
  • mixedとneutralを区別する
  • positive、neutral、negativeの判断基準を文章化する
  • 検証データを使って確信度を校正する
  • neutralだけ別の判定処理へ分岐する

特に、neutralは単に「positiveでもnegativeでもないもの」と定義するだけでは不十分である。レビューのどの表現をneutralとみなすのかを具体的に与える必要がある。

ローカルLLMを選ぶ価値

GraniteのようなローカルLLMは、Jevの代わりにそのまま使えるわけではない。しかし、次のような条件では候補になるのではないか。

  • レビューや問い合わせを外部へ送信できない
  • 小規模なデータを処理したい
  • 多少の誤答を人間が確認できる
  • 問題の種類を限定できる

今回の感情分析のように、positiveとnegativeが比較的明確なデータであれば、70%という正答率でも用途によっては役立つ可能性がある。一方、neutralのような曖昧なクラスを高い信頼性で判定したい場合は、プロンプトやモデルのチューニングが必要である。さらに、確信度を使って自動処理と人手確認を振り分ける場合は、確信度の校正も欠かせない。

Jevは汎用的かつ高速な判断部品として優れている

今回の結果を踏まえて、Jevの優位性を理解できた。Jevは、判断専用のインターフェースを持ち、プロンプトやLLM呼び出し方法の工夫無しに、選択肢や確率、confidenceを構造化された形式で返す。さらに、複数の種類の判断を扱える。生成AIによる業務変革における新たな選択肢として、今後も注目したい。

参考リンク

3
1
2

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?