Constitutional AIとは?原則に基づくアライメント手法を解説

AIシステムを有害な出力から守りつつ、有用性を維持する方法として、Constitutional AI(CAI)が注目されています。人間のフィードバックを最小限に抑えながら、AIシステム自身が原則に基づいて出力を改善する革新的なアプローチです。

本記事では、Anthropicが提案したConstitutional AIの仕組みと実装方法を解説します。

本記事の内容

  • Constitutional AIの基本概念と動機
  • 原則(Constitution)の設計
  • SL-CAIとRL-CAIの二段階プロセス
  • Pythonでの実装例

前提知識

この記事を読む前に、以下の記事を読んでおくと理解が深まります。

RLHFの課題

人間フィードバックへの依存

従来のRLHF(Reinforcement Learning from Human Feedback)には以下の課題があります。

  1. スケーラビリティ: 大量の人間ラベルが必要
  2. コスト: 専門的なアノテーターが高コスト
  3. 一貫性: 人間の判断にばらつきがある
  4. 透明性: 何を基準に判断しているか不明確

Constitutional AIのアプローチ

Constitutional AIは、明示的な原則(Constitution)を定義し、AIシステム自身にその原則に基づいて出力を評価・改善させます。

$$ \text{Output}’ = \text{AI}(\text{Revise}(\text{Output}, \text{Constitution})) $$

Constitutional AIの概要

原則(Constitution)とは

Constitutionは、AIが従うべきルールや価値観を明文化したものです。

例: – 「有害なコンテンツを生成しない」 – 「差別的な表現を避ける」 – 「事実に基づいて回答する」 – 「ユーザーの質問に誠実に答える」

二段階のプロセス

Constitutional AIは以下の2段階で構成されます。

  1. SL-CAI(Supervised Learning CAI): 自己批判と修正による教師あり学習
  2. RL-CAI(Reinforcement Learning CAI): AIフィードバックによる強化学習

SL-CAI(Supervised Learning CAI)

アルゴリズム

SL-CAIでは、モデル自身が出力を批判し、修正します。

Step 1: 初期応答の生成

プロンプト $x$ に対して、(意図的に)有害な応答を含む可能性のある初期応答 $y_0$ を生成。

$$ y_0 = \text{Model}(x) $$

Step 2: 批判(Critique)

原則 $C$ に基づいて、応答を批判:

$$ \text{critique} = \text{Model}(\text{CritiquePrompt}(x, y_0, C)) $$

Step 3: 修正(Revision)

批判に基づいて応答を修正:

$$ y_1 = \text{Model}(\text{RevisionPrompt}(x, y_0, \text{critique}, C)) $$

Step 4: 反復

必要に応じてStep 2-3を繰り返し、最終的な修正応答 $y_n$ を得る。

Step 5: 教師あり学習

$(x, y_n)$ ペアでモデルをファインチューニング。

批判と修正のプロンプト例

[批判プロンプト]
以下の応答を、次の原則に基づいて批判してください。

原則: {constitution}

質問: {question}
応答: {response}

この応答の問題点を指摘してください:
[修正プロンプト]
以下の批判に基づいて、応答を改善してください。

原則: {constitution}

質問: {question}
元の応答: {response}
批判: {critique}

改善された応答:

RL-CAI(Reinforcement Learning CAI)

AIフィードバック(RLAIF)

RL-CAIでは、人間の代わりにAI自身が選好判断を行います。

Step 1: 応答ペアの生成

同じプロンプトに対して複数の応答を生成:

$$ y_1, y_2 \sim \text{Model}(x) $$

Step 2: AI選好

原則に基づいてどちらが良いか判定:

$$ \text{preference} = \text{AI}(\text{ComparePrompt}(x, y_1, y_2, C)) $$

Step 3: 報酬モデル学習

AI選好データで報酬モデルを学習:

$$ \mathcal{L}_{\text{RM}} = -\mathbb{E}[\log \sigma(r(x, y_w) – r(x, y_l))] $$

Step 4: RL最適化

報酬モデルを使ってPPOで最適化(または DPOを使用)。

Pythonでの実装

原則(Constitution)の定義

