LLMサンプリングにおける3つのペナルティ

LLMサンプリングにおける3つのペナルティ
Photo by Ivan Torres / Unsplash

こんにちは!(株)Qualiteg プロダクト開発部です!

今日の昼食はみんなでイタリアンレストランに行きました。3種のチーズピザが好評でした。

さて、本日はLLMにおける3種のチーズならぬ、3種のペナルティをご紹介します。

ChatStreamでは、ペナルティを含む、多彩なプリセットサンプリングアルゴリズムを搭載しています。モデルや目的にあったサンプリングを行うことで、より自然な応答生成を行うことができます。

テキスト生成におけるペナルティの役割

自然言語処理(NLP)、LLMの世界で重要な概念の一つである「ペナルティ」とは何でしょうか。

ペナルティとは?

ペナルティとは、LLMがテキストを生成する際に特定のトークン(単語や文字列)の出現を調整するために使用される仕組みのことです。

テキスト生成モデルが自然で多様な文章を作り出すためには、同じ単語やフレーズが何度も繰り返されるのを防ぐ必要があります。そこで登場するのが、ペナルティです。

LLMが同じような文章、単語を何度も生成してしまうことはわりと頻繁に起こりますので、適切なペナルティを設定します。

ペナルティの種類

テキスト生成におけるペナルティにはいくつかの種類がありますが、今回は特に重要な3つのペナルティについて説明します。

  • Repetition Penalty:
    • 範囲: 繰り返し出現するすべてのトークンやフレーズ。
    • ペナルティ基準: すべての過去のトークンに基づく。
    • 使用例: 過去に生成されたトークンすべてに対して、ペナルティを適用。
  • Frequency Penalty:
    • 範囲: 特定のトークンの出現頻度。
    • ペナルティ基準: 各トークンの出現回数に基づく。
    • 使用例: 生成中に特定のトークンが何回生成されたかを追跡し、その頻度に基づいてペナルティを適用。
  • Presence Penalty:
    • 範囲: 一度でも生成されたトークン。
    • ペナルティ基準: トークンが一度でも生成されたかどうかに基づく。
    • 使用例: すでに生成されたトークンに対して、再出現時にペナルティを適用。

ペナルティの実装

では、実際にペナルティを実装していきましょう。

ChatStream のサンプリングクラスとしてペナルティを実装する

サンプリングクラスは AbstractLogitsProcessor をオーバーライドします。

from chatstream.token_samplers.logits_processor import AbstractLogitsProcessor

AbstractLogitsProcessorはシンプルな抽象クラスで、以下のようになっています。


from abc import ABC, abstractmethod


class AbstractLogitsProcessor(ABC):
    @abstractmethod
    def process(self, logits, params):
        pass

    @abstractmethod
    def get_name(self):
        pass

Repetition Penalty

さっそく、Repetition Penalty を実装してみましょう。

計算手法として乗算型と減算型の二種類を指定できるようにしており、あるトークンがすでに生成された場合、そのトークンのログ確率をペナルティ値で割る(乗算)か、ペナルティ値を引く(減算)というオペレーションを実装していますい。すべての過去のトークンが対象となります。

from chatstream.token_samplers.logits_processor import AbstractLogitsProcessor


