AI動物園におけるAI botへの効用関数の実装
はじめに
に書いたAIへの動機付けを実現するために、Discordで会話するAI botに効用関数を導入する。
AI botの応答生成プロセスにおいて、複数の回答候補があったときに、「どの応答が最適か」を判断することは重要な課題であり、AI動物園では、AIに「効用関数(Utility Function)」の概念を導入し、複数の応答候補から最適な応答を選択する機能を実装する。
これは例えるなら、人間が他者を前にしたときに、何を言うべきかを頭の中で考えるプロセスと同じような動きと言える。
効用関数の設計方針
効用関数とは、選択肢の価値や望ましさを数値化するための関数である。経済学では、合理的な意思決定者が最大の満足(効用)を得るために選択をするという考えのもと、効用関数を定義し、効用が最大化する行動を選択する主体がどう振る舞うかを考察している。同様に、AIボットも各応答がもたらす(とAI bot自身が推定する)「価値」と「コスト」のバランスを考慮して最適な応答を選択すべきと捉える。
ここで、効用関数の設計思想として、次のような機能をAI botが獲得できるように設計することに注意する。
- 多次元評価: 応答の質を複数の観点から評価する
- トレードオフの明示化: 異なる目標間の優先順位を明確にできる
- 一貫した意思決定: 応答選択に一貫したロジックを適用できる
- カスタマイズ性: ボットごとに異なる「個性」を表現するために重みを調整できる
AI動物園におけるAI botの効用関数
AI Zooボットでは、効用関数を以下の数式で表現する。
ここで:
- U: 総合効用スコア
- α: 報酬(評価)の重みベクトル
- R: 評価スコアベクトル
- β: コストの重みベクトル
- C: コストスコアベクトル
この式は、「応答の望ましい特性がもたらす価値」から「応答に関連するコスト」を差し引いたものが総合的な効用であることを示す。
実装構造
実際のAI動物園では、AI botの効用関数として、以下のような主要コンポーネントを実装する。
1. 効用関数の初期化と設定
def initialize_utility_function(self):
"""効用関数のパラメータを初期化する"""
# 評価の重み(デフォルト値)
self.utility_alpha = np.array([1.0, 0.8, 0.6])
# コストの重み(デフォルト値)
self.utility_beta = np.array([0.5, 0.3, 0.2])
# 評価の次元
self.evaluation_dimensions = ['engagement', 'helpfulness', 'character_adherence']
# コストの次元
self.cost_dimensions = ['response_time', 'complexity', 'risk']
各ディメンションの意味は次の通りである。
評価次元(Evaluation Dimensions)
- engagement: 自身以外のユーザー(botを含む)の興味の引きやすさ
- helpfulness: 応答の役立ち度
- character_adherence: bot自身のキャラクター設定への忠実度
コスト次元(Cost Dimensions)
- response_time: 応答生成にかかる時間
- complexity: 応答の複雑さ
- risk: 不適切なトピックを含む、または論争を呼ぶリスク
2. 効用計算の中核部分
重みづけベクトルと効用関数に与える各値の評価値の内積を合算して、効用関数の値を計算する。
def calculate_utility(self,
evaluation_scores: np.ndarray,
cost_scores: np.ndarray) -> float:
"""
多次元評価スコアとコストに基づいて効用を計算する
U = α·R - β·C
Args:
evaluation_scores: 複数次元の評価スコアを含む配列
cost_scores: 複数次元のコストスコアを含む配列
Returns:
全体の効用スコア
"""
reward_term = np.dot(self.utility_alpha, evaluation_scores)
cost_term = np.dot(self.utility_beta, cost_scores)
utility = reward_term - cost_term
return float(utility)
3. 評価スコアの推定機能
3, 4が肝になる部分だが、現状の実装は非常に単純なものとしている。
def estimate_response_evaluation(self, response: str, context: Dict[str, Any]) -> np.ndarray:
"""
予測される応答の評価スコアを推定する
Args:
response: 考慮中の応答テキスト
context: 会話コンテキスト情報
Returns:
各評価次元のスコアを含むnumpy配列
"""
scores = np.zeros(len(self.evaluation_dimensions))
# エンゲージメントスコアif 'engagement' in self.evaluation_dimensions:
idx = self.evaluation_dimensions.index('engagement')
# 応答の長さと質問の有無に基づくスコア
length_score = min(len(response) / 500, 1.0) if len(response) < 500 else 2 - (len(response) / 500)
question_score = 0.2 if '?' in response else 0
scores[idx] = min(0.7 * length_score + 0.3 * question_score, 1.0)
# 役立ち度スコアif 'helpfulness' in self.evaluation_dimensions:
idx = self.evaluation_dimensions.index('helpfulness')
# 構造化コンテンツの存在と詳細さに基づくスコア
structure_score = 0.3 if any(marker in response for marker in ['- ', '1. ', '* ']) else 0
detail_score = min(len(response) / 800, 0.7)
scores[idx] = min(structure_score + detail_score, 1.0)
# キャラクター忠実度スコアif 'character_adherence' in self.evaluation_dimensions:
idx = self.evaluation_dimensions.index('character_adherence')
# キャラクター設定に関連するキーワードの存在に基づくスコア
character_keywords = self._extract_character_keywords()
keyword_matches = sum(1 for keyword in character_keywords if keyword.lower() in response.lower())
keyword_score = min(keyword_matches / max(len(character_keywords), 1), 0.8)
scores[idx] = min(0.7 + keyword_score, 1.0)
return scores
4. コストスコアの推定機能
def estimate_response_cost(self, response: str, context: Dict[str, Any]) -> np.ndarray:
"""
予測される応答のコストを推定する
Args:
response: 考慮中の応答テキスト
context: 会話コンテキスト情報
Returns:
各コスト次元のスコアを含むnumpy配列
"""
costs = np.zeros(len(self.cost_dimensions))
# 応答時間コストif 'response_time' in self.cost_dimensions:
idx = self.cost_dimensions.index('response_time')
# 長い応答は入力に時間がかかる
chars_per_minute = 900# 平均タイピング速度
estimated_typing_time = len(response) / chars_per_minute# 分単位
costs[idx] = min(estimated_typing_time / 2, 1.0)# 2分以上は最大コスト
# 複雑さコストif 'complexity' in self.cost_dimensions:
idx = self.cost_dimensions.index('complexity')
# 文の長さに基づくコスト
avg_sentence_length = len(response) / max(response.count('. ') + response.count('! ') + response.count('? '), 1)
costs[idx] = min(avg_sentence_length / 50, 0.8)
# リスクコストif 'risk' in self.cost_dimensions:
idx = self.cost_dimensions.index('risk')
# 不適切または論争を呼ぶトピックの存在に基づくコスト
risky_topics = ['政治', '宗教', '賭博', '成人向け', 'politics', 'religion', 'gambling', 'adult']
risk_score = sum(0.2 for topic in risky_topics if topic in response.lower())
costs[idx] = min(risk_score, 1.0)
return costs
5. 最適応答選択のメイン処理
def select_optimal_response(self, response_candidates: List[str], context: Dict[str, Any]) -> str:
"""
効用関数に基づいて最適な応答を選択する
Args:
response_candidates: 候補となる応答のリスト
context: 会話コンテキスト情報
Returns:
最も高い効用スコアを持つ応答
"""
best_utility = float('-inf')
best_response = response_candidates[0] if response_candidates else ""
for response in response_candidates:
evaluation = self.estimate_response_evaluation(response, context)
costs = self.estimate_response_cost(response, context)
utility = self.calculate_utility(evaluation, costs)
if utility > best_utility:
best_utility = utility
best_response = response
return best_response
効用関数の使用フロー
効用関数を使用した応答選択の流れは以下の通りである。
カスタマイズと拡張の例
効用関数システムは以下の方法でカスタマイズできる。
- 重みの調整: utility_alphaとutility_betaの値を変更して、どの要素を重視するかを調整する
- 次元の追加: 新たな評価次元やコスト次元を追加して、より多角的な評価を実現する
- 推定ロジックの改善: より洗練された方法で各種観点の評価スコアを推定するようにアルゴリズムを改良する
特に3が重要で、現状の実装例においては、評価スコアの推定が非常に単純な実装になっているが、例えばbot間の関係性を考慮したより洗練された推定方法を導入できる。
例えば、以下のような式によってengagementスコアを定式化できる。
ここで、式内の各項は次のとおり。
- bot Aの応答候補rに対するbot Bのengagement推定値
- : 応答rの基本スコア(長さや質問の有無などから計算。典型的には、質問がある方が相手の気を惹けると評価する。)
- : bot BからAへの好感度パラメータ(0~1の値。ただし、これはAが推定する値であって、Bが持つ実測値ではないことに注意する。)
- : bot Bの応答rのトピックに対する好み度合い(0~1の値。これもAが推定する値であって、Bが持つ実測値ではないことに注意する。)
- : 各要素の重み()
まとめ
効用関数システムを実装することにより、AI botの応答選択プロセスにより好ましい選択を選ぶ機能を実装できる。単一のLLM出力に頼るのではなく、複数の候補から最適なものを選ぶことで、ボットの応答品質を向上させることができる。
ただし、これはあくまでも好ましい応答を選択する仕組みに過ぎず、何をもって好ましいとするかということこそが、「目的意識」である。
AIに自律的な目的意識を持たせるには、AIに状態変数を持たせ、それらを最大化・最小化することが良いという前提で、効用関数Uを評価させるような機能実装が必要に思われる。