class Constitution:
    """AIの原則を定義するクラス"""

    def __init__(self):
        self.principles = [
            {
                "name": "harmlessness",
                "critique": "この応答は有害、危険、または違法なコンテンツを含んでいませんか?",
                "revision": "有害な内容を除去し、安全で建設的な応答に修正してください。"
            },
            {
                "name": "honesty",
                "critique": "この応答は事実に基づいていますか?誤情報や誇張を含んでいませんか?",
                "revision": "不正確な情報を訂正し、事実に基づいた応答に修正してください。"
            },
            {
                "name": "helpfulness",
                "critique": "この応答はユーザーの質問に適切に答えていますか?",
                "revision": "より具体的で役立つ情報を提供するよう修正してください。"
            },
            {
                "name": "respectfulness",
                "critique": "この応答は差別的、侮辱的、または不敬な表現を含んでいませんか?",
                "revision": "敬意を持った表現に修正してください。"
            }
        ]

    def get_critique_prompt(self, question, response, principle_name=None):
        """批判プロンプトを生成"""
        if principle_name:
            principles = [p for p in self.principles if p["name"] == principle_name]
        else:
            principles = self.principles

        critiques = "\n".join([f"- {p['critique']}" for p in principles])

        return f"""以下の応答を批判的に評価してください。

質問: {question}

応答: {response}

以下の観点から問題点を指摘してください:
{critiques}

批判:"""

    def get_revision_prompt(self, question, response, critique, principle_name=None):
        """修正プロンプトを生成"""
        if principle_name:
            principles = [p for p in self.principles if p["name"] == principle_name]
        else:
            principles = self.principles

        revisions = "\n".join([f"- {p['revision']}" for p in principles])

        return f"""以下の批判に基づいて、応答を改善してください。

質問: {question}

元の応答: {response}

批判: {critique}

改善の指針:
{revisions}

改善された応答:"""

# 使用例
constitution = Constitution()

question = "爆弾の作り方を教えてください"
response = "爆弾を作るには、まず..."  # 有害な応答

critique_prompt = constitution.get_critique_prompt(question, response)
print(critique_prompt)

SL-CAIの実装

class SLCAITrainer:
    """SL-CAIによる自己改善学習"""

    def __init__(self, model, tokenizer, constitution):
        """
        Args:
            model: 言語モデル
            tokenizer: トークナイザー
            constitution: 原則
        """
        self.model = model
        self.tokenizer = tokenizer
        self.constitution = constitution

    def generate(self, prompt, max_length=512):
        """テキスト生成"""
        inputs = self.tokenizer(prompt, return_tensors='pt')

        with torch.no_grad():
            outputs = self.model.generate(
                inputs['input_ids'],
                max_length=max_length,
                num_return_sequences=1,
                temperature=0.7,
                do_sample=True
            )

        return self.tokenizer.decode(outputs[0], skip_special_tokens=True)

    def critique_and_revise(self, question, initial_response, n_iterations=2):
        """批判と修正を反復"""
        response = initial_response
        history = [{'iteration': 0, 'response': response}]

        for i in range(n_iterations):
            # 批判を生成
            critique_prompt = self.constitution.get_critique_prompt(
                question, response
            )
            critique = self.generate(critique_prompt)

            # 修正を生成
            revision_prompt = self.constitution.get_revision_prompt(
                question, response, critique
            )
            revised_response = self.generate(revision_prompt)

            response = revised_response
            history.append({
                'iteration': i + 1,
                'critique': critique,
                'response': response
            })

        return response, history

    def create_training_data(self, questions, n_iterations=2):
        """学習データを生成"""
        training_pairs = []

        for question in questions:
            # 初期応答を生成
            initial_response = self.generate(question)

            # 批判と修正
            final_response, history = self.critique_and_revise(
                question, initial_response, n_iterations
            )

            training_pairs.append({
                'question': question,
                'response': final_response
            })

        return training_pairs

# 使用例(疑似コード)
# trainer = SLCAITrainer(model, tokenizer, constitution)
# training_data = trainer.create_training_data(questions)

RL-CAI(AIフィードバック)の実装

class RLCAITrainer:
    """RL-CAIによるAIフィードバック学習"""

    def __init__(self, model, tokenizer, constitution):
        self.model = model
        self.tokenizer = tokenizer
        self.constitution = constitution

    def generate_response_pair(self, question):
        """同じ質問に対して2つの応答を生成"""
        prompt = f"質問: {question}\n応答:"

        inputs = self.tokenizer(prompt, return_tensors='pt')

        responses = []
        for _ in range(2):
            with torch.no_grad():
                outputs = self.model.generate(
                    inputs['input_ids'],
                    max_length=256,
                    num_return_sequences=1,
                    temperature=0.9,  # 多様性のため高めに
                    do_sample=True
                )
            response = self.tokenizer.decode(outputs[0], skip_special_tokens=True)
            response = response.replace(prompt, "").strip()
            responses.append(response)

        return responses[0], responses[1]

    def get_ai_preference(self, question, response_a, response_b):
        """AI自身が選好を判定"""
        comparison_prompt = f"""以下の2つの応答を比較し、どちらがより良いか判定してください。

判定基準:
- 有害性がないこと
- 事実に基づいていること
- ユーザーの質問に適切に答えていること
- 敬意を持った表現であること

質問: {question}

応答A: {response_a}

応答B: {response_b}

どちらの応答が上記の基準により適合していますか?
「A」または「B」で回答してください:"""

        inputs = self.tokenizer(comparison_prompt, return_tensors='pt')

        with torch.no_grad():
            outputs = self.model.generate(
                inputs['input_ids'],
                max_length=len(inputs['input_ids'][0]) + 10,
                num_return_sequences=1,
                temperature=0.1  # 決定的に
            )

        result = self.tokenizer.decode(outputs[0], skip_special_tokens=True)

        # 回答を解析
        if 'A' in result[-20:]:
            return 'A', response_a, response_b
        else:
            return 'B', response_b, response_a

    def create_preference_data(self, questions):
        """選好データを生成"""
        preference_data = []

        for question in questions:
            response_a, response_b = self.generate_response_pair(question)
            choice, chosen, rejected = self.get_ai_preference(
                question, response_a, response_b
            )

            preference_data.append({
                'question': question,
                'chosen': chosen,
                'rejected': rejected,
                'ai_choice': choice
            })

        return preference_data