class RepetitionPenaltyProcessor(AbstractLogitsProcessor):
    """
    繰り返しのトークンに対してペナルティを適用するプロセッサ。

    このクラスは、過去に使用されたトークンのlogitsを調整することで、繰り返しの出現を抑制する。
    ペナルティの適用方法は、乗算または減算のいずれかで、パラメータで指定できる。

    乗算ペナルティ計算の基本は logits[token_id] /= penalty で、過去に出現した token_id のロジット値を減らしていき
    出現確率を下げることで繰り返し同じトークンが出力されることを抑制する

    """

    def __init__(self):
        pass

    def process(self, logits, params):

        past_tokens = params.get("past_tokens", None)
        penalty = params.get("penalty", None)
        penalty_method = params.get("penalty_method", "multiplicative")

        # 過去のトークンのlogitsにペナルティを適用
        if penalty is not None and past_tokens is not None:
            # logitsのコピーを作成(引数として渡されたlogitsの非破壊保証)
            adjusted_logits = logits.clone()
            # penaltyの値の型を確認する
            if not isinstance(penalty, (int, float)):
                raise ValueError(f"penalty should be a scalar value, but got {penalty}({type(penalty)})")

            # ペナルティの適用方法に応じてlogitsを更新する
            if penalty_method == "multiplicative":
                if penalty != 1.0:
                    for token_id in set(past_tokens):
                        adjusted_logits[token_id] /= penalty

            elif penalty_method == "subtractive":
                for token_id in set(past_tokens):
                    adjusted_logits[token_id] -= penalty
            else:
                raise ValueError(f"Unknown penalty_method: {penalty_method}")
        else:
            adjusted_logits=logits

        return {"name": "RepetitionPenaltyProcessor", "type": "logits", "logits": adjusted_logits}


    def get_name(self):
        return "rep_penalty"

Frequency Penalty

Frequency Penaltyは以下のようになります。

トークンが出現するたびに、各トークンの出現回数に基づきそのトークンのログ確率をペナルティ値で累積的に割る(乗算)か、ペナルティ値を累積的に引く(減算)というオペレーションを実装しています。

class FrequencyPenaltyProcessor(AbstractLogitsProcessor):
    """
    生成されたトークンの出現頻度に基づいてペナルティを適用するプロセッサ。

    このクラスは、生成中に各トークンが出現した回数を追跡し、頻繁に出現するトークンにペナルティを適用する。
    ペナルティの適用方法は乗算または減算のいずれかで、パラメータで指定できる。
    """

    def __init__(self):
        self.token_counts = {}

    def process(self, logits, params):
        penalty = params.get("penalty", None)
        penalty_method = params.get("penalty_method", "multiplicative")

        # logitsのコピーを作成
        adjusted_logits = logits.clone()

        if penalty is not None:
            if not isinstance(penalty, (int, float)):
                raise ValueError(f"penalty should be a scalar value, but got {penalty}({type(penalty)})")

            for token_id, count in self.token_counts.items():
                if penalty_method == "multiplicative":
                    adjusted_logits[token_id] /= (penalty ** count)
                elif penalty_method == "subtractive":
                    adjusted_logits[token_id] -= (penalty * count)
                else:
                    raise ValueError(f"Unknown penalty_method: {penalty_method}")

        return {"name": "FrequencyPenaltyProcessor", "type": "logits", "logits": adjusted_logits}

    def update_token_counts(self, token_ids):
        for token_id in token_ids:
            if token_id in self.token_counts:
                self.token_counts[token_id] += 1
            else:
                self.token_counts[token_id] = 1

    def get_name(self):
        return "freq_penalty"

Presence Penalty

Presence Penalty は以下のようになります。

トークンが一度でも生成されたかどうかに基づき一度生成されたトークンのログ確率をペナルティ値で割る(乗算)か、ペナルティ値を引く(減算)というオペレーションを実装しています。

class PresencePenaltyProcessor(AbstractLogitsProcessor):
    """
    生成されたトークンの存在に基づいてペナルティを適用するプロセッサ。

    このクラスは、特定のトークンがすでに出現しているかどうかを追跡し、存在するトークンにペナルティを適用する。
    ペナルティの適用方法は乗算または減算のいずれかで、パラメータで指定できる。
    """

    def __init__(self):
        self.seen_tokens = set()

    def process(self, logits, params):
        penalty = params.get("penalty", None)
        penalty_method = params.get("penalty_method", "multiplicative")

        # logitsのコピーを作成
        adjusted_logits = logits.clone()

        if penalty is not None:
            if not isinstance(penalty, (int, float)):
                raise ValueError(f"penalty should be a scalar value, but got {penalty}({type(penalty)})")

            for token_id in self.seen_tokens:
                if penalty_method == "multiplicative":
                    adjusted_logits[token_id] /= penalty
                elif penalty_method == "subtractive":
                    adjusted_logits[token_id] -= penalty
                else:
                    raise ValueError(f"Unknown penalty_method: {penalty_method}")

        return {"name": "PresencePenaltyProcessor", "type": "logits", "logits": adjusted_logits}

    def update_seen_tokens(self, token_ids):
        self.seen_tokens.update(token_ids)

    def get_name(self):
        return "presence_penalty"

