調べたいと思ったきっかけ
GoogleのLLMのライブラリを調べていて、一個ずつ見ていっていたのですが、だいたい同じように Temperature
, TopK
, TopP
などの設定を指定できます。
またOpenAIのAPIでもTemperature
やTopP
などが指定できます。
Temperature
, TopK
, TopP
がなにか理解したくありませんか?
LLM使いこなしていくために、これらについて調べてみました。なにか誤りなどがあればご指摘いただけるとありがたいです。
GoogleのLLMを使うSDKのコードたち
Gemini Nano with the Google AI Edge SDK
val generationConfig = generationConfig {
context = ApplicationProvider.getApplicationContext() // required
temperature = 0.2f
topK = 16
maxOutputTokens = 256
}
scope.launch {
// Single string input prompt
val input = "I want you to act as an English proofreader. I will provide you
texts, and I would like you to review them for any spelling, grammar, or
punctuation errors. Once you have finished reviewing the text, provide me
with any necessary corrections or suggestions for improving the text: These
arent the droids your looking for."
val response = generativeModel.generateContent(input)
print(response.t ext)
}
developer.android.com/ai/gemini-nano/experimental
MediaPipe Tasks
val options = LlmInferenceOptions.builder()
.setModelPATH('/data/local/.../')
.setMaxTokens(1000)
.setTopK(40)
.setTemperature(0.8)
.setRandomSeed(101)
.build()
llmInference = LlmInference.createFromOptions(context, options)
val inputPrompt = "Compose an email to remind Brett of lunch plans at noon on Saturday."
val result = llmInference.generateResponse(inputPrompt)
logger.atInfo().log("result: $result")
ai.google.dev/edge/mediapipe/solutions/genai/llm_inference/android
Vertex AI in Firebase
val generativeModel = Firebase.vertexAI.generativeModel(
modelName = "gemini-1.5-flash-preview-0514",
generationConfig = generationConfig {
temperature = 0.7f
topK = 40
}
)
val prompt = "Write a story about a magic backpack."
val response = generativeModel.generateContent(prompt)
print(response.text)
LLMの仕組みについて
LLMは文章を一度に生成するのではなく、一つずつトークン(単語またはサブワード)を予測していきます。
各トークンが生成されるたびに、そのトークンが入力に追加され、次のトークンの予測に使用されます。
例えば
来年はもっと
ってきたときに、LLMは 頑張る
というトークンを次に返し、その後に。
というトークンを返したりして、文章を作ります。
これから説明していくLLMから出力の流れを以下に書きます。
モデルが出力する スコア (logits
)
モデルは頑張る
を直接返すわけではなく、まず logits
1 と言われる各トークンのスコアを出力します。
例えば スコア(logits) はここでは来年はもっと
に対して、 頑張る
が一番スコアが高い状態になっています。
トークン | スコア(logit) |
---|---|
頑張る | 2.0 |
成長する | 1.8 |
学ぶ | 1.5 |
健康 | 1.0 |
新しい | 0.5 |
スコア(logits)を確率分布に変換するソフトマックス関数
スコア(logits)だけだと、どれを出すか選ぶのが難しいのでソフトマックス関数 2 というのを利用します。
ソフトマックス関数自体を理解するのはちょっと難しいのですが、この関数の性質が結構都合が良いのです。ソフトマックス関数はうまくそれぞれのトークンに対する確率(確率分布)の合計が100%になるように計算することができます。
例えば、スコアがマイナスの場合※や、値の差が大きいほど他のトークンを指数関数的に選ばれにくくするなど考慮できます。
※指数関数がマイナスをうまく扱える理由
$2 ^ x$であったときに $2 ^ 1$ は 2$2 ^ 0$ は 1
で、$2^{-1}$は1/2になります。
このようにマイナスでも指数関数で使うと正の値になります。この特徴をソフトマックス関数により使っています。
ここでは 来年はもっと
の次に 頑張る
を33.2
%の確率で出すという感じです。
トークン | スコア(logit) | 確率(ソフトマックス関数で計算) | どのぐらいの割合かのイメージ |
---|---|---|---|
頑張る | 2.0 | 33.2% | ###### |
成長する | 1.8 | 27.1% | ##### |
学ぶ | 1.5 | 20.1% | #### |
健康 | 1.0 | 12.2% | ## |
新しい | 0.5 | 7.4% | # |
この計算の計算コードや試せるリンク
Python計算コード
https://gist.github.com/takahirom/448d2c1e3abb0f76a9bbe4b2983052e7
Kotlinでの計算コード
https://gist.github.com/takahirom/6ec47fb00ab3fcc8289f281a3fc912cc
こちらのリンクではWeb上で、これから説明するTemperatureなどを変更したり、数式のコードなどを確認することができます。
https://pl.kotl.in/KBMhIIdWR
確率を元に単語が選ばれる
(上と同じ図)
トークン | スコア(logit) | 確率(ソフトマックス関数で計算) | どのぐらいの割合かのイメージ |
---|---|---|---|
頑張る | 2.0 | 33.2% | ###### |
成長する | 1.8 | 27.1% | ##### |
学ぶ | 1.5 | 20.1% | #### |
健康 | 1.0 | 12.2% | ## |
新しい | 0.5 | 7.4% | # |
この確率分布を元に単語が選ばれるという感じです。
来年はもっと
から 成長する
など。(2番目が選ばれた場合)
で、 Temperature
, TopK
, topP
とかって?
Temperature
まず Temperature
を理解する前に知っておきたいのが、
ソフトマックス関数は指数関数 2 を使うので差が大きいほど確率に差をつけるという性質があります。
Temperature
はこのLLMモデルの出力であるスコア(logits)に対して割り算します。
スコア(logits
) / Temperature
したものを ソフトマックス関数の入力として利用するということです。
$$
\text{softmax}\left(\frac{x_i}{T}\right)
$$
※ $ x_i $ は各トークンのロジット(モデルの出力スコア)で、${T}$ はTemperatureです。
例えば logitsの値が 2.0
で Temperature
が 0.1
なら 2.0 / 0.1
で 20.0
になります。
0.1
で割るとlogitsの値が10倍になるので、例えば、割り算した結果の 頑張る
と 新しい
で 20
と 5
でスコアにかなり差(20 - 5 = 15)が開きますよね?すると頑張る
の確率が87%になります。そのようにスコアの差を調整できるのが Temperature
です。
トークン | スコア(logit) | temperatureで割り算したスコア temperature = 0.1 |
確率(ソフトマックス関数で計算) | どのぐらいの割合かのイメージ |
---|---|---|---|---|
頑張る | 2.0 | 20.0 | 87.6% | ################# |
成長する | 1.8 | 18.0 | 11.8% | ## |
学ぶ | 1.5 | 15.0 | 0.6% | |
健康 | 1.0 | 10.0 | 0.0% | |
新しい | 0.5 | 5.0 | 0.0% |
temperature = 2.0
だと、頑張る
が26%
のみになり、かなり均等に出るようになります。これは割り算した結果の差が1.0
と 0.3
の差(0.7
)しかないためです。これにより Temperature
が高いとさまざまな トークンが出力されるようになるのです。
トークン | スコア(logit) | temperatureで割り算したスコア temperature = 2.0 |
確率(ソフトマックス関数で計算) | どのぐらいの割合かのイメージ |
---|---|---|---|---|
頑張る | 2.0 | 1.0 | 26.6% | ##### |
成長する | 1.8 | 0.9 | 24.0% | #### |
学ぶ | 1.5 | 0.8 | 20.7% | #### |
健康 | 1.0 | 0.5 | 16.1% | ### |
新しい | 0.5 | 0.3 | 12.6% | ## |
この Temperature による割り算 を図に入れると以下のようになります。
Temperatureの値が高いほど、logits(スコア)の差が小さくなることで、さまざまなものが出やすくなる代わりに、変なものがでてしまう確率が高まっていきます。
TopK
, TopP
は?
多様なものを出したいけど、あまりにも例外的なものは出したくないことがよくあります。
そういったときにTemperature
を計算した後にこのトークンをフィルタリングする処理を行います。
TopK
ではスコアの上位K個のトークンだけをフィルタリングします。これにより極端に低い確率のトークンを排除し、生成されたテキストの品質を保持します。
例えば スコア(logits)の上位 3つのトークンだけを指定してそれだけ残し、ソフトマックス関数にかけることができます。
トークン | スコア(logit) | 確率 | どのぐらいの割合かのイメージ |
---|---|---|---|
頑張る | 2.0 | 41.2% | ######## |
成長する | 1.8 | 33.8% | ###### |
学ぶ | 1.5 | 25.0% | ##### |
健康 | 1.0 | 0.0% | |
新しい | 0.5 | 0.0% |
また TopP
では一度ソフトマックス関数にかけて、上から確率を足し合わせていき、そのTopP
の値に達するまでのトークンを考慮します。これにより動的にトークンの数を調整し、重要な選択肢を考慮しながら多様性を持たせます。
例えば50%を指定したら、50%消費されるまでの上位を選ぶ形になります。
トークン | スコア(logit) | 確率変更前 | 確率変更後 | どのぐらいの割合かのイメージ |
---|---|---|---|---|
頑張る | 2.0 | 33.2% | 55.0% | ########## |
成長する | 1.8 | 27.1% | 45.0% | ######### |
学ぶ | 1.5 | 20.1% | 0.0% | |
健康 | 1.0 | 12.2% | 0.0% | |
新しい | 0.5 | 7.4% | 0.0% |
他にも MinP
などが LLMの仕組みである Transformer
に仕組みが追加されたりしているようですが、なんとなくここまで読んだ方は意味がわかったりするのではないでしょうか?
このソフトマックス関数とか知ってなにか意味あるの?
ソフトマックス関数を知っておくと、Temperatureとは何かなどが分かるだけでなく、応用が効くようになります。例えば、有害コンテンツのフィルタリングのソフトウェアを作りたいときに、LLMにこのコンテンツが有害であるかどうかを聞いた後に、このソフトマックス関数を直接使って、次のLLMからの回答トークンが "Yes"になる確率と"No"になる確率だけを取得することができます。それにより、「"Yes"の確率が高いので有害である」など有害コンテンツのフィルタリングを実装するようなことができたりするわけです。
例: Evaluating content safety with ShieldGemma and Keras
実際のソフトマックス関数を使った有害コンテンツのフィルタリング判定コード
コードの中にlogitsやsoftmaxが出てきてテンション上がりますよね?
last_logits = keras.ops.take(logits, last_prompt_index, axis=1)[:, 0]
yes_logits = last_logits[:, self.yes_token_idx]
no_logits = last_logits[:, self.no_token_idx]
yes_no_logits = keras.ops.stack((yes_logits, no_logits), axis=1)
return keras.ops.softmax(yes_no_logits, axis=1)
まとめ
ちょっと中のことを知ってみると、実際の動きを考えながらパラメーターを指定できるので効率的になり、LLMの発展的な使い方も見えてくるのではないでしょうか。わからないことは多いですが学んでいきたいです。
-
このブログによると "logitsとは、「ソフトマックス活性化関数に通す前のニューラルネットワークの出力」" だそうです。 https://minus9d.hatenablog.com/entry/2020/10/25/193018#google_vignette ↩
-
ソフトマックス関数は以下の式で定義されます。(今回は分からなくても問題なし。)
$$
\text{softmax}(x_i) = \frac{e^{x_i}}{\sum_{j} e^{x_j}}
$$
ここで、$( x_i )$ は各トークンのロジット(モデルの出力スコア)です。この関数により、各トークンのスコアを確率分布に変換します。 ↩ ↩2