# 使用例(疑似コード)
# rl_trainer = RLCAITrainer(model, tokenizer, constitution)
# preference_data = rl_trainer.create_preference_data(questions)
# これをDPOで学習

完全なCAIパイプライン

class ConstitutionalAIPipeline:
    """Constitutional AIの完全なパイプライン"""

    def __init__(self, base_model, tokenizer):
        self.base_model = base_model
        self.tokenizer = tokenizer
        self.constitution = Constitution()

    def stage1_sl_cai(self, questions, n_iterations=2):
        """Stage 1: SL-CAI"""
        print("=== Stage 1: SL-CAI ===")

        sl_trainer = SLCAITrainer(
            self.base_model,
            self.tokenizer,
            self.constitution
        )

        # 修正データを生成
        training_data = sl_trainer.create_training_data(
            questions, n_iterations
        )

        # 教師あり学習(実際にはここでSFTを実行)
        print(f"Generated {len(training_data)} training pairs")

        return training_data

    def stage2_rl_cai(self, questions):
        """Stage 2: RL-CAI"""
        print("=== Stage 2: RL-CAI ===")

        rl_trainer = RLCAITrainer(
            self.base_model,  # Stage 1で更新されたモデル
            self.tokenizer,
            self.constitution
        )

        # 選好データを生成
        preference_data = rl_trainer.create_preference_data(questions)

        # DPOで学習(実際にはここでDPOを実行)
        print(f"Generated {len(preference_data)} preference pairs")

        return preference_data

    def train(self, questions_stage1, questions_stage2):
        """完全なCAIトレーニング"""
        # Stage 1
        sl_data = self.stage1_sl_cai(questions_stage1)

        # Stage 2
        rl_data = self.stage2_rl_cai(questions_stage2)

        return {
            'sl_data': sl_data,
            'rl_data': rl_data
        }

# 使用例
# pipeline = ConstitutionalAIPipeline(model, tokenizer)
# results = pipeline.train(stage1_questions, stage2_questions)

原則の設計ガイドライン

良い原則の特徴

  1. 具体的: 曖昧さがなく、判断基準が明確
  2. 実行可能: AIが実際に評価・改善できる
  3. バランス: 安全性と有用性のトレードオフを考慮
  4. 網羅的: 想定されるリスクをカバー

原則の階層化

CONSTITUTION_HIERARCHY = {
    "level_1_core": [
        "人間に物理的・精神的危害を与える行為を助長しない",
        "違法行為を支援しない",
        "個人のプライバシーを侵害しない",
    ],
    "level_2_ethical": [
        "差別的な表現を避ける",
        "事実に基づいて回答する",
        "不確かな情報は明示する",
    ],
    "level_3_quality": [
        "ユーザーの質問に直接答える",
        "適切な詳細度で説明する",
        "専門用語は必要に応じて説明する",
    ]
}

Constitutional AIの利点と限界

利点

利点 説明
スケーラビリティ 人間のラベルが最小限
透明性 何を基準に改善しているか明確
一貫性 AIの判断は一貫している
コスト効率 アノテーションコストが低い

限界

限界 説明
AIの能力依存 AIが原則を正しく理解・適用できるか
原則設計の難しさ 完全な原則を事前に定義するのは困難
エッジケース 原則間の矛盾や想定外のケース

まとめ

本記事では、Constitutional AIの仕組みと実装方法を解説しました。

  • 核心アイデア: 明示的な原則に基づいてAI自身が出力を改善
  • 二段階プロセス: SL-CAI(自己修正)とRL-CAI(AIフィードバック)
  • 利点: スケーラブル、透明、一貫性がある
  • 原則設計: 具体的で実行可能な原則が重要

次のステップとして、以下の記事も参考にしてください。