他のサンプリングパラメータとペナルティ

さて、他のサンプリングパラメータとの関係についてもみておきましょう。

まず、上のように実装したペナルティのおさらいですが、

ペナルティ

  1. Repetition Penalty: 特定のトークンやフレーズの繰り返しを防ぎます。
  2. Frequency Penalty: 生成されたトークンの出現頻度に基づいてペナルティを適用し、頻繁に出現するトークンを抑制します。
  3. Presence Penalty: 一度生成されたトークンが再度出現するのを防ぎます。

Top-k, Top-p, Temperature

次は、この3つです。また3種ですね。

この3つは特によく登場するサンプリング手法です。

top-ktop-p、および temperature は生成されるテキストの質と多様性を制御するためのパラメータです。簡単に説明すると、

Top-k

目的: 最も確率の高い k 個のトークンだけを考慮します。

  • 方法: 確率の高い k 個のトークンを選び、その中から次のトークンをランダムに選択します。
  • 計算方法:
    1. トークンの確率を降順に並べ替えます。
    2. 上位 k 個のトークンを選びます。
    3. その中から次のトークンを選択します。

Top-p (または Nucleus Sampling)

目的: トークンの累積確率が p(例:0.9)となるまでトークンを選択します。

  • 方法: 確率の高いトークンを累積確率が p になるまで選び、その中から次のトークンをランダムに選択します。
  • 計算方法:
    1. トークンの確率を降順に並べ替えます。
    2. 確率の累積和が p を超えるまでトークンを選びます。
    3. その中から次のトークンを選択します。

Temperature

目的: 生成されるテキストのランダム性を制御します。

  • 方法: temperature を用いてトークンの確率分布を調整します。
  • 計算方法:
    1. 各トークンのログ確率を temperature で割ります。
    2. これにより、確率分布がスムーズになります(高温度:分布が平坦に、低温度:分布が尖ります)。

ペナルティと top_k,top_p,temperatureの計算シナリオ

たとえば、Qualiteg May Change the World with ChatStream というテキスト生成を行うシナリオで考えてみましょう。

ペナルティとTop-k, Top-p, Temperatureの相互作用

Qualiteg May Change the World with ChatStreamという文章生成において、どのようにペナルティが適用され、top-ktop-ptemperature とどのように連携するか以下にをみていきます。

1. ペナルティの適用

  • ペナルティの適用:
    • Repetition Penalty、Frequency Penalty、Presence Penalty が適用され、特定のトークンのログ確率が調整されます。
    • 例えば、「Qualiteg」という単語がすでに何度か出現している場合、その単語のログ確率が低くなります。

2. Temperatureの適用

  • Temperatureの適用:
    • 調整されたログ確率は temperature によってさらにスケールされます。
    • これにより、分布の形状が変わり、生成されるトークンのランダム性が増減します。
    • 高温度(例:1.2)の場合、分布が平坦になり、ランダム性が増します。
    • 低温度(例:0.7)の場合、分布が尖り、最も高い確率のトークンが選ばれやすくなります。

3. Top-kの適用

  • Top-kの適用:
    • top-k が適用され、上位 k 個のトークンのみが選択肢として残ります。
    • これにより、最も確率の高いトークンの中から次のトークンが選ばれます。
    • 低確率のトークンが除外され、生成されるテキストの質が高まります。

4. Top-pの適用

  • Top-pの適用:
    • top-p が適用され、累積確率が p を超えるまでトークンが選択されます。
    • これにより、確率の高いトークンの中から次のトークンがランダムに選ばれます。
    • こちらも低確率のトークンを除外し、生成されるテキストの質が高まります。

