*********
この辺りの内容は、これから生成AIを学びたい学生さんや若手のAI初心者さんに捧げます。
これからのAI時代を作っていくのはあなた方です。
プログラミングのPythonを習得して、興味が出てきたらAI関連の本や記事を読み進めてください。
いつでも好奇心をもって、コードを実装して試してください。
さすればきっと、AIがあなたの力になる日が来るでしょう。
ASI (Artificial Super Intelligence、超知能) が人類の課題を解決してくれる日を信じて。
*********
AIが自ら進化する時代へ
今、AI業界では「AIが自分自身を進化させる」というテーマが急速に注目を集めています。
従来は、人間が手作業でAIのアルゴリズムや設定を調整していました。しかし最近では、AIが自分で「もっと良い方法はないか」と考え、試行錯誤しながら成長していく時代が現実になろうとしています。
この流れの最先端を走るのが、日本発のSakana AIによる「ダーウィン・ゲーデルマシン(DGM)」と、アメリカ・MITが開発した「SEAL(Self-Evolving Agent with LoRA)」という二つの自己進化AI技術です。
今回、この二つを組み合わせ、「ハイブリッド自己進化型AI」を実験しました。
その過程でわかった“AIが進化するための条件”について、お伝えします。
DGMとSEAL、2つの自己進化AI技術の概要
まずDGMとSEALの仕組みについて簡単に説明します。
■ DGM(ダーウィン・ゲーデルマシン)
DGMの最大の特徴は「AIが自分自身のプログラムを書き換えて改善できる」点です。
まるで生物が進化するように、AI自身が新しいアルゴリズムを考え、実際にプログラムを修正して性能を向上させます。
■ SEAL(Self-Evolving Agent with LoRA)
SEALは、「新しいやり方を学習して試し、成果を評価して、さらに進化する」というサイクルを自律的に繰り返すAIです。
MITが開発したこの仕組みは、AIが新たなスキルや知識を次々と身につけていく“自己進化”を実現しています。
実験の流れ DGM × SEALのハイブリッド構成
この2つの仕組みの“良いとこ取り”をして、DGMが「より良い方法(ワークフロー)」をAIに提案し、SEALがそれを学習・評価して自己進化するという流れを目指しました。
役割分担
*DGM役(改善案の提案)
今回はSakana AIのDGMの役割を、GPT-3.5というAIチャットボットに任せました。
「このタスク、どうすれば効率的にできる?」とGPTに問いかけ、Pythonのコード(ワークフロー)を生成してもらいます。
* SEAL役(学習&評価)
SEALの役割は、日本語が得意な軽量AI「TinySwallow-1.5B-Instruct」に担当してもらいます。
GPTが考えた新しいワークフローをTinySwallowが学習し、その性能を評価する、という構成です。
実験テーマ「足し算」を言語AIにやらせてみる
どんなタスクでハイブリッド自己進化AIを試すかですが、今回はあえて「足し算」という言語AIにとって専門外のテーマを選びました。
もともとTinySwallow-1.5B-Instructは数学も結構学習しているみたいで、簡単な計算問題は解けるモデルですが、さて、どうなるかです。
タスク例
入力「5 + 8」
正解「13」
本来、日本語テキストを理解することが得意なTinySwallowに、「DGM(GPT-3.5)」が出したプログラムを使って計算タスクを学ばせ、その効果を確かめます。
実験結果 DGMの提案は完璧、でもSEALは学習できず
最初にDGM役のGPT-3.5が出したPythonコードは、それなりなものでした。
python
lambda text: str(sum(map(int, text.split(‘+’))))
足し算のコードですね。
このコードがあれば、「5 + 8」などの計算を自動で処理できます。
そこで、このワークフローをTinySwallowに学習させてみました。
「入力が“13”なら、そのまま“13”と返すだけ」という極めて単純な学習タスクのはずでした。
しかし、実際にTinySwallowに学習させてテストしたところ「13」に対して「27」など、全く違う数字を返しました。
なぜ失敗したのか? ファインチューニングとデータ量の本質
この原因を理解するには、「AIの学習」を人間の勉強に例えるのが分かりやすいです。
事前学習(巨大な図書館を読破)
TinySwallowは、事前学習の段階でインターネット上の膨大な日本語テキストを読んでいます。
まるで巨大な図書館の本を全て読んだかのように、知識は豊富です。
ただし、その知識の中に計算ルールが入っていたのかは定かではありません。
ファインチューニング(4枚の単語カード)
今回の実験で用意した足し算の学習データは、たった「4行」だけ。
これは、いくら頭の良い学生でも試験前日に4枚の単語カードを渡され、「これで計算をマスターしろ」と言われるようなものです。
(今回は流れを知るのが目的なので、実際は数千から万単位のデータを揃えたいところです)
推論(試験本番)
もし本番で「10 + 10」という新しい問題が出たらどうなるでしょうか?
足し算のルールを身につけていないと仮定すると、TinySwallowは自分の得意なことの「自然な日本語の文章を生成する」ことに頼ってしまいます。
これが、TinySwallowが「計算結果」ではなく「Here’s the calculation:」や「## ステップ」などの文章を返した理由だと思われます。
どれだけのデータが必要なのか?
AIに新しいスキルをしっかり身につけさせるには、通常「数百〜数千、場合によっては数万件」の多様なデータが必要です。
たとえば「足し算」なら、下記のような様々な組み合わせの例を数多く見せる必要があります。
* 1 + 1 = 2
* 10 + 25 = 35
* 123 + 456 = 579
* 99 + 0 = 99
* など、桁数や数字のパターンが異なる大量の例
こうして初めて、AIは「+記号は両側の数値を足す」というルールを抽象的に学び始めます。
実験でわかった“条件”とは
今回の実験で得た最大の教訓は、「AIの自己進化」には3つの要素(今回の場合)がすべて揃う必要があるということです。
1. DGM(提案AI)の能力
いくらDGM(コーチ)が完璧な作戦を立てても、
2. SEAL(学習AI)の専門性や適性
その作戦を実行するSEAL(選手)がタスクに根本的に向いていなければ、
3. 十分な学習データの質と量
さらに練習量(ファインチューニングデータ)が圧倒的に不足していれば、
自己進化のサイクルは機能しません。
まとめ AI自己進化に必要なもの
今回の実験は、「AIがAIを進化させる」という夢の実現に向けて、まだ課題があることを教えてくれました。
今回のハイブリッド自己進化システムは、「DGM(戦略担当)」・「SEAL(現場担当)」・「十分なデータ(練習量)」の三位一体で成り立ちます。
どれか1つでも欠けてしまうと、進化のサイクルは止まってしまいます。
技術やアイデアだけではなく、地道なデータ作りやモデルごとの専門性も不可欠です。
「AIがAIを進化させる世界」は確実に近づいていますが、その実現には“データの力”と“モデル同士の相性”も考慮する必要があるかと。
今回のDGMとSEALを使ったハイブリッド自己進化型の実験からは、「提案力・学習能力・十分なデータ」この3つが揃って初めて、進化させられることを教えてくれました。
それ以外にも今後の実験と通して、第4、第5のルールが出てくるかも知れません。
そんな発見もまた楽しいものです。
⚫︎実験につかった pythonコード
最後にコードを貼っておきます。ご自由にお使いください。
わかりやすいようにコメントをこまめに書いてあります.
なお、GPTのAPI料金がかかります。
そこのコードを改変すれば無料でできます。
(コードの使用は自己責任でお願いします)
# ==============================================================================
# 1. 必要ライブラリのインストール
# ==============================================================================
!pip install -q transformers peft bitsandbytes openai accelerate huggingface_hub
print("✅ ライブラリのインストールが完了しました。")
# ==============================================================================
# 2. 初期設定(Driveマウント、APIキー入力)
# ==============================================================================
import os
import torch
from huggingface_hub import login, snapshot_download
# --- Google Driveのマウント ---
from google.colab import drive
print("🔐 Google Driveをマウントします。認証を行ってください...")
drive.mount('/content/drive', force_remount=True)
print("✅ Google Driveのマウントが完了しました!")
print("\n--- APIキーとトークンの入力 ---")
# --- Hugging Face トークンの入力 ---
hf_token = input("\n>>> Hugging Faceのアクセストークンを貼り付けてEnterキーを押してください: ").strip()
if not hf_token: raise ValueError("❌ Hugging Faceのアクセストークンが入力されませんでした。")
# --- OpenAI APIキーの入力 ---
openai_api_key = input("\n>>> OpenAIのAPIキーを貼り付けてください: ").strip()
if not openai_api_key: raise ValueError("❌ OpenAIのAPIキーが入力されませんでした。")
os.environ["OPENAI_API_KEY"] = openai_api_key
print("✅ OpenAI APIキーを環境変数に設定しました。")
# --- GPUの利用可能性を確認 ---
if not torch.cuda.is_available(): print("⚠️ 警告: GPUが利用できません。")
else: print(f"✅ GPUが利用可能です。デバイス: {torch.cuda.get_device_name(0)}")
# ==============================================================================
# 3. モデルの準備(初回のみダウンロード、2回目以降はスキップ)
# ==============================================================================
print("\n" + "="*60)
print("=== モデルの準備を開始します ===")
print("="*60)
model_name_on_hf = "SakanaAI/TinySwallow-1.5B-Instruct"
local_model_path = "/content/drive/MyDrive/ColabModels/tiny-swallow-v3"
print(f"モデルの保存/読込パス: {local_model_path}")
config_path = os.path.join(local_model_path, "config.json")
if os.path.exists(config_path):
print(f"✅ モデルは既に存在します。ダウンロードをスキップします。")
else:
print(f"⚠️ モデルが見つかりません。ダウンロードを開始します...")
login(token=hf_token)
snapshot_download(repo_id=model_name_on_hf, local_dir=local_model_path, token=hf_token)
print(f"✅ モデルのダウンロードが完了しました! → '{local_model_path}'")
# ==============================================================================
# 4. SEAL: モデルをローカルからロードして自己学習するクラス (公式作法対応)
# ==============================================================================
from transformers import AutoModelForCausalLM, AutoTokenizer, BitsAndBytesConfig, TrainingArguments, Trainer
from peft import LoraConfig, get_peft_model, prepare_model_for_kbit_training
class SEAL_TinySwallow:
def __init__(self, model_path: str):
print(f"\n[SEAL] ローカルパス '{model_path}' からモデルをロードします...")
bnb_config = BitsAndBytesConfig(load_in_4bit=True, bnb_4bit_quant_type='nf4', bnb_4bit_compute_dtype=torch.bfloat16)
self.tokenizer = AutoTokenizer.from_pretrained(model_path)
base_model = AutoModelForCausalLM.from_pretrained(model_path, quantization_config=bnb_config, device_map="auto")
base_model = prepare_model_for_kbit_training(base_model)
lora_config = LoraConfig(r=16, lora_alpha=32, target_modules=["q_proj", "v_proj"], lora_dropout=0.05, bias="none", task_type="CAUSAL_LM")
self.model = get_peft_model(base_model, lora_config)
self.model.eval()
print(f"✅ [SEAL] ローカルモデル ({model_path}) の準備が完了しました!")
def finetune_on_feedback(self, prompts: list, targets: list):
print("\n[SEAL] フィードバックに基づくファインチューニングを開始します...")
# ★★★【最重要改善点】学習データを公式チャット形式に変換 ★★★
messages_list = []
for p, t in zip(prompts, targets):
messages = [
{"role": "system", "content": "あなたは与えられた数式を計算するアシスタントです。"},
{"role": "user", "content": p},
{"role": "assistant", "content": t}
]
messages_list.append(messages)
full_prompts = [self.tokenizer.apply_chat_template(msg, tokenize=False, add_generation_prompt=False) for msg in messages_list]
class SimpleDataset(torch.utils.data.Dataset):
def __init__(self, full_prompts, tokenizer):
self.encodings = tokenizer(full_prompts, truncation=True, padding=True, max_length=128, return_tensors="pt")
def __len__(self): return len(self.encodings["input_ids"])
def __getitem__(self, idx):
item = {key: val[idx] for key, val in self.encodings.items()}
item['labels'] = item['input_ids'].clone()
return item
dataset = SimpleDataset(full_prompts, self.tokenizer)
args = TrainingArguments(
output_dir='./results', num_train_epochs=1, per_device_train_batch_size=1,
logging_steps=10, save_steps=20, learning_rate=1e-4, disable_tqdm=False,
fp16=torch.cuda.is_available(), report_to="none",
)
self.model.train()
trainer = Trainer(model=self.model, args=args, train_dataset=dataset)
trainer.train()
self.model.eval()
print("✅ [SEAL] ファインチューニングが完了しました。")
def generate(self, prompt: str, max_new_tokens: int = 5) -> str:
# ★★★【最重要改善点】推論時も公式チャット形式を使用 ★★★
messages = [
{"role": "system", "content": "あなたは与えられた数式を計算するアシスタントです。"},
{"role": "user", "content": prompt},
]
input_ids = self.tokenizer.apply_chat_template(
messages, add_generation_prompt=True, return_tensors="pt"
).to(self.model.device)
terminators = [self.tokenizer.eos_token_id] + [
self.tokenizer.convert_tokens_to_ids(token) for token in ["<|eot_id|>", "<|im_end|>"]
]
valid_terminators = [term_id for term_id in terminators if term_id is not None]
with torch.no_grad():
output_ids = self.model.generate(
input_ids, max_new_tokens=max_new_tokens, do_sample=False,
eos_token_id=valid_terminators,
)
response_ids = output_ids[0][input_ids.shape[-1]:]
return self.tokenizer.decode(response_ids, skip_special_tokens=True).strip()
# ==============================================================================
# 5. DGM: GPT-3.5-turboでワークフローを自動生成するクラス (変更なし)
# ==============================================================================
import openai
import re
class DGM_GPT:
def __init__(self):
self.client = openai.OpenAI(api_key=os.environ["OPENAI_API_KEY"])
self.archive = []
def propose_new_workflow(self, task_description: str, example_input: str):
print("\n[DGM] gpt-3.5-turboに新しいワークフローの提案を依頼します...")
prompt = ("あなたはPythonコード生成の専門家です。下記のタスクを解決するPythonの<code>lambda</code>式を1つだけ生成してください。\n"
"【タスク説明】\n" + task_description + "\n【入力例】\n" + example_input +
"\n【厳格な出力ルール】\n1. 出力は<code>lambda</code>で始まるコードのみとする。\n2. 説明、コメント、<code></code><code>python ... </code><code></code>のようなマークダウンは絶対に含めない。\n3. 1行で完結させること。\n"
"【出力例】\nlambda text: str(sum(map(int, text.split('+'))))")
try:
response = self.client.chat.completions.create(model="gpt-3.5-turbo", messages=[{"role": "user", "content": prompt}], temperature=0.7)
content = response.choices[0].message.content.strip()
match = re.search(r"lambda.*", content)
if not match: raise ValueError("応答からlambda式が見つかりませんでした。")
code_line = match.group(0)
workflow_func = eval(code_line)
print(f"✅ [DGM] ワークフロー提案を受信: {code_line}")
return workflow_func, code_line
except Exception as e:
print(f"❌ [DGM] ワークフロー生成中にエラーが発生しました: {e}")
print(" デフォルトのワークフローを使用します。")
return eval("lambda text: text"), "lambda text: text"
def archive_workflow(self, code_line: str, score: float):
print(f"[DGM] ワークフロー '{code_line}' のスコア {score:.2f} を記録しました。")
self.archive.append((code_line, score))
# ==============================================================================
# 6. ハイブリッド自己進化デモの実行 (変更なし)
# ==============================================================================
print("\n" + "="*60)
print("=== ハイブリッド自己進化デモ(足し算タスク)を開始します ===")
print("="*60)
seal = SEAL_TinySwallow(model_path=local_model_path)
dgm = DGM_GPT()
texts = ["5 + 8", "12 + 7", "3 + 15", "9 + 9"]
targets = ["13", "19", "18", "18"]
task_desc = "与えられた 'a + b' 形式の文字列を計算し、結果を文字列として返すタスク。"
num_generations = 2
for i in range(num_generations):
print(f"\n\n--- GENERATION {i+1}/{num_generations} ---")
workflow_func, code_line = dgm.propose_new_workflow(task_desc, texts[0])
workflowed_texts = [workflow_func(t) for t in texts]
seal.finetune_on_feedback(workflowed_texts, targets)
print("\n[EVAL] 学習後のモデル性能を評価します...")
correct_count = 0
for inp_text, target_text in zip(workflowed_texts, targets):
generated_text = seal.generate(inp_text)
print(f" - Input: '{inp_text}'")
print(f" - Generated: '{generated_text}'")
print(f" - Target: '{target_text}'")
if generated_text.strip() == target_text.strip():
correct_count += 1
print(" -> ✅ Correct")
else:
print(" -> ❌ Incorrect")
accuracy = correct_count / len(texts)
print(f"✅ [EVAL] 評価完了。Accuracy after SEAL adaptation: {accuracy:.2f}")
dgm.archive_workflow(code_line, accuracy)
print("\n\n" + "="*60)
print("=== 自己進化ループ完了:最終結果 ===")
print("="*60)
if dgm.archive:
best_code_line, best_acc = max(dgm.archive, key=lambda item: item[1])
print(f"🏆 最も性能の良かったワークフロー (gpt-3.5-turbo提案):")
print(f" コード: {best_code_line}")
print(f" 精度: {best_acc:.2f}")
else:
print("ワークフローが一つも生成されませんでした。")
****************
最近のデジタルアート作品を掲載!
X 旧ツイッターもやってます。
https://x.com/ison1232
インスタグラムはこちら
https://www.instagram.com/nanahati555/
****************