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

発話音声からリアルなリップシンクを生成する技術 第3回:wav2vec特徴量から口形パラメータへの学習

発話音声からリアルなリップシンクを生成する技術 第3回:wav2vec特徴量から口形パラメータへの学習

こんにちは! 前回までの記事では、 * wav2vecを用いた音声特徴量抽出の仕組み(第1回)と、 * リップシンク制作における累積ドリフトの補正技術(第2回) について解説してきました。今回はいよいよ、これらの技術を統合して実際に音声から口の動きを生成する核心部分に踏み込みます。 本記事で扱うのは、wav2vecが抽出した768次元の音響特徴量を、26個の口形制御パラメータの時系列データに変換する学習プロセスです。これは単なる次元削減ではありません。音の物理的特性を表す高次元ベクトルから、人間の口の動きという全く異なるモダリティへの変換なのです。この変換を実現するには、音韻と視覚的な口形の間にある複雑な対応関係を、ニューラルネットワークに学習させる必要があります。 特に重要なのは、この対応関係が静的ではなく動的であるという点です。同じ音素でも前後の文脈によって口の形が変わり、さらに音が聞こえる前から口が動き始めるという時間的なズレも存在します。これらの複雑な現象をどのようにモデル化し、学習させるのか。本記事では、LSTMとTransformerという2つの強力なアプロー

By Qualiteg 研究部
AI時代のデータ漏洩防止の要諦とテクノロジー:第1回 AI DLPとPROXY

AI時代のデータ漏洩防止の要諦とテクノロジー:第1回 AI DLPとPROXY

こんにちは!本日はAI時代のデータ漏洩防止について、とくにその通信技術面に焦点をあてつつ、AIセキュリティにどのように取り組んでいくべきか、解説いたします。 1. はじめに 生成AIの急速な普及により、企業のデータガバナンスは新たな局面を迎えています。ChatGPTやClaudeといった大規模言語モデル(LLM)は、業務効率を飛躍的に向上させる一方で、意図しない機密情報の漏洩という深刻なリスクをもたらしています。 従業員が何気なく入力した顧客情報や営業秘密が、AIサービスの学習データとして使用される可能性があることを、多くの組織はまだ十分に認識していません。従来のDLP(Data Loss Prevention)ソリューションは、メールやファイル転送を監視することには長けていましたが、リアルタイムで行われるWebベースのAIチャットやAIエージェントとの対話で発生しうる新しい脅威には対応できていないのが現状です。 本記事では、AI時代のデータ漏洩防止において中核となる技術、特にHTTPS通信のインターセプトとその限界について、技術的な観点から詳しく解説します。プロキシサーバー

By Qualiteg プロダクト開発部, Qualiteg コンサルティング
LLM推論基盤プロビジョニング講座 第5回 GPUノード構成から負荷試験までの実践プロセス

LLM推論基盤プロビジョニング講座 第5回 GPUノード構成から負荷試験までの実践プロセス

こんにちは!これまでのLLM推論基盤プロビジョニング講座では、推論速度の定義、リクエスト数見積もり、メモリ消費量計算、推論エンジン選定について詳しく解説してきました。 今回は、残りのステップである「GPUノード構成見積もり」「負荷試験」「トレードオフ検討」について一気に解説し、最後に実際のサーバー構成例をご紹介します。 STEP5:GPUノード構成見積もり GPUメモリから考える同時リクエスト処理能力 LLMサービスを構築する際、どのGPUを何台選ぶかは非常に重要な決断です。今回はLlama 8Bモデルを例に、GPUメモリ容量と同時リクエスト処理能力の関係を見ていきましょう。 GPUメモリの使われ方を理解する ここは復習となりますが、 LLM推論においてGPUメモリは主に2つの用途で消費されます 1. モデル重みデータ: LLMモデル自体を格納するためのメモリ 2. KVキャッシュ: ユーザーとの対話コンテキストを保持するための一時メモリ Llama 8Bを16ビット精度で実行する場合、モデル重みデータは約16GBのメモリを占めます。これは固定的なメモリ消

By Qualiteg コンサルティング
発話音声からリアルなリップシンクを生成する技術 第2回:AIを使ったドリフト補正

発話音声からリアルなリップシンクを生成する技術 第2回:AIを使ったドリフト補正

こんにちは! 前回の記事では、当社のMotionVoxで使用している「リップシンク」技術について、wav2vecを用いた音声特徴量抽出の仕組みを解説しました。音声から正確な口の動きを予測するための基礎技術について理解いただけたかと思います。 今回は、その続編として、リップシンク制作における重要な技術的課題である「累積ドリフト」に焦点を当てます。wav2vecで高精度な音素認識ができても、実際の動画制作では複数の音声セグメントを時系列に配置する際、わずかなタイミング誤差が蓄積して最終的に大きなずれとなる現象が発生します。 本記事では、この累積ドリフトのメカニズムと、機械学習を活用した最新の補正技術について、実際の測定データを交えながら詳しく解説していきます。前回のwav2vecによる特徴抽出と今回のドリフト補正技術を組み合わせることで、MotionVoxがどのように高品質なリップシンクを実現しているのか、その全体像が見えてくるはずです。 累積ドリフトとは何か 基本概念 累積ドリフトとは、個々の音声セグメントが持つ微小なタイミング誤差が、時間の経過とともに蓄積していく現象で

By Qualiteg 研究部