サンプリング計算の具体例

例えば、以下のような設定のとき、どのように計算されていくかを具体的にみていきましょう

  • 設定:

    • Repetition Penalty: 1.2
    • Frequency Penalty: 0.8
    • Presence Penalty: 1.5
    • Temperature: 0.7
    • Top-k: 50
    • Top-p: 0.9
  • ステップ 1: 初期のログ確率の計算
    まず、モデルが各トークンの初期のログ確率(logits)を計算します。例えば、以下のような初期ログ確率が得られたとします:

["Qualiteg": 2.0, "May": 1.5, "Change": 1.0, "the": 0.5, "World": 0.3, "with": 0.2, "ChatStream": 0.1]
  • ステップ 2: ペナルティの適用
    次に、各ペナルティを適用します。以下の例では、すでに「Qualiteg」が1回出現しており、他のトークンは初めて出現するものとします。

    • Repetition Penalty: 「Qualiteg」がすでに出現しているため、logits["Qualiteg"] に 1.2 のペナルティを適用します。
    logits["Qualiteg"] /= 1.2
    2.0 / 1.2 ≈ 1.67
    
    • Frequency Penalty: 出現頻度に基づくペナルティを適用します。「Qualiteg」は1回出現しているため、頻度ペナルティを適用します。
    logits["Qualiteg"] *= 0.8
    1.67 * 0.8 ≈ 1.34
    
    • Presence Penalty: 「Qualiteg」がすでに存在しているため、logits["Qualiteg"] に 1.5 のペナルティを適用します。
    logits["Qualiteg"] /= 1.5
    1.34 / 1.5 ≈ 0.89
    

    ペナルティを適用した後のログ確率

    ["Qualiteg": 0.89, "May": 1.5, "Change": 1.0, "the": 0.5, "World": 0.3, "with": 0.2, "ChatStream": 0.1]
    
  • ステップ 3: Temperatureの適用
    次に、temperature を適用して確率分布を調整します。temperature が 0.7 に設定されている場合、各ログ確率は 0.7 で割られます。

    logits["Qualiteg"] /= 0.7
    0.89 / 0.7 ≈ 1.27
    
    logits["May"] /= 0.7
    1.5 / 0.7 ≈ 2.14
    
    logits["Change"] /= 0.7
    1.0 / 0.7 ≈ 1.43
    
    logits["the"] /= 0.7
    0.5 / 0.7 ≈ 0.71
    
    logits["World"] /= 0.7
    0.3 / 0.7 ≈ 0.43
    

    調整後のログ確率:

    ["Qualiteg": 1.27, "May": 2.14, "Change": 1.43, "the": 0.71, "World": 0.43, "with": 0.29, "ChatStream": 0.14]
    
  • ステップ 4: Top-kの適用
    次に、top-k を適用します。ここでは top-k=50 ですが、上位5個のトークンのみを示します。

    上位トークン:

    ["May": 2.14, "Change": 1.43, "Qualiteg": 1.27, "the": 0.71, "World": 0.43]
    
  • ステップ 5: Top-pの適用
    最後に、top-p を適用します。ここでは top-p=0.9 です。累積確率が 0.9 を超えるまでトークンを選択します。

    累積確率の計算:

    • 確率の計算:

      • May: exp(2.14) ≈ 8.50
      • Change: exp(1.43) ≈ 4.18
      • Qualiteg: exp(1.27) ≈ 3.56
      • the: exp(0.71) ≈ 2.03
      • World: exp(0.43) ≈ 1.54

      合計:8.50 + 4.18 + 3.56 + 2.03 + 1.54 ≈ 19.81

    • 累積確率の計算:

      • May: 8.50 / 19.81 ≈ 0.43
      • Change: 4.18 / 19.81 ≈ 0.21
      • Qualiteg: 3.56 / 19.81 ≈ 0.18
      • ここまでの累積確率:0.43 + 0.21 + 0.18 ≈ 0.82
      • the: 2.03 / 19.81 ≈ 0.10(累積確率:0.82 + 0.10 ≈ 0.92)

    累積確率が 0.9 を超えたため、the までのトークンが選択肢に残ります

    ["May": 2.14, "Change": 1.43, "Qualiteg": 1.27, "the": 0.71]
    
  • ステップ 6: トークンの選択
    最終的に残ったトークンからランダムに次のトークンが選ばれます。この例では、["May", "Change", "Qualiteg", "the"] の中から1つが選ばれます。

このシナリオでは、ペナルティ、temperaturetop-ktop-p がどのように組み合わさってテキスト生成に影響を与えるかをみてきました。

ペナルティは特定のトークンの出現確率を調整し、temperature は分布の形状を変え、top-ktop-p は選択肢を絞り込むことで、最終的に生成されるテキストの質と多様性を制御することが実際の計算過程を追うことで理解できたとおもいます。

まとめ

今日は、3種類のペナルティとその周辺にあるサンプリング手法をみてきました。

ChatStreamにも今日作成した PenaltyProcessorを取り込むことが可能です。(ただし、ChatStreamにはすでにPenaltyProcessorのプリセット実装が存在しますが) 基本的に、logitsをどのようにサンプリングするかはサービス提供者の自由ですので、好きなProcessorを好きな順序で組み合わせることができます。
サンプリングを適用したいモデルに対して、もっとも好ましい出力となるようなサンプリングクラス(関数)の組み合わせ方、実際の値をどうするか、などは実際のモデルの入出力結果を測定して判断していくことになるとおもいます。このあたりは、ノウハウのかたまりでもあるので、もしご興味があればQualitegにぜひご相談ください。

ペナルティの比較

[付録]ペナルティの比較

ペナルティタイプ 目的 適用方法 ペナルティの例
Repetition Penalty 特定のトークンやフレーズが繰り返されるのを防ぐ。 過去に生成されたすべてのトークンのログ確率(logits)に対してペナルティを適用する。 例えば、あるトークンがすでに生成された場合、そのトークンのログ確率をペナルティ値で割る(乗算)か、ペナルティ値を引く(減算)。
Frequency Penalty 生成されたトークンの出現頻度に基づいてペナルティを適用し、頻繁に出現するトークンを抑制する。 各トークンが生成された回数に基づいてペナルティを適用する。トークンが出現するたびに、そのトークンの出現確率を低減させる。 トークンが出現するたびに、そのトークンのログ確率をペナルティ値で累積的に割る(乗算)か、ペナルティ値を累積的に引く(減算)。
Presence Penalty すでに生成されたトークンが再度出現するのを防ぐ。 トークンが一度でも生成されたかどうかに基づいてペナルティを適用する。一度生成されたトークンには再出現の際にペナルティが適用される。 一度生成されたトークンのログ確率をペナルティ値で割る(乗算)か、ペナルティ値を引く(減算)。

Read more

Claude Opus 5.0 完全ガイド モデル仕様とAPI・Claude Code運用ポイント

Claude Opus 5.0 完全ガイド モデル仕様とAPI・Claude Code運用ポイント

こんにちは! 2026年7月24日、AnthropicからClaude Opus 5がリリースされました。 Opus 4.8(5月28日リリース)からわずか2ヶ月での世代交代です。このあたりのスピード感、加速していますね。 さて、当ブログではClaude Opus 4.7 完全ガイド、Claude Opus 4.8 完全ガイドとOpusの世代を追いかけてきましたが、今回のOpus 5は過去2回の「4.x内のアップデート」とは立て付けが根本的に違います。 何が違うのか。まず、Opus 5は「最上位モデル」ではありません。 Anthropicのラインナップには2026年6月9日リリースのClaude Fable 5が最上位として存在し、Opus 5はその下位、Sonnet 5の上位という「中上位」ポジションで投入されました。 Opusという名前が「最上位ティア」を意味した時代は、Fable 5の登場で終わっています。 そのうえでAnthropicはOpus 5を「

By Qualiteg プロダクト開発部
TensorRT 10 × Blackwell 移行ガイド【前編】RTX 50 で推論資産が動かない — 基本と最初の壁

TensorRT 10 × Blackwell 移行ガイド【前編】RTX 50 で推論資産が動かない — 基本と最初の壁

こんにちは! 新しい GPU を手に入れてワクワクしながら既存の推論環境を載せ替えたら、 昨日まで普通に動いていたものが軒並みエラーで止まった そんな経験はないでしょうか。NVIDIA RTX 50 系、NVIDIA RTX PRO 系(Blackwell 世代)への移行では、これがかなりの高確率で起きます。 そして厄介なことに、エラーで止まってくれるのは、まだ親切なほうで、、TensorRT の世界には 「ビルドは通る、実行も通る、速度もちゃんと出る、けれど出力だけが静かに壊れている」 という、いちばん見つけにくい失敗の仕方が存在します。 本記事はその全体像を扱うシリーズの前編です。 対象環境 OS: Ubuntu 24.04 (WLS) GPU: NVIDIA RTX PRO 4000 Blackwell・GeForce RTX 5060 Ti (ともに Compute Capability 12.

By Qualiteg プロダクト開発部
Kimi K3 徹底リサーチ — 2.8兆パラメータ、「史上最大のオープンウェイト」は実現するか

Kimi K3 徹底リサーチ — 2.8兆パラメータ、「史上最大のオープンウェイト」は実現するか

こんにちは! 2026年7月16日、中国・北京の Moonshot AI が新しいフラッグシップモデル Kimi K3 を発表し、APIやWebサービスでの提供を開始しました。 総パラメータ2.8兆という規模、100万トークンのコンテキスト、そして 「史上最大のオープンウェイトモデルになる」 という宣言がAI界隈をにぎわせています。 当ブログでは今年5月の記事「Mythos(ミュトス)レベルのオープンモデルはいつ出るのか」で、オープンモデルがクローズドのフロンティアにいつ追いつくのかを予測しました。 Kimi K3 は、まさにその問いに対する現時点での最新の「回答」のひとつです。 一方で、この記事を書いている7月20日時点では、モデルのウェイトも技術レポートもまだ公開されていません。 ただし、XなどSNSかいわいでは、 「ガードレールが弱めで、Fable5では拒否されるようなプロンプトでも対応してくれる」 「すぐにOpus4.8にフォールバックする Fable5より使い勝手がいい」 といった声が散見されており、 米国産のガードレール強め方針にたいして、ガードレール

By Qualiteg プロダクト開発部
PII 非識別化の本質——「誰か」は偽ってよい、「何が起きたか」は偽ってはならない

PII 非識別化の本質——「誰か」は偽ってよい、「何が起きたか」は偽ってはならない

こんにちは!Qualitegプロダクト開発部です! 本日は、PII( Personally Identifiable Information→個人情報)の非識別化に関する内容を解説いたします。 当社ではこれまで、高精度なPII検出技術やLLM利用時の段階的PIIマスキング、PII検出のテスト設計など、個人情報検出とAIセキュリティに関する技術解説をお届けしてきました。 現在、当社では、PII検出マスキング技術「PII-FIエンジン」と、それを活用したPIIのマスキング・非識別化サービス「PII-FI Scan」「PII-FI API」を開発・提供しています。 本記事では、「PIIを検出したあと、それをどう書き換えるか」の設計原則を、1つの例文を試金石にして、私たちが実際のプロダクトで採用している整理をご紹介します。 先にことわっておきますと、本記事でいう「非識別化(de-identification)」は、文書やログを安全に共有・分析するための技術的な加工(個人を特定できないように加工する処理)のお話です。 個人情報保護法上の「仮名加工情報」「匿名加工情報」に該当することを

By Qualiteg プロダクト開発部, Qualiteg AIセキュリティチーム