LLMをモデルマージと進化アルゴリズムのハイブリッドで実験してみた話

 

********

※LINE対応チャットボット版の
「LINEチャットボット屋」
いろんなチャットボットがあります。
ぜひ、ご覧ください!

***************

***************

モデルマージで2つのAIをひとつに

LLMを使った実験を続けていると、「異なるモデルを混ぜたらどうなるのか?」という素朴な疑問が浮かびます。
今回の実験はまさにその疑問から始まりました。

使用したのは、日本語系の小型LLMである
rinna/japanese-gpt2-smallと
cyberagent/open-calm-smallです。

古いモデルですが、Googleコラボで2つのモデルを実装するので、メモリの関係上仕方ないですね。

どちらも自然な日本語生成に定評のあるモデルで、今回はこれを“モデルマージ”によって融合してみました。

モデルマージとは、簡単に言えば2つのモデルの重み(パラメータ)を混ぜ合わせる技術です。

ちょうど遺伝子を掛け合わせるようなもので、「Aモデルの文体」と「Bモデルの知識」をほどよくブレンドできる可能性があります。

ただし、このマージは「掛け合わせて終わり」ではありません。
組み合わせ方によっては性能が落ちることもあり、偶然うまく混ざるケースもあります。

そんな“相性実験”を経て、1体の融合モデルが誕生しました。

通常はここでハイおしまいなのですが、これだけではつまらないなと。

じゃあ、このモデルをさらに進化アルゴリズムで進化させてみようと単純に思いました。
まさに知的好奇心です。

どうなるのか、知りたいですよね。

えっ、興味ない。
そうですか。私的にはめっちゃ面白い題材なんですが。

そんなこんなで、やってみますかと。

進化アルゴリズムでLoRAを育てる

続けて実験したのが、進化アルゴリズムを使ったLoRA(Low-Rank Adapter)層の進化実験です。

LoRAは、モデルの一部だけを学習させる軽量チューニング手法です。
このLoRAの重みを「遺伝子」とみなし、進化アルゴリズム(GA)で世代交代を繰り返すという計画を立てました。

初期状態では10個のLoRA個体(=微妙に異なる重みセット)を作成しました。

それぞれに同じプロンプトを与えて生成させ、「文の自然さ」「正しい表記」「冗長さの少なさ」をスコア化しました。

スコアの高い個体ほど“適応度”が高いと判断し、上位個体を親として交叉・突然変異を行い、次世代を生み出す。
これを20世代繰り返しました。

設定は以下の通りです。

個体数:10
世代数:20
交叉率:0.5
突然変異率:0.3

AIがAIを選び、より自然に進化していく。
このプロセスをColab上でリアルタイムに見られるのは、なかなか面白かったです。

余談 ドラマのサラブレッドに思うこと

話は変わりますが、先日日曜日の午後9時からのドラマでロイヤルファミリーと言う競馬を題材にしたドラマが始まりました。

非常に面白く、普段はドラマを見ないのですが、最後まで見入ってしまいました。

ドラマの中で出たのは、サラブレッドと言うのは遺伝子をたどっていくと、最終的にたったの3頭に集約されるそうです。

当然ながら、いろんなサラブレッドが交配を重ねてきたでしょうけど、結果として競走馬としての実力を兼ね備えた遺伝子をたどると、その上位3頭になったのでしょう。

つまり、他の馬たちは競走馬としては淘汰されてしまったと言う話ですね。

AIの世界でも、当然この進化アルゴリズムは通用するわけで、サラブレッドの交配は大変ですが、AIの進化はパソコンの中で作業としては完結できる。しかも自動化です。

これはやってみるに値する作業でしょう。

20世代の進化ログを眺めてみる

進化の過程は、ログとして次のように出力されました。

Gen 00 | best=1.600 avg=1.187
Gen 13 | best=2.000 avg=1.273
Gen 19 | best=1.600 avg=1.233

Evolution done. Elapsed ~18.5 min, best=2.000

bestはその世代で最も良かった個体のスコア、
avgは個体群の平均スコアを意味します。

この数字が1.0〜2.0の間で推移し、13世代目で最高値の「2.000」を記録しました。

進化アルゴリズムらしく、初期はバラバラな出力が多かったのですが、世代を重ねるにつれて平均スコアが安定してきました。

言い回しの崩れが減り、より自然な文体に近づいていくのがログからも見て取れました。

これまた、なかなか面白い様ですね。

実際の出力を見てみよう

最終的に得られた進化済みLoRAを使って生成した出力がこちらです。

紅茶の例

> 生徒から「何でもやってみたけど上手くいかなかった」という声をよく聞きます。では、どうして上手くいかなくなったのか?実は、多くの人がやっていることが間違っているのです。私はこの3つに気をつけています。まずは、茶葉を煎りたてのものにすることです。

コーヒーの例

> 豆に水を加えることで、豆の風味を引き立てることが出来ます。また、お湯を入れることで抽出したコーヒーに含まれる糖分も引き出してくれます。ミルクを加えると味がまろやかになり、栄養価もアップします。

どちらも文法の崩れが少なく、自然な語彙選択ができています。

ただし、「箇条書き」や「番号付き3行」といった構造的指示はまだ弱く、流暢さ優先の傾向が残っています。
とりあえず、この段階でここまで整った出力はよしとしましょう。

次の一歩は構造理解を学ぶ進化へ

今回の進化では「自然さ」と「表記の正確さ」を評価軸にしましたが、次はさらに一歩進んで構造理解を進化に取り込む予定です。

具体的には、

箇条書きを使えたら+0.8点
指定行数を守れたら+0.5点
といった構造スコアを新たに追加し、AIが「文章構造を守る」方向に進化するよう誘導していきます。

モデルマージで生まれた“融合型モデル”に、進化アルゴリズムで磨きをかける。

このハイブリッド実験は、単なる遊びではなく、人間がチューニングしなくても自己最適化していくAIの原型になるのかも知れません。

LLMをモデルマージと進化アルゴリズムのハイブリッドで実験してみた話まとめ

モデルマージで性格の違うAIを融合
進化アルゴリズムでLoRAを自動進化
20世代で自然な文体を獲得
次は「構造を理解するAI」へと進化させる(予定?)

AIが進化する瞬間を目の前で観察できるのは、なかなか面白い実験でした。
進化アルゴリズムはさらなる進歩を遂げていくでしょう。

使用したPythonコードです

以下に使ったPythonコードを掲載します。
結構エラーが出たので修正してます。

コード内のコメントもいろいろあって、まあ見逃してください。

そのままコラボにペーストすれば動きます。
APIは使っていません。

hugging faceのアクセストークンでしたか。
それは必要です。

エラー出たら、GTP5先生とかに聞いてください。


!pip -q install transformers==4.44.2 peft==0.12.0 accelerate==0.34.2 bitsandbytes==0.43.3 \
huggingface_hub==0.25.2 sentencepiece ipywidgets

import os, json, torch, textwrap, shutil
from huggingface_hub import login, HfApi, snapshot_download
from IPython.display import display
import ipywidgets as widgets

# UI
token_box = widgets.Password(
description='HF Token:',
placeholder='hf_xxxxx...(読み取り権限でOK)',
layout=widgets.Layout(width='60%')
)
btn = widgets.Button(description='Login to HF', button_style='primary')
out = widgets.Output()

def do_login(_):
with out:
out.clear_output()
token = token_box.value.strip()
if not token.startswith("hf_"):
print("❌ トークン形式が違うっぽい(hf_で始まるか確認してね)")
return
try:
who = HfApi().whoami(token=token)
os.environ["HUGGINGFACE_HUB_TOKEN"] = token # transformers>=4.38 は token=... で渡すのが推奨
login(token=token, add_to_git_credential=False)
print(f"✅ ログインOK:{who.get('name') or who.get('email')}")
except Exception as e:
print("❌ ログイン失敗:", e)

btn.on_click(do_login)
display(token_box, btn, out)

# =========================================================
# ✅ セル3:モデルを“ダウンロードしてから”読み込み(修正版)
# =========================================================
from huggingface_hub import snapshot_download
import os

# モデル指定
model_a_repo = "rinna/japanese-gpt2-small"
model_b_repo = "cyberagent/open-calm-small"

cache_root = "/content/hf_models"
os.makedirs(cache_root, exist_ok=True)

token = os.environ.get("HUGGINGFACE_HUB_TOKEN", None)
assert token, "HFトークンが未設定です(上のセルでログインしてから実行してね)"

def dl(repo_id):
local_dir = os.path.join(cache_root, repo_id.replace("/", "__"))
if not os.path.exists(local_dir):
print(f"⬇️ ダウンロード: {repo_id}")
snapshot_download(
repo_id,
local_dir=local_dir,
token=token,
resume_download=True, # 中断復帰OK
repo_type="model" # モデル用
)
else:
print(f"✅ 既存ディレクトリを利用: {local_dir}")
return local_dir

local_a = dl(model_a_repo)
local_b = dl(model_b_repo)

from transformers import AutoModelForCausalLM, AutoTokenizer

alpha = 0.5
save_dir = "./merged_model"
os.makedirs(save_dir, exist_ok=True)

# トークンを明示的に渡す
model_a = AutoModelForCausalLM.from_pretrained(local_a, torch_dtype=torch.float16, token=token)
model_b = AutoModelForCausalLM.from_pretrained(local_b, torch_dtype=torch.float16, token=token)

# tokenizerはA側を採用(必要なら後でB側と比較)
#tok_a = AutoTokenizer.from_pretrained(local_a, token=token)
#if tok_a.pad_token is None:
#tok_a.pad_token = tok_a.eos_token

# 変更前:
# tok_a = AutoTokenizer.from_pretrained(local_a, token=token)

# 変更後:
tok_a = AutoTokenizer.from_pretrained(local_a, token=token, use_fast=False)
if tok_a.pad_token is None:
tok_a.pad_token = tok_a.eos_token

sd_a = model_a.state_dict()
sd_b = model_b.state_dict()

merged = {}
skipped = []
for k, va in sd_a.items():
vb = sd_b.get(k, None)
if vb is not None and va.shape == vb.shape and va.dtype == vb.dtype:
merged[k] = (1 - alpha) * va + alpha * vb
else:
merged[k] = va
skipped.append(k)

print(f"ℹ️ 形状不一致などでAを採用したパラメータ数: {len(skipped)} / {len(sd_a)}")
if skipped:
print("例:", skipped[:10])

model_a.load_state_dict(merged, strict=False)
model_a.save_pretrained(save_dir)
tok_a.save_pretrained(save_dir)
print("✅ マージ完了 →", save_dir)

from transformers import AutoModelForCausalLM, AutoTokenizer
import torch

m = AutoModelForCausalLM.from_pretrained(save_dir, torch_dtype=torch.float16, device_map="auto")
t = AutoTokenizer.from_pretrained(save_dir, use_fast=False) # ← ここ
if t.pad_token is None:
t.pad_token = t.eos_token

prompt = "コーヒーの淹れ方のコツを、優しく短く教えて。"
inputs = t(prompt, return_tensors="pt").to(m.device)
with torch.no_grad():
out_ids = m.generate(
**inputs,
max_new_tokens=80,
do_sample=True,
temperature=0.7,
top_p=0.9,
repetition_penalty=1.1, # 連発を抑える
no_repeat_ngram_size=2, # 短い反復を防ぐ
pad_token_id=t.eos_token_id
)

# プロンプトの長さを元に出力部分だけを切り出す方法(安全)
gen_part = out_ids[0, inputs.input_ids.shape[1]:]
print(t.decode(gen_part, skip_special_tokens=True))

# セルF0:bitsandbytes をアンインストール(量子化は使わない前提)
!pip -q uninstall -y bitsandbytes

# セルFix1:bnb と triton を入れて import エラーを解消
!pip -q install bitsandbytes==0.43.3 triton==2.3.0

# セルE1:環境セットアップ&LoRA付与(bitsandbytes無し)
!pip -q install transformers==4.44.2 peft==0.12.0 accelerate==0.34.2

import os, copy, time, random
import numpy as np
import torch
from transformers import AutoModelForCausalLM, AutoTokenizer
from peft import LoraConfig, get_peft_model

DEVICE = "cuda" if torch.cuda.is_available() else "cpu"
BASE_MODEL = "./merged_model" # マージ済みモデル

# 乱数固定
SEED = 42
random.seed(SEED); np.random.seed(SEED); torch.manual_seed(SEED)
if DEVICE == "cuda":
torch.cuda.manual_seed_all(SEED)

print("Loading merged model...")
model = AutoModelForCausalLM.from_pretrained(
BASE_MODEL,
torch_dtype=torch.float16 if DEVICE=="cuda" else torch.float32,
device_map="auto"
)
tokenizer = AutoTokenizer.from_pretrained(BASE_MODEL, use_fast=False)
if tokenizer.pad_token is None:
tokenizer.pad_token = tokenizer.eos_token

# GPT-2系向けのLoRAターゲット
lora_cfg = LoraConfig(
r=8,
lora_alpha=16,
target_modules=["c_attn", "c_proj", "c_fc"],
lora_dropout=0.05,
bias="none",
task_type="CAUSAL_LM",
)
peft_model = get_peft_model(model, lora_cfg)
peft_model.eval()

print("✅ Ready for evolution (no bitsandbytes).")

# セルE2:ユーティリティ(生成、スコア、遺伝子操作)

# 生成(プロンプトの後ろだけ取り出す)
def generate(peft_model, tokenizer, prompt, max_new_tokens=96):
inputs = tokenizer(prompt, return_tensors="pt").to(peft_model.device)
with torch.no_grad():
out_ids = peft_model.generate(
**inputs,
max_new_tokens=max_new_tokens,
do_sample=True,
temperature=0.7,
top_p=0.9,
repetition_penalty=1.15,
no_repeat_ngram_size=2,
pad_token_id=tokenizer.eos_token_id,
eos_token_id=tokenizer.eos_token_id,
)
gen_only = out_ids[0, inputs.input_ids.shape[1]:]
return tokenizer.decode(gen_only, skip_special_tokens=True).strip()

# 軽い流暢さスコア
def fluency_score(txt: str) -> float:
L = len(txt)
s = 0.0
if 40 <= L <= 280: s += 1.0 if any(p in txt for p in ["。","!","!","?","?"]): s += 0.2 if "。" in txt and txt.count("。。") == 0: s += 0.2 # 同一記号の連続を軽く減点 for ch in set(txt): if ch != " " and ch*4 in txt: s -= 0.6; break return s # “表記の崩れ”を抑えるための簡易スコア(例:『淹れる』推奨) def orthography_score(txt: str) -> float:
good = ["淹れる","淹れ方","蒸らし","抽出","湯温"]
bad = ["をれる","れ方","お茶をれる","コーヒーをれる","れれる"]
return 0.6*sum(txt.count(w) for w in good) - 1.0*sum(txt.count(b) for b in bad)

# (好きに差し替え可)評価用プロンプト
PROMPTS = [
"紅茶教室の講師として、初心者向けに、3つのコツを箇条書きで短く教えて。",
"コーヒーを美味しく淹れるための手順を、番号付きで3行で説明して。",
"来客に出すお茶のマナーを、やさしく1段落でまとめて。"
]

# 総合評価(平均)
def evaluate_individual(peft_model, tokenizer, lora_sd):
# LoRA適用
with torch.no_grad():
for k,v in peft_model.state_dict().items():
if "lora_" in k and k in lora_sd:
v.copy_(lora_sd[k].to(v.device, dtype=v.dtype))
peft_model.eval()

total = 0.0
for p in PROMPTS:
out = generate(peft_model, tokenizer, p)
total += fluency_score(out) + orthography_score(out)
return total / len(PROMPTS)

# LoRA重みの抽出/適用(state_dictベース)
def extract_lora_state_dict(peft_model):
return {k: v.detach().cpu().clone() for k,v in peft_model.state_dict().items() if "lora_" in k}

def crossover_uniform(a, b, cxpb=0.5):
child = {}
for k in a.keys():
ta, tb = a[k], b[k]
mask = torch.rand_like(ta, dtype=torch.float32) < cxpb
child[k] = torch.where(mask.to(ta.device), ta.to(ta.device), tb.to(ta.device)).cpu()
return child

def mutate_gaussian(sd, mut_pb=0.3, sigma=0.01):
child = {}
for k, t in sd.items():
x = t.clone()
mask = torch.rand_like(x, dtype=torch.float32) < mut_pb noise = torch.randn_like(x, dtype=torch.float32) * sigma x = x + noise * mask child[k] = x return child print("✅ Utils ready.") # セルE3:進化ループ本体(POP=10, GEN=20) POP_SIZE = 10 N_GEN = 20 CXPB = 0.5 # 交叉率 MUT_PB = 0.3 # 突然変異率 MUT_SIG = 0.01 # 変異の強さ SAVE_DIR = "./evo_outputs" os.makedirs(SAVE_DIR, exist_ok=True) base_lora = extract_lora_state_dict(peft_model) def random_individual(): # 初期はベース周りにランダム微擾 return mutate_gaussian(base_lora, mut_pb=0.5, sigma=0.02) population = [random_individual() for _ in range(POP_SIZE)] history = [] best_sd, best_score = None, -1e9 start = time.time() for gen in range(N_GEN): scores = [evaluate_individual(peft_model, tokenizer, ind) for ind in population] gen_best = float(np.max(scores)) gen_avg = float(np.mean(scores)) best_idx = int(np.argmax(scores)) print(f"Gen {gen:02d} | best={gen_best:.3f} avg={gen_avg:.3f}") history.append((gen, gen_best, gen_avg)) if gen_best > best_score:
best_score = gen_best
best_sd = copy.deepcopy(population[best_idx])

# エリート選択(上位20%)
elite_k = max(1, POP_SIZE // 5)
elite_idx = list(np.argsort(scores))[::-1][:elite_k]
elites = [population[i] for i in elite_idx]

# 親プール(トーナメント)
def tournament(k=3):
idxs = random.sample(range(POP_SIZE), k)
return population[max(idxs, key=lambda j: scores[j])]

parents = elites[:]
while len(parents) < POP_SIZE:
parents.append(tournament())

# 次世代
next_pop = elites[:]
while len(next_pop) < POP_SIZE: pa, pb = random.sample(parents, 2) child = crossover_uniform(pa, pb, cxpb=0.5 if CXPB>0 else 1.0)
child = mutate_gaussian(child, mut_pb=MUT_PB, sigma=MUT_SIG)
next_pop.append(child)

population = next_pop

elapsed = (time.time() - start) / 60.0
print(f"\n✅ Evolution done. Elapsed ~{elapsed:.1f} min, best={best_score:.3f}")

# 保存
torch.save(best_sd, os.path.join(SAVE_DIR, "best_lora_state.pt"))
import csv
with open(os.path.join(SAVE_DIR, "evolution_log.csv"), "w", newline="", encoding="utf-8") as f:
w = csv.writer(f); w.writerow(["gen","best","avg"]); w.writerows(history)
print("Saved:", os.path.join(SAVE_DIR, "best_lora_state.pt"))
print("Saved:", os.path.join(SAVE_DIR, "evolution_log.csv"))

# セルE4:ベスト個体で軽く生成プレビュー
best_path = "./evo_outputs/best_lora_state.pt"
assert os.path.exists(best_path), "best_lora_state.pt が見つかりません"

# LoRAを適用
with torch.no_grad():
current = peft_model.state_dict()
best_sd = torch.load(best_path, map_location="cpu")
for k in current.keys():
if "lora_" in k and k in best_sd:
current[k].copy_(best_sd[k].to(current[k].device, dtype=current[k].dtype))

# テストプロンプト
tests = [
"紅茶教室の講師として、初心者向けに、3つのコツを箇条書きで短く教えて。",
"コーヒーを美味しく淹れるための手順を、番号付きで3行で説明して。"
]
for p in tests:
print("\n[PROMPT]", p)
print("[OUTPUT]", generate(peft_model, tokenizer, p))

!zip -r evo_results.zip merged_model evo_outputs

from google.colab import drive
drive.mount('/content/drive')

!mkdir -p /content/drive/MyDrive/evo_backup
!cp -r ./merged_model ./evo_outputs /content/drive/MyDrive/evo_backup/

import pandas as pd, matplotlib.pyplot as plt
df = pd.read_csv('./evo_outputs/evolution_log.csv')
plt.plot(df['gen'], df['best'], label='Best')
plt.plot(df['gen'], df['avg'], label='Average')
plt.xlabel('Generation'); plt.ylabel('Score'); plt.legend(); plt.grid(); plt.show()

研究者は論文を書く。
開発者はブログ記事を書く。

さすれば、科学は進歩する。かな。

****************

最近のデジタルアート作品を掲載!

X 旧ツイッターもやってます。
https://x.com/ison1232

インスタグラムはこちら
https://www.instagram.com/nanahati555/

***************

コーヒー豆専門 販売コンサルのチャットボット「珈琲豆売り増くん」を作りました

********

※LINE対応チャットボット版の
「 LINEチャットボット屋」
いろんなチャットボットがあります。
ぜひ、ご覧ください!

********

無料です。使ってみてください。

珈琲豆売り増くん
https://chatgpt.com/g/g-68d261dd6ef081919b3a4d03fd580428-kohidou-zhuan-men-fan-mai-konsaru-jia-bei-dou-mai-rizeng-kun

********

***************

***************

「珈琲豆売り増くん」とは?コーヒー販売者専用の相談相手

コーヒー豆の販売を始めたいけれど、「どうやって集客すればいいのか分からない」「どんな価格で売ればいいのか迷う」という声をよく聞きます。
そんな悩みを解決するために誕生したのが、コーヒー豆販売に特化したコンサル型チャットボット「珈琲豆売り増くん」です。

「珈琲豆売り増くん」は、これから販売を始める個人事業主や小規模カフェ、オンラインショップを立ち上げる人に向けて作られています。

単なるFAQボットではなく、ユーザーの状況を聞き出しながら最適な販売戦略を提案してくれる“伴走型アドバイザー”というのが特徴です。

たとえば、「初心者に向けて売りたいのか」「ギフト需要を狙いたいのか」などをヒアリングし、そのターゲットに合わせた販売プランを具体的に提示してくれます。
「とりあえず誰かに相談したい」というときに、いつでも使える心強い相棒になるはずです。

販売初心者でも安心!戦略から集客まで伴走サポート

コーヒー豆の販売を考えるとき、まず立ちはだかるのは「何から始めればいいか分からない」という壁です。
珈琲豆売り増くんは、その最初の一歩から伴走してくれる存在です。

例えば、こんなアドバイスをしてくれます。

ターゲット顧客の明確化:「初心者」「コーヒー通」「ギフト需要」などから、自分に合う層を選ぶサポート。

販売チャネルの提案:BASEやSTORESのようなネットショップ、InstagramやLINE公式アカウントとの連携方法。

価格設定の考え方:原価計算だけでなく「顧客が納得する価格」を重視するアドバイス。

小さなテスト販売の提案:まずは少量パックから販売して反応を確認する方法。

こうした具体的なガイドがあると、初心者でも安心して進められます。

しかも一度の相談で終わるのではなく、「やってみた結果どうでしたか?」と次のステップに進むための会話ができるのが、このボットの最大の強みです。

 

SNS活用・価格設定・リピーター対策まで具体的に提案

販売を続けていくと、「SNSに何を投稿すればいいのか」「リピーターを増やすにはどうすればいいのか」といった悩みが出てきます。

珈琲豆売り増くんは、そうした実践的な課題にも対応してくれます。

SNS活用
Instagramでは「焙煎中の動画」「パッケージ写真」「淹れ方のコツ」を投稿すると効果的だと言われています。
このような具体例を交えて「どんな写真や文章を載せればいいか」までアドバイスしてくれるのです。

価格設定
単なる「仕入れ+利益」で決めるのではなく、「ターゲット顧客が納得する価格」になるように提案してくれます。

初心者向けには小容量のお試しパック、ギフト需要には高級感あるボックス、というように価格帯ごとの工夫まで考えてくれます。

リピーター対策
「次回割引クーポンを入れる」「LINEで新商品情報を配信する」など、長く付き合ってくれる顧客を増やすためのアドバイスも得られます。

一度きりの販売で終わらず、ブランドとして成長させていく道筋を示してくれるのは大きな魅力です。

実際に使うとどうなる?想定シナリオとメリット

では、実際に珈琲豆売り増くんを使ったとき、どんな流れになるのでしょうか。

想定シナリオ

販売者が質問する:「コーヒー豆をネットで売りたいけど、どこから始めればいいですか?」

ボットが回答する:「まずはターゲットを初心者向けかギフト需要かに絞りましょう。どちらを優先したいですか?」

販売者が答える:「ギフト需要を狙いたいです」

ボットが提案する:「それなら高級感あるパッケージを少量から試してみましょう。まずはBASEで販売を始め、Instagramで贈り物シーンを投稿すると効果的です」

このように、会話の中で状況を整理しながら具体的な行動プランに落とし込めるのが特徴です。

利用メリット

24時間いつでも相談できる
自分専属のマーケティング担当のように伴走してくれる
初心者でも段階的に学びながら販売を進められる
集客からリピーター戦略まで一貫してサポートしてくれる

まるで「売上を一緒に作ってくれるパートナー」を持つような感覚になるはずです。

まとめ:あなたのコーヒー販売を伸ばす心強いパートナー

コーヒー豆の販売は、ただ豆を用意するだけではなく、戦略を考え、顧客に伝え、リピーターを増やしていくことが求められます。

その過程は一人でやろうとすると不安も多く、何度も壁にぶつかると聞きます。

珈琲豆売り増くんは、そんな販売者に寄り添いながら、一歩ずつ前に進めるための相談相手です。
販売チャネルの選び方から価格設定、SNS運用、リピーター対策まで、幅広くアドバイスOK。

これからコーヒー豆販売を始めたい人にとって、まさに「心強い伴走者」になるでしょう。

無料ですので、ぜひ使ってみてください。

珈琲豆売り増くん
https://chatgpt.com/g/g-68d261dd6ef081919b3a4d03fd580428-kohidou-zhuan-men-fan-mai-konsaru-jia-bei-dou-mai-rizeng-kun

****************

最近のデジタルアート作品を掲載!

X 旧ツイッターもやってます。
https://x.com/ison1232

インスタグラムはこちら
https://www.instagram.com/nanahati555/

****************

プロの仮想投資家があなたの事業をジャッジする!「マネーの獅子プロ版」チャットボットを作りました

****************

マネーの獅子プロ版はnoteにて販売しています。(500円)
↓↓↓
「あなたの事業は出資されるのか」 マネーの獅子プロ版 30人のAI投資家があなたの事業を審査する そんなチャットボットを作りました
https://note.com/tonsuke456/n/nbf5083e8cca4

****************

※LINE対応チャットボット版の
「 LINEチャットボット屋」
いろんなチャットボットがあります。
ぜひ、ご覧ください!

***************

***************

AI投資家が、あなたのビジネスに投資するかを判断する

新しい時代のビジネス評価ツール、「マネーの獅子プロ版」チャットボットを作りました。

これは、AIが投資家の視点であなたの事業アイデアを分析し、成功確率を数値化して示すシステムです。
感覚ではなく、データに基づいて判断することで、起業家や事業主がより確実な一歩を踏み出せるように支援します。

ビジネスを立ち上げるとき、「本当にこのアイデアは成功するのか?」「投資に値するのか?」という悩みは誰にでもありますよね。

マネーの獅子プロ版は、そうした不安を解消するために作られました。

投資家が実際にチェックする視点をAIに学習させ、あなたのアイデアを客観的に評価します。
今までのような“なんとなく良さそう”という判断から一歩先へ進めるのが、このツールの最大の特徴です。

あなたの事業アイデアの“実力”を分析します

マネーの獅子プロ版が目指すのは、「ビジネスの実力を数値化」することです。
AIがあなたの事業概要を読み取り、市場性や収益性などを総合的に分析。最終的に「成功確率スコア」として提示します。

このスコアは単なる数字ではありません。AIが過去の成功・失敗データをもとに学習しているため、統計的な裏付けがあります。

「何が強みで、どこに改善の余地があるのか」が明確にわかるため、次のアクションにすぐ移せます。

さらに、マネーの獅子プロ版ではスコアを上げるための「改善提案」も自動生成されます。

まるで経験豊富な投資家が、あなたの事業計画を添削してくれているような感覚です。経験頼りではなく、データと理論に基づいた判断をしてくれます。

AIが評価する5つの指標とは? 成功確率を表示します

「市場性」「収益性」「実現性」「差別化」「成長性」を徹底分析。

マネーの獅子プロ版は、事業を次の5つの観点から分析します。

1. 市場性
今後の市場規模や需要の伸びを予測。あなたのビジネスが成長産業に位置しているかを判断します。

2. 収益性
利益構造の妥当性をチェック。価格設定、コスト構造、利益率の見込みをAIが評価します。

3. 実現性
実行体制やリソースの面から、実現可能性を数値化。実行プランが現実的かどうかを判定します。

4. 差別化
競合との違いを分析し、独自性の強さをスコア化。他社にはない強みがどれほどあるかを評価します。

5. 成長性
短期的・中長期的なスケールアップの可能性をチェック。持続的に発展できるかを見極めます。

これらの評価を総合して導き出されるのが、「成功確率スコア」です。
あなたの考えたビジネスモデルがどれだけ成功確率があるのかを教えてくれます。

改善提案まで! スコアに基づいた次の一手をアドバイス

マネーの獅子プロ版の最大の魅力は、スコアを出して終わりではないことです。

各項目の評価に基づき、「次に取るべき行動」を具体的にアドバイスします。

例えば、収益性が低い場合は「サブスクリプションモデルの導入を検討」「高単価商品の開発」など、現実的な改善策が提示されます。

市場性が弱いと判定された場合は、「よりニッチなターゲット層の設定」「成長市場へのピボット」など、軌道修正のヒントを受け取れます。

これらの提案は、ただのテンプレートではなく、あなたの事業内容に合わせて生成されるオーダーメイド型。まさに“AI投資家が伴走してくれる感覚”です。

ビジネスの方向性に悩んだとき、プロの視点を即座に得られることは大きな強みになります。

スタートアップから個人事業まで、誰でも診断OK

マネーの獅子プロ版は、スタートアップだけでなく、個人事業主や副業起業家にも開かれたツールです。
業種や規模を問わず、アイデアベースでも診断が可能。たとえまだ構想段階であっても、AIが市場性や実現性を判断してくれます。

利用はとてもシンプルです。タイトル、概要、対象顧客、強みを入力するだけで、数秒後には投資家目線のレポートが生成されます。

分析結果はコメント形式でわかりやすく表示されるため、ビジネス初心者でもすぐに理解できます。

ぜひ一度、AI投資家の目であなたのアイデアをチェックしてみてください。もしかしたら、そこには新しい可能性が見えてくるかもしれません。

実際の「マネーの獅子プロ版」の事業成功確率レポートを紹介

下記はあるビジネスモデルを実際に入力して、その投資の可否をジャッジしてもらった結果です。ご参考にしてください。

*****

この結果は30名の仮想投資家の視点をもとに分析した予測値です。
将来の成果を保証するものではありません。

🕐 初回評価の成功確率
平均:42% 中央値:41%
信頼区間:39〜45%

🎯 改善提案(詳細分析)

まず注目すべきは、この事業が取り組んでいる「シニアの孤立防止・健康維持」という社会課題が非常に重要であり、行政や医療機関からも追い風がある点です。しかし現時点のビジネスモデルは、月額課金+スポンサー広告という二本柱に限定されており、シニア層特有の課金率の低さや広告依存のリスクが課題として浮き彫りになっています。投資家の多くが「事業の持続可能性」を懸念しており、まずは収益構造の補強が必要と考えられます。

有力な改善策としては、B2BやB2G(行政向け)の収益源を加えることです。例えば介護事業者や病院に対して「地域の高齢者コミュニティへのアクセス権」を販売したり、行政が推進する高齢者見守り事業と連携することで、安定的な収益を確保できます。さらに健康保険組合や製薬企業とスポンサー契約を結べば、単なる広告モデル以上に強固な協賛基盤を築けるでしょう。これにより、ユーザー課金に依存しすぎない持続的なモデルに進化できます。

次に、利用率と継続率を高める工夫が必要です。シニア層はデジタル利用習慣が弱く、アプリ単体では継続率が低い傾向があります。そのため「リアルイベントや地域交流会」との連携が鍵となります。オフラインでの活動がオンラインコミュニティへの導線となり、逆にアプリ上で知り合った仲間がイベント参加につながるという循環をつくることが、投資家からも高く評価されやすい仕組みです。

さらに、使いやすさを徹底したUI/UX設計と、紙媒体や電話サポートを活用したハイブリッド運用が有効です。特に70代以上の利用者にとっては、完全デジタル依存ではハードルが高いため、「地域新聞やチラシ」と「アプリ」の二重のチャネルで接触することで導入障壁を下げられます。これらを通じて、投資家が求める「利用者の拡大余地とリテンションの裏付け」が強化され、成功確率を引き上げることが期待できます。

🧭 分析コメント(詳細解説)

今回の評価では、30名の仮想投資家のうち、約12名が「条件付きで投資可能」、10名が「社会的意義は認めるが収益性に不安」、8名が「規模拡大の難しさから消極的」と判断しました。平均成功確率は42%に留まりましたが、市場の社会的意義は高く評価され、特にESG・インパクト投資志向の投資家からはポジティブなコメントが寄せられました。

一方で懸念点は収益性の低さと利用率の不透明さです。シニア向けアプリは一般的に課金率が低く、広告モデルも単価が上がりにくいため、「どのように安定収益を構築するのか」が投資家の関心事となりました。特に、収益が「ユーザー数に大きく依存している」点が不安要素として指摘されています。

ただし、地域NPOとの提携による「信頼性の高いコミュニティ設計」は大きな強みと評価されています。既存のSNSとの差別化要素として、投資家は「地域性」と「安心感」を重要なモート(参入障壁)と捉えており、この点は市場での差別化要因となり得ます。

また、市場の外部要因も追い風です。高齢化の進行に伴い、行政や医療機関の関心が高まっているため、B2G/B2Bの導入がうまく進めば評価は一気に改善します。投資家は「自治体や介護事業者との契約がどの程度確保できるか」を、今後の投資判断の基準にする可能性が高いでしょう。

総じて、この事業は社会的価値と政策的追い風がある一方で、収益モデルの再設計と利用率向上の具体策が不足しているため、現時点での投資確率はやや低めとなりました。しかし、行政や介護業界との提携に成功すれば、成功確率は55〜60%まで上昇する可能性が十分にあります。

🪜 次にできること
ここから先は実際にお試しを!

まとめ

どうでしょうか。この仮想投資家があなたのビジネスモデルをAIの力でジャッジするチャットボット。

特に、成功確率の算出と、改善提案のアドバイスは必見です。

これを元にあなたのビジネスを修正して、実際の投資家の方々から、ぜひマネーを引き出してください。

また、投資を受けないという方でも、この成功確率とアドバイスを参考にして、ぜひあたなの作ったビジネスモデルをブラッシュアップしてください。

さすれば、さらに成功への道は近くなるかと思います。

マネーの獅子プロ版はnoteにて販売しています。(500円)
↓↓↓
「あなたの事業は出資されるのか」 マネーの獅子プロ版 30人のAI投資家があなたの事業を審査する そんなチャットボットを作りました
https://note.com/tonsuke456/n/nbf5083e8cca4

ぜひ利用してみてください。

****************

最近のデジタルアート作品を掲載!

X 旧ツイッターもやってます。
https://x.com/ison1232

インスタグラムはこちら
https://www.instagram.com/nanahati555/

****************

Sakana AIさんのShinkaEvolveを参考にgoogle colabでアスキーアートを進化させてみた

********

※LINE対応チャットボット版の
「 LINEチャットボット屋」
いろんなチャットボットがあります。
ぜひ、ご覧ください!

********

進化的アルゴリズムを実装する理由

下記の記事が面白かったので、google colabで簡易版を実装してみました。
https://sakana.ai/shinka-evolve/
英語版ですが、翻訳、もしくは生成AIに投げれば解説してくれます。

さて、最近AI技術の中でも注目されているのが「進化的アルゴリズム」です。

上記のSakana AIさんのShinkaEvolveが面白く、これを再現したいなあと。

git hubにコードが公開されているようですが、出力がコードなので、学生さんにどうかなと。

それで、アスキーアートで出力させたら面白いのではと。

今回作ったコードは、生物の進化のように、少しずつ改良を重ねながらより良い形を見つけていくものです。

この考え方をAIと組み合わせ、「ASCII・アスキーアートを進化させる」という少しユニークな実験を行ってみました。

テーマは「猫のアスキーアートを進化させる」

最初はシンプルな記号だけのアートから始まり、世代を重ねるごとにどのように姿を変えていくのかを観察しました。
この記事では、実際の出力や進化の過程を交えながら、その結果を紹介します。

初期アートからのスタート

まず、進化の出発点に置いたのは、次のような非常にシンプルな3行のアートです。

***
* *
***

この段階では、もちろん猫らしさは一切ありません。
しかし、進化アルゴリズムはこのような小さな種(シード)からスタートし、世代ごとに少しずつ改善を重ねながら最終的な形に近づいていくとのこと。

今回の設定は以下の通りです。

試行数:150回

世代上限:30世代

母集団サイズ:5

評価関数では、アートの「密度がちょうど良い(0.25前後)」「左右対称である」「縦に芯がある」などの基準をスコア化し、より美しいアートを残すようにしました。

世代を重ねて進化していく

実際にプログラムを走らせると、初期スコアは 108.73 でした。
そして、最初に登場した猫らしい形がこちらです。

 

 

この段階で、すでに「猫らしさ」を感じられるレベルになっています。

そこからさらに世代を重ねるごとに、形は少しずつ複雑で洗練されたものになっていきました。

第2世代(GEN 2)では、耳や顔のディテールが増え、全体のバランスも整ってきました。

第4世代では、より左右対称性が高まり、密度も評価関数の理想値に近づいてきました。

最終的にはスコア 114.82 を記録し、アートとしての完成度がぐっと上がりました。

早期停止の理由と「最終世代」の完成形

設定上は30世代まで進化を続けられるようにしていましたが、今回は 9世代目で早期停止となりました。

これは、「一定期間スコアの改善が見られなければ自動的に終了する」という仕組みが働いたためです。
このような「早期停止」は、無駄な試行を避け、効率的に最終結果へ到達するための仕組みです。

 

完成した最終世代のアート

そして完成した最終世代のアートがこちらです。

耳のあたりが若干ずれていますが、
最初の「***」からは想像もつかないほど猫らしい形になっています。

線の密度や縦軸の安定感も評価基準に沿っており、美しくまとまったASCII・アスキーアートとしていいんじゃないでしょうか。

AIがアートを育てていく実感

今回の実験を通して感じたのは、「AIがアートを生成する」というよりも、「AIがアートを育てていく」という感覚でした。

人間が行うのは「テーマ」と「条件」の設定だけです。
あとはAIが自動的に候補を出し、評価関数によって優れたものを残しながら、少しずつ形を洗練させていきます。

世代ごとの出力を見ていると、「耳の形が整った」「目がはっきりした」など、少しずつの進化がはっきりと感じ取れます。

これはまるで、AIと一緒に作品を育てているような体験です。

ただし、課題もいくつかあります。
・評価関数の設計によって、結果が大きく変化する
・改善が停滞すると、似たようなアートが繰り返し生成される
・LLMを使うため、APIコストや応答速度の考慮も必要

それでも、短時間でここまでの進化を見せるのは非常に興味深く、AIが「試行錯誤」を通して美を探す過程を目の当たりにできました。

まとめ

今回のgoogle colabの実験を通して、次のような学びが得られました。

・シンプルな初期アートでも、進化を重ねれば見事なアートっぽくなる。

・評価関数の工夫次第で、最終的な形や特徴が大きく変わる。

・早期停止により、効率的にベストな結果を得られる。

・進化の過程を観察すること自体が楽しく、教育的価値もある。

・商用化を考える場合は、APIコストや最適化の設計も重要。

人の手をほとんど介さず、AIが進化というプロセスを経て芸術を生み出す姿は爽快でした。

次は評価基準を変えて「犬」や「ドラゴン」など別のテーマに挑戦したり、チャットボットとして公開し、他の人にも体験してもらえるような形にする予定です。

使用したPythonコード

使用したPythonコードです。
google colabで実装できます。

※openAIのAPI(gpt4o-mini)を使っているので、コードを走らせると若干料金がかかります。
とはいえ、1回走らせて数円でしょう。
(一応、150回の試行で10円位との試算ですが、各自で試算してください)


# -*- coding: utf-8 -*-
"""
Google Colab 用:ASCIIアート進化アルゴリズム(LLM+評価関数)
-----------------------------------------------------------------
■ できること
- テキストボックスから APIキー を入力(Base URL / モデルは固定)
- OpenAI gpt-4o-mini を使って親コードから改良案を生成
- 評価関数でスコア化、良い個体を残して世代交代
- 新規性フィルタ(似た案の量産を回避)
- 試行回数・世代数・母集団サイズなどをパラメータで調整
- LLM を使わない“ローカル変異デモモード”でも動作可
"""

import math, random, json, time, textwrap
import requests
from dataclasses import dataclass
from typing import List, Dict, Any

try:
from IPython.display import display, HTML, clear_output
import ipywidgets as widgets
except Exception:
widgets = None

# =============================
# 設定
# =============================
class Config:
MAX_TRIALS = 150
MAX_GENERATIONS = 30
POP_SIZE = 5
SIMILARITY_THRESHOLD = 0.92
EARLY_STOP_PATIENCE = 5
PATCH_TYPE_PROBS = {"diff": 0.6, "full": 0.25, "cross": 0.15}
TARGET_DENSITY = 0.25

# =============================
# 評価関数
# =============================
def evaluate_art(art: str, target_density: float = Config.TARGET_DENSITY) -> float:
lines = [ln for ln in art.split("\n") if ln.strip()]
if not lines:
return 0.0
area = sum(len(ln) for ln in lines)
stars = sum(ch in "*#@%☆★^/\\_|" for ln in lines for ch in ln)
density = stars / max(1, area)
score = 100.0 * (1.0 - abs(density - target_density))
sym = sum(1 for ln in lines if ln == ln[::-1])
score += sym * 5.0
deco = sum(art.count(s) for s in ["☆", "★", "^", "/", "\\", "_"])
score += min(deco, 10) * 1.5
mid = max(len(ln) for ln in lines) // 2
vertical = 0
for ln in lines:
if mid < len(ln) and ln[mid] in "*|^#@%": vertical += 1 score += vertical * 2.0 return max(score, 0.0) # ============================= # 類似度チェック # ============================= def similarity(a: str, b: str) -> float:
a_lines = [ln.strip() for ln in a.split("\n") if ln.strip()]
b_lines = [ln.strip() for ln in b.split("\n") if ln.strip()]
if not a_lines or not b_lines:
return 0.0
m = sum(1 for i in range(min(len(a_lines), len(b_lines))) if a_lines[i] == b_lines[i])
return m / max(len(a_lines), len(b_lines))

def is_novel(candidate: str, archive: List[str], th: float = Config.SIMILARITY_THRESHOLD) -> bool:
return all(similarity(candidate, prev) < th for prev in archive) # ============================= # ローカル変異 # ============================= def mild_mutation(art: str) -> str:
s = art.replace("* *", "*☆*").replace("---", "^-^")
if random.random() < 0.3: lines = [ln for ln in s.split("\n") if ln] if lines: width = max(len(ln) for ln in lines) box_top = "╔" + "═" * width + "╗" box_bot = "╚" + "═" * width + "╝" new_lines = [ln.ljust(width) for ln in lines] s = "\n".join([box_top] + ["║" + ln + "║" for ln in new_lines] + [box_bot]) return s def fresh_from_scratch() -> str:
return " /\\_/\\\n ( o.o )\n > ^ <\n" def crossover(a: str, b: str) -> str:
a_lines = a.split("\n")
b_lines = b.split("\n")
cut_a = len(a_lines) // 2
cut_b = len(b_lines) // 2
return "\n".join(a_lines[:cut_a] + b_lines[cut_b:])

# =============================
# LLM 設定
# =============================
@dataclass
class LLMConfig:
api_key: str = ""
use_llm: bool = True
base_url: str = "https://api.openai.com"
model: str = "gpt-4o-mini"
timeout: float = 60.0

LLM_SYS_PROMPT = "あなたはASCIIアートを改良するアシスタントです。出力はASCIIアートのみで、コードブロックや説明は不要です。"

def call_llm(cfg: LLMConfig, prompt: str) -> str:
headers = {
"Authorization": f"Bearer {cfg.api_key}",
"Content-Type": "application/json",
}
payload = {
"model": cfg.model,
"messages": [
{"role": "system", "content": LLM_SYS_PROMPT},
{"role": "user", "content": prompt},
],
"temperature": 0.9,
"max_tokens": 500,
}
url = cfg.base_url.rstrip("/") + "/v1/chat/completions"
try:
resp = requests.post(url, headers=headers, data=json.dumps(payload), timeout=cfg.timeout)
resp.raise_for_status()
data = resp.json()
return data["choices"][0]["message"]["content"].strip("`\n ")
except Exception as e:
return f"LLM_ERROR: {e}\n" + mild_mutation(prompt)

# =============================
# パッチ生成
# =============================
def generate_patch(parents: List[str], llm_cfg: LLMConfig, theme: str) -> str:
kind = random.choices(list(Config.PATCH_TYPE_PROBS.keys()), weights=list(Config.PATCH_TYPE_PROBS.values()), k=1)[0]
if kind == "diff":
base = random.choice(parents)
user_prompt = f"お題: {theme}\n親アートを少しだけ美しく改良してください。左右対称・密度0.25付近・特殊記号は少量:\n---\n{base}\n---\n出力はASCIIアートのみ。"
return call_llm(llm_cfg, user_prompt)
elif kind == "full":
user_prompt = f"お題: {theme}\n0から新しいASCIIアートを1つ作ってください。3〜10行、各行幅は揃える。左右対称、密度0.25付近。出力はASCIIアートのみ。"
return call_llm(llm_cfg, user_prompt)
else:
if len(parents) >= 2:
a, b = random.sample(parents, 2)
user_prompt = f"お題: {theme}\n親Aと親Bの良い部分を組み合わせ、新しいASCIIアートを生成:\n[A]\n{a}\n[B]\n{b}\n条件: 左右対称、3〜10行、各行幅揃える。出力はASCIIアートのみ。"
return call_llm(llm_cfg, user_prompt)
else:
return mild_mutation(parents[0])

@dataclass
class Individual:
art: str
score: float

def select_parents(pop: List[Individual], k: int = 2) -> List[Individual]:
ranked = sorted(pop, key=lambda x: x.score, reverse=True)
weights = [1.0 / (i + 1) for i in range(len(ranked))]
return random.choices(ranked, weights=weights, k=k)

def run_evolution(initial_art: str, theme: str, llm_cfg: LLMConfig):
archive: List[str] = [initial_art]
population: List[Individual] = [Individual(initial_art, evaluate_art(initial_art))]
while len(population) < Config.POP_SIZE:
seed = fresh_from_scratch()
population.append(Individual(seed, evaluate_art(seed)))
archive.append(seed)
best = max(population, key=lambda x: x.score)
trials_used = len(population)
no_improve = 0
print("[START] 初期ベストスコア:", round(best.score, 2))
print(best.art, "\n---\n")
gen = 0
while gen < Config.MAX_GENERATIONS and trials_used < Config.MAX_TRIALS: gen += 1 parents = select_parents(population, 2) parent_arts = [p.art for p in parents] children: List[Individual] = [] for _ in range(Config.POP_SIZE): cand = generate_patch(parent_arts, llm_cfg, theme) if not cand.strip(): continue if not is_novel(cand, archive): continue sc = evaluate_art(cand) children.append(Individual(cand, sc)) archive.append(cand) trials_used += 1 if trials_used >= Config.MAX_TRIALS:
break
if not children:
cand = mild_mutation(best.art)
sc = evaluate_art(cand)
children.append(Individual(cand, sc))
archive.append(cand)
trials_used += 1
merged = sorted(population + children, key=lambda x: x.score, reverse=True)
population = merged[: Config.POP_SIZE]
if population[0].score > best.score:
best = population[0]
no_improve = 0
else:
no_improve += 1
print(f"[GEN {gen}] trials={trials_used} best={round(best.score,2)}")
print(best.art, "\n---\n")
if no_improve >= Config.EARLY_STOP_PATIENCE:
print("[EARLY STOP] 改善停滞")
break
print("[DONE] 総試行:", trials_used, "世代:", gen)
print("[BEST SCORE]", round(best.score, 2))
print(best.art)
return best

# =============================
# Colab UI
# =============================
if widgets is not None:
api_key_w = widgets.Password(value='', description='API Key:', layout=widgets.Layout(width='70%'))
theme_w = widgets.Text(value='猫(左右対称、美しさ重視)', description='お題:', layout=widgets.Layout(width='70%'))
trials_w = widgets.IntSlider(value=150, min=10, max=500, step=10, description='試行数')
gens_w = widgets.IntSlider(value=30, min=5, max=200, step=5, description='世代上限')
pop_w = widgets.IntSlider(value=5, min=2, max=20, step=1, description='母集団')
init_art_w = widgets.Textarea(value="***\n* *\n***", description='初期アート', layout=widgets.Layout(width='70%', height='120px'))
out = widgets.Output()
def on_run_clicked(_):
with out:
clear_output()
Config.MAX_TRIALS = int(trials_w.value)
Config.MAX_GENERATIONS = int(gens_w.value)
Config.POP_SIZE = int(pop_w.value)
llm_cfg = LLMConfig(api_key=api_key_w.value.strip())
print("[THEME]", theme_w.value)
print("\n[INITIAL]\n" + init_art_w.value + "\n---\n")
best = run_evolution(init_art_w.value, theme_w.value, llm_cfg)
print("\n[RESULT] ベストスコア:", round(best.score,2))
print(best.art)
run_btn = widgets.Button(description='進化を実行', button_style='success')
run_btn.on_click(on_run_clicked)
ui = widgets.VBox([api_key_w, theme_w, widgets.HBox([trials_w, gens_w, pop_w]), init_art_w, run_btn, out])
display(ui)
else:
print("Colab 環境で実行してください。")

いろんなアスキーアート作ってください。

****************

最近のデジタルアート作品を掲載!

X 旧ツイッターもやってます。
https://x.com/ison1232

インスタグラムはこちら
https://www.instagram.com/nanahati555/

****************

AI仮想顧客アンケート チャットボット 「アンケ太郎くん」を作った話

***************

***************

下記の記事が面白かったので、チャットボットに落とし込みできるか試してみました。

https://ascii.jp/elem/000/004/321/4321473/
https://ascii.jp/elem/000/004/321/4321473/2/

ビジネス系は制作してても面白くないですね。

じゃあなんでやるのって話ですが、単なる知的好奇心です。
(もともとアート系なのであしからずです。ちなみにエンタメ系大好きです笑)。

実際のところは、Pythonでコードを書いて、Google colabあたりですっきりプログラミングした方が格好的には良いのですが、それだと多くの人に使ってもらうときに、webサービスかアプリで公開+APIを使って課金、こんな感じで手間がかかります。

手早く公開したかったので、チャットボットで作れば可能かと思い、今回の実装となりました。

あくまでも仮想顧客のデータですので、参考値として利用してください。

「アンケ太郎くん」
noteに投稿しました。
そちらから有料記事で購入お願いします。(300円)↓↓↓

AIマーケッターが仮想顧客にアンケートするチャットボット「アンケ太郎くん」を作った話

********

※LINE対応チャットボット版の
「 LINEチャットボット屋」
いろんなチャットボットがあります。
ぜひ、ご覧ください!

********

AIでできる仮想顧客アンケート

新しい商品やサービスを考えたとき、「これって本当に売れるのだろうか?」「利用者がいるのかなあ」と気になるのは自然なことです。

実際に市場調査を行おうとすると、アンケートの設計、対象者の募集、回答の回収と集計など、多くの手間と費用がかかります。
個人事業や小さなチームでは、すぐに大規模な調査を実施するのは難しいのが現実です。

そんなときに役立つのが仮想顧客アンケートのチャットボットです。

これは実際の顧客データが手元になくても、AIが統計的な平均傾向をもとに「仮想の顧客」を生成し、その人たちがどう答えるかをシミュレーションできる仕組みです。

もちろん本物の実地調査ではありませんが、最初のアイデア検証や方向性の確認には十分な材料になります。

例えば
「1本200円の新作プロテインバーを売りたい」
「15,000円のユーティリティアイアンを販売したい」
といったケースで、想定ターゲットがどう感じるかを数十件から数百件のデータで一度に確認できるのは大きな強みです。

仮想顧客アンケート チャットボットの仕組み

このチャットボットの特徴はシンプルです。ユーザーが売りたい商品やサービスの情報を入力すると、まず「顧客データを持っていますか?」と確認します。

もしCSVやExcelの顧客データを持っていれば、それをアップロードしてAIが活用します。
データがなければ「統計平均」をもとにAIが仮想顧客を自動生成します。

この仮想顧客には、
・年齢帯
・性別
・居住地域
・世帯構成
・収入帯
・ライフスタイル
などが設定されます。

そこに対して
「購買意向(5段階)」「
価格評価(安い/ちょうどいい/高い)」
「利用シーン」
「代替選好(健康志向・時短・ボリューム・価格重視)」などの
アンケート回答が付与されます。

結果は二つの形式で出力されます。ひとつは誰でも読みやすいレポート形式。
もうひとつは機械が扱いやすいJSON形式です。

JSONはそのまま外部ツールで分析にかけられるため、あとからグラフ化や追加集計も容易です。

使い方のステップ 商品入力から出力まで

利用の流れはとてもわかりやすいです。

1. 商品情報を伝える
商品名、価格帯、想定シーンを簡単に入力します。

例:「新作のユーティリティアイアンを15,000円で売りたい。残り距離があるときに使う想定。」

これだけです。
これ以降はわからなければ、何もしないでOK。
勝手にAIが判断して進めてくれます。簡単〜🎵

2. 件数を指定する
30〜50件:軽いテスト
100件:より詳細な比較
300件:分割出力で大規模シミュレーション

3. 顧客データの有無を答える
ある場合:ファイルをアップロード
ない場合:統計平均をもとにAIが仮想顧客を生成

4. 出力スタイルを選ぶ
読みやすい文章レポート
JSONファイルでのデータ出力

例えば「プロテインバーを200円で売りたい、40件でシミュレーションして」と伝えるだけで、仮想顧客のプロフィールと回答、さらに集計結果が一度に得られます。

これまで数週間から数ヶ月かかっていた作業が、数分で形になります。

回答の多様性をコントロールする方法

このチャットボットの特徴のひとつは、回答の「多様性」をユーザーが調整できる点です。

生成AIにはtemperatureとtop-pというパラメータがあります。

temperatureは答えの「ゆらぎ」の大きさを意味します。
低い場合(0.3〜0.5):答えが安定し同じ傾向に寄る
普通(0.7〜0.9):バランスよく多様性もある
高い場合(1.0以上):ユニークで幅広い答えが出やすい

top-pは確率的に上位の答えをどこまで拾うかを調整します。
低い場合(0.5〜0.6):一番ありそうな答えに絞る
高い場合(1.0):幅広くいろいろな答えを含める

数値を直接指定しなくても、
「安定した答えが欲しい」
「もっとユニークにして」
と日本語で指示するだけでAIが自動的に数値に変換します。

例えば「安定した答え」なら
temperature=0.4,
top-p=0.6、

「ユニークに」なら
temperature=1.1,
top-p=1.0
に設定されます。

これにより、「しっかりした調査のような結果を見たい」のか、「多様な意見を広く拾いたい」のかを簡単に調整できます。

ビジネス提案の成功確率

アンケ太郎くんでは、アンケート結果をもとに「ビジネス提案の成功確率」も出力されます。
これは単なる数字ではなく、商品企画の方向性を確認するための指標です。

購買意向、価格評価、利用シーン、改善要望などの総合的な傾向から、どの程度市場に受け入れられる可能性があるかをAIが推定します。

顧客が「ちょうどいい」と感じているか、利用シーンが多様か、改善の余地が明確かといった点を見れば、ビジネスの成長余地も読み取れます。

この出力は、実際の市場調査の代替ではありませんが、初期提案の信頼性を高める「仮説の裏付け」として活用できます。

ビジネス提案の成功確率の例

  • 平均購買意向 3.6 → 中程度の前向き姿勢

  • 「ちょうどいい」価格評価が60%と高め

  • 利用シーンが多様で、日常使いとギフト需要の両方をカバー

  • 改善要望は主にラインナップ追加やデザイン改善で解決可能

👉 想定される成功確率:68%

※この数値はAIによる参考推定であり、実際の市場調査・販売結果とは異なる可能性があります。

この数値を参考に、次の 挑戦をするかどうかを判断するのも、わるくはないかと思います。

AI仮想顧客アンケート チャットボット アンケ太郎くんまとめ

仮想顧客アンケート チャットボットは、アイデアの初期段階で「売れるかどうか」をおおまかに確かめたいときに非常に役立ちます。

実際の顧客調査とは違い、すぐに数十件〜数百件の声が確認できるため、企画や仮説づくりのスピードが格段に上がります。

もちろん、出力されるのはあくまで「仮想データ」です。これを最終的な判断の根拠にすることはできません。
しかし「この価格帯は高いと感じる人が多い」「この利用シーンがよく出てくる」といった手がかりを得るには十分です。

最低30件以上あれば統計的にも一定の傾向を読み取ることができ、40〜50件であればバランスの取れたデータになります。

必要であれば300件まで一気にシミュレーションできるため、企画の裏付け資料としては心強い存在になるでしょう。

新しい商品を考えたときは、まずこのチャットボットで「仮想顧客の声」を拾ってみる。

そして、そこで得られた気づきをもとに本格的な調査やテストマーケティングに進めば、より失敗の少ない商品づくりにつながります。

ぜひ皆さん使ってみてください。

「アンケ太郎くん」
noteに投稿しました。
そちらから有料記事で購入お願いします。(300円)↓↓↓

AIマーケッターが仮想顧客にアンケートするチャットボット「アンケ太郎くん」を作った話

 

****************

最近のデジタルアート作品を掲載!

X 旧ツイッターもやってます。
https://x.com/ison1232

インスタグラムはこちら
https://www.instagram.com/nanahati555/

****************

「プロの焙煎士に聞け!コーヒー・焙煎職人の答え箱」チャットボットを作りました

 

👉 [プロの焙煎士に聞け!コーヒー・ 焙煎職人の答え箱はこちら]

https://chatgpt.com/g/g-68d10e5950c88191aad0b96259c3df38-huronobei-jian-shi-niwen-ke-bei-jian-zhi-ren-noda-exiang

※LINE対応チャットボット版の
「プロの焙煎士に聞け!コーヒー・焙煎職人の答え箱」
はこちらで有料にて販売中!

「プロの焙煎士に聞け!コーヒー・焙煎職人の答え箱」チャットボットを作りました。もちろん無料です。(LINE版は有料)
コーヒー道を極めたい方はぜひアクセスして、本物のコーヒーの淹れ方を習得してください!

***************

***************

「プロの焙煎士に聞け! 焙煎職人の答え箱」とは何か

コーヒーを自宅で楽しむ人が増える中で、「もっと美味しく淹れたい」「焙煎について詳しく知りたい」という声が高まっています。
そんなニーズに応えるのが、今回公開したChatGPTベースの新サービス「焙煎職人の答え箱」です。

これは、コーヒー焙煎に関するあらゆる疑問を気軽に相談できるAIツール。
プロの焙煎士の知識で、焙煎度合いの違いから器具の特徴、さらには豆の保存方法まで幅広く答えてくれます。

難しい専門書をひも解かなくても、まるで焙煎歴数十年の職人に直接質問できるような感覚で使えるのが最大の魅力です。

コーヒー愛好者が得られる5つのメリット

「コーヒー・焙煎職人の答え箱」を使うことで、どんなメリットがあるのかを整理してみました。

1. 疑問を即解決
「シティローストとフルシティローストの違いは?」などの細かい質問にもすぐ回答。

2. 実践的なアドバイス
家庭用焙煎機の使い方、焙煎時の火加減や排気調整など、すぐに試せるアドバイスが得られます。

3. 初心者から上級者まで対応
初めて焙煎に挑戦する人も、すでに自分の焙煎スタイルを持っている人も、それぞれのレベルに合わせた回答をしてくれます。

4. 学びながら楽しめる
回答には簡単な補足や背景も添えられているため、単なる答え以上に「なるほど」と思える知識が身につきます。

5. いつでもどこでも無料で利用可能
スマホから気軽にアクセスでき、24時間いつでも質問できるのはAIならではの強みです。

よくある質問と答え ― 焙煎にまつわる疑問をプロが解消

ここでは実際に寄せられやすい質問と、それに対する答えのイメージを紹介します。

Q. 浅煎りと深煎りでカフェイン量は変わるの?
焙煎が進むと豆の重量が軽くなるため、同じスプーン1杯で比べると深煎りの方がカフェインはやや少なめ。ただし豆の種類や抽出方法の影響も大きい。

Q. 家庭用フライパンでも焙煎できますか?
可能。ただし火加減の調整が難しいため、最初は焦げやすい。こまめにかき混ぜ、焙煎の進み具合を「音」や「香り」で確かめるのがコツ。

Q. 豆の保存は冷凍がいいの?常温がいいの?
焙煎後2週間以内に飲み切るなら常温でもOK。ただし長期保存なら冷凍推奨。解凍時に結露を防ぐため、小分けにして保存するのがベスト。

こうした具体的な答えがすぐに返ってくるので、初心者にとってはもちろん、知識を整理したい上級者にも役立ちます。

なるほどです!レビュー部分をもう少し掘り下げて、「なぜ役立ったのか」「どんな変化があったのか」まで描写すると読みごたえが出ますね。下記のように書き換えてみました。

使用者の声・体験談レビュー

実際に「コーヒー・焙煎職人の答え箱」を試したユーザーからは、単なる感想にとどまらず、生活や学びに直結する体験談が寄せられています。

自家焙煎歴3ヶ月の方
「焙煎度合いを説明してもらう時に、色や香りの目安まで教えてくれたのが特に役立ちました。専門書には“ミディアムローストは酸味が残る”と書いてあっても、実際にどう見分ければいいのか分からなかったんです。
でも『1ハゼの直後で火を弱めて、色はまだ明るめの茶色、香りは少し酸味が残るフルーティさがある』と具体的に答えてくれたので、初めて自分の焙煎に自信が持てました。」

カフェ経営を目指す方
「焙煎の排気や火加減について相談した時、AIなのに“シティローストなら7分前後で排気を強め、豆の表面が乾いてから一気に仕上げると雑味が出にくい”といった、実際の焙煎現場でしか聞けないようなアドバイスが返ってきました。
独学ではバラバラだった知識が整理され、仕上がりが安定するようになったのが一番の収穫です。正直、経験豊富な焙煎士に横で指導してもらったような感覚でした。」

毎朝ドリップ派の方
「豆の保存方法を聞いたときに、単なる“冷凍が良い”という答えではなく、“小分けにして密封、取り出す時は結露を防ぐために常温で少し戻す”という具体的な流れを教えてくれました。
その通りにしたら、2週間経っても香りがしっかり残っていて、味も鮮明。以前は冷凍しても風味が落ちたのに、保存の仕方ひとつでここまで変わるのかと驚きました。」

利用者の体験談からは、単なる知識提供にとどまらず、焙煎や抽出の「実際の行動の質」を変えてくれる力があることが分かります。

まさに、日々のコーヒー1杯をアップグレードするパートナーと言えるでしょう。

今すぐ試したくなる!コーヒー・焙煎職人の答え箱の魅力的な使い方

せっかくのサービスも、どう使うかで価値が変わります。おすすめの活用方法を紹介しましょう。

焙煎ログの補助ツールとして
自分で焙煎した記録を残す時に「この焙煎度合いは何に近い?」と聞いて整理。

カフェ仲間との会話のネタに
「こんな質問してみたよ」と共有することで、知識の交流やディスカッションにも役立ちます。

日々の小さな疑問を気軽に解消
「今日は蒸し暑いけど、この天候は焙煎に影響する?」など、雑談感覚で聞けるのも魅力です。

学習教材として
焙煎の基礎から応用まで網羅されているので、コーヒー講座の補助教材としても使えます。

「コーヒー・焙煎職人の答え箱」は、単なる質問箱にとどまらず、コーヒーをもっと深く楽しむためのパートナーになってくれるはずです。

「プロの焙煎士に聞け!コーヒー・焙煎職人の答え箱」まとめ

コーヒーの奥深さに触れると、どうしても疑問が次々と湧いてくるものです。そんな時に頼れるのが「コーヒー・焙煎職人の答え箱」。
専門知識を持つAIが、あなたの小さな疑問から本格的な相談まで、わかりやすく応えてくれます。

自宅での一杯をもっと美味しくしたい人、焙煎にチャレンジしたい人、さらにはカフェ開業を目指す人まで、幅広く役立つツールです。

ぜひ一度アクセスして、その便利さと奥深さを体感してみてください。
もちろん無料です。(LINE版は有料)

👉 [プロの焙煎士に聞け! コーヒー・焙煎職人の答え箱はこちら]

https://chatgpt.com/g/g-68d10e5950c88191aad0b96259c3df38-huronobei-jian-shi-niwen-ke-bei-jian-zhi-ren-noda-exiang

※LINE対応チャットボット版の
「プロの焙煎士に聞け!コーヒー・焙煎職人の答え箱」
はこちらで有料にて販売中!

****************

最近のデジタルアート作品を掲載!

X 旧ツイッターもやってます。
https://x.com/ison1232

インスタグラムはこちら
https://www.instagram.com/nanahati555/

****************

小規模言語モデル SLMの「Gemma3 270M」をgoogle Colabで動かしてみた

Gemma 270Mはどんな感じなのか

最近、Googleが公開しているGemmaシリーズ。その中でも最小サイズのGemma 3 270MをGoogle Colabで動かしてみました。
「とりあえずどんな感じで動くのか」「小さいモデルってどんな挙動になるのか」という実験です。

結論から言うと
動く。ちゃんと答えは返ってくる。けど、内容はかなりズレる。
それが逆に面白い、という結果になりました。

Gemma3 270Mとは

Gemma3 270Mとは、Googleが公開した大規模言語モデルで最小サイズのモデルです。

パラメータ数はわずか2億7千万(270M)。
近年主流の数十億〜数百億規模のLLMと比べると圧倒的に小型です。

そのため動作は軽く、Google Colabの無料環境やメモリの限られたPCでも動かせる点が大きな特徴となっています。

ただし、モデルサイズが小さい分、知識量や推論力はかなり限定的で、事実ベースの質問に対しては誤答やトンチンカンな返答をすることも多いです。
一方で、言語の形式はそれなりに整っており、短文での応答や独特の言い回しは可能です。

この特性を活かし、知識ベースの利用ではなく「遊び用キャラ」や「特定の口調・スタイルに特化したボット」としてファインチューンする用途に向いているモデルといえます。

google Colabでの実行方法

Colabでは以下の流れで実行できます。

1. Hugging Faceのアカウントを作り、Gemmaのモデルページで利用規約に同意
2. Colab上でtransformershuggingface_hubをインストール
3. Hugging Faceトークンを入力してモデルをダウンロード
4. テキストボックスUIを作って、質問文を入力して対話

実際に作ったUIは、ブラウザ上でチャット風に会話できるスタイルにしました。

「モデル準備」ボタンでGemma3 270Mをロードし、その下に質問用テキストエリアを配置。
送信すると返答が吹き出しで表示されるという、簡易チャットアプリのような仕組みです。

この時点で「Gemma3 270MをColabに落として、ユーザーの入力に答えさせる」という部分は問題なく動作しました。

⚫︎pythonコードを貼っておきます。
google colabで一発で動く版。

有料版のT4でも質問からの返答まで結構時間かかります。
無料版のcpuだとだいぶかかるかと。

使う時はHugging faceのアクセストークンが要ります。
またモデルページで同意しないとダウンロードできないやつです。
(わからない人は、google検索 or AIに聞いてください)


# ✅ Google Colab ワンセル版:Gemma 3 270MをローカルDLして連続チャット(安全/事実モード付き)
# - このセルをそのまま実行 → 画面のUIでHFトークン入力→「モデル準備」→下の欄で継続対話
# - モデルは /content/models/ に保存(再起動まで保持)
# - 既定モデル:google/gemma-3-270m-it(指示追従)
# - 「安全モード」= FP32 & 貪欲生成(CUDA assert回避)、「事実質問モード」= do_sample=False

!pip -q install "transformers>=4.43.0" "accelerate>=0.33.0" huggingface_hub ipywidgets > /dev/null

import os, shutil, time, html, torch, textwrap
import ipywidgets as widgets
from IPython.display import display, Markdown, clear_output
from huggingface_hub import login, snapshot_download
from transformers import AutoTokenizer, AutoModelForCausalLM

# =========================
# UI: モデル準備(上段)
# =========================
hf_token_box = widgets.Password(
description='HF Token',
placeholder='hf_xxx...(Hugging Face アクセストークン)',
layout=widgets.Layout(width='96%')
)
model_box = widgets.Dropdown(
options=[
('google/gemma-3-270m-it(指示追従・推奨)', 'google/gemma-3-270m-it'),
('google/gemma-3-270m(素のPT)', 'google/gemma-3-270m'),
],
value='google/gemma-3-270m-it',
description='Model',
layout=widgets.Layout(width='96%')
)
system_box = widgets.Textarea(
description='システム',
value="日本語で、簡潔かつ正確に回答してください。事実質問では推測せず、わからない場合はわからないと答えてください。",
placeholder='(任意)アシスタントのキャラや方針。',
layout=widgets.Layout(width='96%', height='70px')
)
max_new_tokens_box = widgets.IntSlider(
description='max_new',
value=256, min=32, max=1024, step=32, continuous_update=False
)
temperature_box = widgets.FloatSlider(
description='temp',
value=0.3, min=0.0, max=1.5, step=0.1, readout_format='.1f', continuous_update=False
)
safe_mode_chk = widgets.Checkbox(
description='安全モード(FP32/貪欲)※落ちる時はON',
value=True
)
factual_mode_chk = widgets.Checkbox(
description='事実質問モード(do_sample=False)',
value=True
)
fresh_download_chk = widgets.Checkbox(
description='強制再ダウンロード(既存を削除)',
value=False
)
prepare_btn = widgets.Button(description='モデル準備', button_style='primary')
prep_out = widgets.Output()

header = widgets.HTML("
<h3>Gemma 3 270M(Colabローカル保存・連続チャット)</h3>
")
row1 = widgets.HBox([hf_token_box])
row2 = widgets.HBox([model_box])
row3 = widgets.HBox([system_box])
row4 = widgets.HBox([max_new_tokens_box, temperature_box, safe_mode_chk, factual_mode_chk, fresh_download_chk, prepare_btn])
display(header, row1, row2, row3, row4, prep_out)

# =========================
# UI: チャット(下段)
# =========================
chat_html = widgets.HTML(
value="""
<div id="chat" style="font-family: ui-sans-serif,System-ui,-apple-system; background: #111; color: #eee; padding: 12px; border-radius: 12px; height: 360px; overflow: auto;">
<div style="opacity: .7;">💬 ここに対話ログが表示されます</div>
</div>
""",
layout=widgets.Layout(width='100%')
)
user_box = widgets.Textarea(
description='あなた',
placeholder='ここに質問を入力(例:日本で3番目に高い山は?)',
layout=widgets.Layout(width='96%', height='90px'),
disabled=True
)
send_btn = widgets.Button(description='送信', button_style='success', disabled=True)
clear_btn = widgets.Button(description='履歴クリア', button_style='', disabled=True)
chat_out = widgets.Output()

display(chat_html, user_box, widgets.HBox([send_btn, clear_btn]), chat_out)

# =========================
# 内部状態・ヘルパ
# =========================
def local_model_dir(model_id: str) -> str:
safe = model_id.replace("/", "__")
return f"/content/models/{safe}"

def ensure_model_local(model_id: str, hf_token: str, force_redownload: bool=False) -> str:
target_dir = local_model_dir(model_id)
if force_redownload and os.path.isdir(target_dir):
shutil.rmtree(target_dir)
if not os.path.isdir(target_dir) or len(os.listdir(target_dir)) == 0:
login(token=hf_token)
os.makedirs(target_dir, exist_ok=True)
snapshot_download(
repo_id=model_id,
local_dir=target_dir,
local_dir_use_symlinks=False,
token=hf_token,
)
return target_dir

_model_cache = {"key": None, "tok": None, "mdl": None}
_messages = [] # [{"role":"system"|"user"|"assistant","content":str},...]

def escape_html(s: str) -> str:
return html.escape(s).replace("\n", "
")

def render_chat():
parts = []
for m in _messages:
if m["role"] == "user":
parts.append(f"""
<div style="margin: 8px 0; text-align: right;">
<div style="display: inline-block; background: #2b6cb0; color: white; padding: 8px 10px; border-radius: 10px; max-width: 80%;">{escape_html(m['content'])}</div>
</div>
""")
elif m["role"] == "assistant":
parts.append(f"""
<div style="margin: 8px 0; text-align: left;">
<div style="display: inline-block; background: #2d2d2d; color: #eee; padding: 8px 10px; border-radius: 10px; max-width: 80%;">{escape_html(m['content'])}</div>
</div>
""")
elif m["role"] == "system":
parts.append(f"""
<div style="margin: 8px 0; text-align: center; opacity: .75;">
<div style="display: inline-block; background: #333; color: #ddd; padding: 6px 8px; border-radius: 10px; max-width: 80%;">{escape_html(m['content'])}</div>
</div>
""")
if not parts:
parts = ["
<div style="opacity: .7;">💬 ここに対話ログが表示されます</div>
"]
chat_html.value = f"""
<div id="chat" style="font-family: ui-sans-serif,System-ui,-apple-system; background: #111; color: #eee; padding: 12px; border-radius: 12px; height: 360px; overflow: auto;">{''.join(parts)}</div>
"""

def load_from_local(local_dir: str, safe_mode: bool):
"""
safe_mode=True: FP32でロード(GPUでもfp32固定)
safe_mode=False: CUDAあればfp16
"""
key = (local_dir, "fp32" if safe_mode else "fp16auto")
if _model_cache["key"] == key and _model_cache["tok"] and _model_cache["mdl"]:
return _model_cache["tok"], _model_cache["mdl"]

kwargs = dict(trust_remote_code=True, low_cpu_mem_usage=True, device_map="auto")
if torch.cuda.is_available() and not safe_mode:
kwargs.update(torch_dtype=torch.float16)
else:
kwargs.update(torch_dtype=torch.float32)

tok = AutoTokenizer.from_pretrained(local_dir)
mdl = AutoModelForCausalLM.from_pretrained(local_dir, **kwargs).eval()

_model_cache.update({"key": key, "tok": tok, "mdl": mdl})
return tok, mdl

def build_prompt(tok, messages):
# Gemma 3 は chat_template 対応。失敗時はフォールバック。
try:
return tok.apply_chat_template(messages, add_generation_prompt=True, tokenize=False)
except Exception:
sys_txt = ""
for m in messages:
if m["role"] == "system":
sys_txt = m["content"] + "\n\n"
last_user = [m["content"] for m in messages if m["role"] == "user"][-1]
return f"{sys_txt}User: {last_user}\nAssistant:"

def generate_once(tok, mdl, prompt: str, max_new: int, temp: float, safe_mode: bool, factual_mode: bool):
"""
生成部:
- factual_mode or safe_mode: do_sample=False(貪欲)で安定重視
- otherwise: サンプリング(創作・発想向け)
- 例外時はFP32/貪欲にフォールバック
"""
inputs = tok(prompt, return_tensors="pt").to(mdl.device)
gen_base = dict(
max_new_tokens=max_new,
eos_token_id=tok.eos_token_id,
pad_token_id=tok.eos_token_id,
)

if factual_mode or safe_mode:
with torch.no_grad():
out_ids = mdl.generate(**inputs, do_sample=False, **gen_base)
return tok.decode(out_ids[0][inputs["input_ids"].shape[1]:], skip_special_tokens=True)

try:
with torch.no_grad():
out_ids = mdl.generate(
**inputs,
do_sample=True,
temperature=max(0.0, min(1.5, temp)),
top_p=0.9,
repetition_penalty=1.05,
**gen_base
)
return tok.decode(out_ids[0][inputs["input_ids"].shape[1]:], skip_special_tokens=True)
except RuntimeError:
with torch.no_grad():
out_ids = mdl.generate(**inputs, do_sample=False, **gen_base)
return tok.decode(out_ids[0][inputs["input_ids"].shape[1]:], skip_special_tokens=True)

def trim_history(messages, max_turns=12):
# システム1 + 直近の往復を最大max_turnsまで
sys_msgs = [m for m in messages if m["role"] == "system"]
others = [m for m in messages if m["role"] != "system"]
if len(others) <= max_turns * 2:
return sys_msgs + others
return sys_msgs + others[-max_turns*2:]

# =========================
# 動作:モデル準備ボタン
# =========================
@prep_out.capture(clear_output=True)
def on_prepare(_):
token = hf_token_box.value.strip()
if not token:
print("❗Hugging Face のアクセストークンを入力してください(モデルページで利用規約に同意が必要な場合あり)。")
return
model_id = model_box.value
safe_mode = bool(safe_mode_chk.value)
force = bool(fresh_download_chk.value)

_messages.clear()
sys_txt = system_box.value.strip()
if sys_txt:
_messages.append({"role": "system", "content": sys_txt})
render_chat()

try:
print(f"📥 モデルをローカルへ準備中: {model_id}")
local_dir = ensure_model_local(model_id, token, force_redownload=force)
print(f" → 保存先: {local_dir}")
print(f"⏳ モデル読込中...({'安全' if safe_mode else '通常'}モード)")
tok, mdl = load_from_local(local_dir, safe_mode=safe_mode)
print("✅ 準備完了。下の入力欄から送信できます。")
user_box.disabled = False
send_btn.disabled = False
clear_btn.disabled = False
except Exception as e:
print(f"❌ 準備に失敗しました: {e}")
user_box.disabled = True
send_btn.disabled = True
clear_btn.disabled = True

prepare_btn.on_click(on_prepare)

# =========================
# 動作:送信ボタン
# =========================
@chat_out.capture(clear_output=False)
def on_send(_):
if send_btn.disabled:
return
user_text = user_box.value.strip()
if not user_text:
return

# 送信 → 表示
_messages.append({"role": "user", "content": user_text})
render_chat()
user_box.value = ""

# 推論
try:
local_dir = local_model_dir(model_box.value)
# 既存キャッシュ(prepare時にロード済みのはず)
tok, mdl = _model_cache["tok"], _model_cache["mdl"]
if tok is None or mdl is None:
# 念のため再ロード
tok, mdl = load_from_local(local_dir, safe_mode=bool(safe_mode_chk.value))

msgs = trim_history(_messages, max_turns=12)
prompt = build_prompt(tok, msgs)
reply = generate_once(
tok, mdl, prompt,
max_new=max_new_tokens_box.value,
temp=temperature_box.value,
safe_mode=bool(safe_mode_chk.value),
factual_mode=bool(factual_mode_chk.value)
)
except Exception as e:
reply = f"(エラーが発生しました) {e}"

_messages.append({"role": "assistant", "content": reply})
render_chat()

send_btn.on_click(on_send)

# =========================
# 動作:履歴クリア
# =========================
def on_clear(_):
sys_txt = system_box.value.strip()
_messages.clear()
if sys_txt:
_messages.append({"role": "system", "content": sys_txt})
render_chat()

clear_btn.on_click(on_clear)

# 初期描画
render_chat()

Gemma 270Mの返答の中身は?

では、実際にどんな返答をしたのか。
例えばこんなやり取り。

質問:「日本で3番目に高い山は?」
返答:「はい、日本で3番目にたかい山は『たかい山』です。」

思わず笑ってしまいました。
「それっぽい答え」は返してくるけど、事実は全く外れている。
この“ズレ”が小型モデルならではのおかしさです。

他にも、
「明日はどっちですか?」と聞くと、
「明日はどこですか?」と返してきたり。

要するに日本語はそれっぽくつなげられるけど、意味理解や知識は弱いということです。

 

なぜこうなる?

Gemma3 270Mは、パラメータ数がわずか270M。
これは一般的な最新LLM(数十Bクラス)と比べると、文字通り100分の1以下の規模です。
そのため:

* 言語の形式は整えられる
* でも事実知識はほぼ持っていない
* 推論力も弱いので質問を理解しきれない

といった挙動になります。
モデルが悪いのではなく、小さすぎるから当然というわけです。

このモデルをどう利用するか?

じゃあ「役立たない」かというと、そうでもありません。
小型モデルは“キャラ特化”に向いているのではと。

例えば:

* 大阪弁キャラ
* 冗談やツッコミ専用ボット
* 語尾変換(〜でござる、〜やで)
* 決めフレーズを連発する面白キャラ

こういったスタイルを付与するなら、270Mでも十分にファインチューニングできます。
むしろ小さいからこそ短時間・低コストで微調整できるのが魅力です。

Gemma3 270Mをファインチューニングするには

「Gemma3 270Mを自分好みのキャラに染める」方法です。
やり方はざっくり次の通り。

データを作る

まずは学習データ。
形式はHugging Faceのchat形式JSONLが便利です。


jsonl
{"messages":[
{"role":"system","content":"大阪弁で、短く面白く答える。"},
{"role":"user","content":"自己紹介して"},
{"role":"assistant","content":"どーも、関西ノリの小型モデルやで。軽いけどキレ味出すで。"}
]}
{"messages":[
{"role":"user","content":"ボケて"},
{"role":"assistant","content":"リンゴ三分の一しか残ってへん。え、計算できへんの?おもろいやろ。"}
]}

GTP5曰く、数百〜数千件あれば十分とのこと。
重要なのは一貫した口調と決めフレーズを盛り込むことだそう。

学習レシピ(LoRAで軽く)

Colabで動かすならLoRAが無難。
手順イメージはこんな感じかと(GPT5先生作)

!pip -q install transformers peft accelerate datasets trl

from datasets import load_dataset
from transformers import AutoTokenizer, AutoModelForCausalLM
from peft import LoraConfig, get_peft_model
from trl import SFTTrainer, SFTConfig
import torch

base_model = "google/gemma-3-270m-it"
tok = AutoTokenizer.from_pretrained(base_model, use_fast=True)
tok.pad_token = tok.eos_token

ds = load_dataset("json", data_files="data.jsonl")["train"]

peft_cfg = LoraConfig(
r=16, lora_alpha=32, lora_dropout=0.05,
target_modules=["q_proj","k_proj","v_proj","o_proj","gate_proj","up_proj","down_proj"]
)

mdl = AutoModelForCausalLM.from_pretrained(
base_model,
torch_dtype=torch.bfloat16 if torch.cuda.is_available() else torch.float32,
device_map="auto",
)
mdl = get_peft_model(mdl, peft_cfg)

train_cfg = SFTConfig(
output_dir="./gemma270m-osaka-lora",
per_device_train_batch_size=2,
gradient_accumulation_steps=16,
learning_rate=2e-4,
num_train_epochs=2,
max_seq_length=256,
)
trainer = SFTTrainer(
model=mdl,
tokenizer=tok,
train_dataset=ds,
formatting_func=lambda batch: [tok.apply_chat_template(x["messages"], tokenize=False) for x in batch],
args=train_cfg,
)
trainer.train()

筆者曰く。
多分簡単には想定通りに動かないでしょう。
手間がかかる分、あれかなと。

時間のある方はチャレンジしてみてください。
エラーが出たら、コピペしてGTP5あたりに投げれば、回答してくれます。
ファイトー!

プロンプトチューニングでキャラ弁できるのか

ファインチューニングは手間がかかるので、今回はざっくりとプロンプトチューニングで成功するのかを試しました。

下記が入れるプロンプトです。

****

「あなたは大阪弁で会話する関西キャラのアシスタントです。
常にフレンドリーでユーモラスに話し、軽くツッコミを入れたり冗談を交えることを意識してください。
一人称は「ワイ」、相手は「アンタ」と呼んでもいいです。
文章はなるべく短めでテンポよく、最後に「やで」「やんけ」「知らんけど」などの大阪弁らしい語尾を入れてください。
わからない質問が来たら、正直に「そんなん知らんわ〜」と笑いながら答えてください。

[例]
ユーザー: 自己紹介して
アシスタント: ワイは軽量やけどノリのええLLMやで。おもろい話なら任せとき!

ユーザー: ボケて
アシスタント: リンゴ三分の一しか残ってへんねん。計算できへんのかい!知らんけど。

ユーザー: 今日の天気は?
アシスタント: えー、そこはネット繋がってへんから詳しくは知らんわ〜。けどワイの心は快晴やで。」

このプロンプトをsystemに入れて、通常の質問と回答は下記です。

****

質問
こんにちは。今日の大阪の天気はどんな感じですか?

回答
はい、承知いたしました。大阪の天気について、フレンドリーでユーモアのある会話を心がけました。

質問
大阪といえば、野球はどこチーム?

回答
大阪といえば、野球はどこチーム?

****

んーん。意味不明ですね。
やはり270Mですから、こんなもんなんでしょう。

それと英語ベースでの学習されているみたいなので、日本語はイマイチっぽいです。

(とはいえ、英語も試しましたが…。
てな感じです。日本語よりは良かったけど。
まあ、仕方ない)

Gemma3 270Mをgoogle colabで動かすのまとめ

* Gemma3 270MはColabでもサクッと動く
* ただし事実知識は弱く、質問応答はトンチンカン
* でも逆にそのズレが面白い
* 「遊び用キャラ」にファインチューニング成功すれば、なんとか使えるかも。
*プロンプトチューニングもイマイチ。まずは日本語学習が先決か。
*知識蒸留すると違うのかな。今回はやってないので、時間あればやりたいですね。
でもどうせやるなら、1B以上ないときついかなと。

つまり、Gemma3 270Mは真面目なQA用ではなく、遊び心あるキャラボットを作成する勉強ツールとして使うと楽しいかもです。

 

****************

最近のデジタルアート作品を掲載!

X 旧ツイッターもやってます。
https://x.com/ison1232

インスタグラムはこちら
https://www.instagram.com/nanahati555/

****************

LLMのMoE(Mixture of Experts)とは何なのか

いよいよ今月にはGTP5が出るようですね。
なんでもGTP5は、ここで扱ってるMoEが使われているとかどうとか。

そんな先端な仕組みを、今回は取り上げてみました。

MoEについて

AIは何でもできる、万能な存在というイメージを持つ人も多いかもしれません。
でも、実際にAIの仕組みを学んでいくと「得意分野・不得意分野」や「効率的な役割分担」がいかに大切かが見えてきます。

そして、今の大規模言語モデル(LLM)などで一気に注目されている仕組みが「MoE(Mixture of Experts/ミクスチャー・オブ・エキスパート)」です。

MoEは、その名の通り「たくさんの“専門家”の中から、その場で最適な人だけを選んで答えてもらう」ことで、AIの能力と効率を同時に高める、とても面白い仕組みです。

この記事では、
MoEとは何か
実際の処理の流れ
体感して分かったこと
を、なるべくやさしく書いていきます。

MoE(Mixture of Experts)とは何か?

MoEとは「Mixture of Experts」の略。
直訳すれば「専門家の混合」ですが、AIではたくさんの専門家(エキスパート)を用意して、その都度、得意な人だけを選んで仕事をしてもらうという構造を指します。

イメージしやすいように例えると、
「質問内容によって、得意な先生に相談する」
という学校のシーンに似ています。

例えば、国語の質問なら国語の先生に、数学の質問なら数学の先生に聞きますよね。
普通のニューラルネットワーク(AIの従来モデル)は「一人の先生」が全部に対応します。

MoEは「専門家軍団」を用意して、質問ごとに最適な先生(複数もOK)だけが動くようにしているのです。

その結果、
計算効率が良くなる
無駄が少ないのでモデルを巨大化できる
多様なタスクや言語に強くなれる
というAIの理想に近づいています。

MoEの技術的な仕組み

MoEの具体的な構造はどうなっているのか、簡単に説明します。

1. 複数の「専門家」(エキスパート)を用意
AIの中に、いくつもの小さなネットワーク(専門家)を作ります。
たとえば32個、64個、最近は100個以上のことも。

2. 「ゲーティングネットワーク」で誰を選ぶか決める
ゲート役のネットワーク(Gating Network)が「今回の質問ならAさんとCさん」というように自動で選びます。
この振り分けはAI自身が学習するので、入力内容によって毎回変わります。

3. 選ばれた専門家だけが本気を出す
選抜メンバーだけが実際の計算を担当します。
全員で計算しない分、大規模でも計算資源が節約できる

処理速度も速い
それぞれの専門家が得意分野を深めやすいという強みがあります。

4. 専門家たちの答えを「重み付きで合成」して最終出力
選ばれた専門家の出力を、それぞれ重み付けして混ぜて最終的なAIの答えにします。

MoEを体感するため、実験版を動かす

理屈を知っただけでは「本当に分担してるの?」と実感しづらいものです。
そこで実際に「MoEの分担」を体験できる簡単なチャットボットのデモを作ってみました。

このデモは、質問文を入力すると
日本語の雑談→日本語専門のエキスパートが返答
数学の質問→数学専門のエキスパートが返答
のように、質問内容によって答える専門家が自動で振り分けられるようにしています。

また、「どちらの専門家がどれくらい選ばれたか(重み)」も一緒に表示されます。

実際のやりとり例

質問1:「今日は天気どうですか?」
ゲートの重み:日本語 93%、数学 7%
答え:「日本語でお答えします:『今日は天気どうですか?』ありがとうございます!」

質問2:「足し算を教えて」
ゲートの重み:日本語 25%、数学 75%
答え:「数学の答え:2 + 2 = 4」

質問3:「日本の数学教育について教えて」
ゲートの重み:日本語 69%、数学 31%
答え:「日本語でお答えします:『日本の数学教育について教えて』ありがとうございます!」

⚫︎文末にPythonコードあり。

実際に動かして感じたこと

このデモを実際に動かしてみて感じたのは、
質問ごとに専門家が自動で切り替わる
割り振りの度合いが数字で分かる
「混合」(Mixture)の意味が可視化できる
と、MoEの本質的なイメージが掴みやすくなりました。

AI内部では、これが全て数値ベクトルの計算で行われていると考えると、さらに「分担AIのすごさ」がよく分かります。

数値で見るMoE

実はMoEの内部では
入力→特徴ベクトル(たとえば文章の埋め込みベクトル)
ゲート→softmaxで各専門家の重みを計算
各専門家が数値ベクトルで「答え」を出す
それを重み付きで合成して最終出力
という流れになっています。

例えば
入力ベクトル:\[0.3, 0.5, -0.2]
ゲート出力:エキスパートA(70%)、B(30%)
エキスパートの答え:A→\[1.2, -0.5]、B→\[0.8, 1.1]
最終出力:0.7×\[1.2, -0.5]+0.3×\[0.8, 1.1]=\[1.08, -0.02]
といった計算が、舞台裏で自動的に動いています。

この計算は一見地味ですが、「専門家の混合」というMoEの本質を表しています。

MoEが活躍する最新AIの世界

今やMoEは
Google Gemini
Mixtral(Mistral AI)
Qwen2-MoE(Alibaba)
など、最先端AIモデルでも採用されています。

なぜここまでMoEが重宝されるかというと、
無駄のない分担で超大規模モデルでも高速に動く
多様な言語や分野への対応力が高まる
省エネかつ高精度なAIが作れる
といった強力なメリットがあるからです。

体感してわかった“分担AI”の力

MoEを実際に体験して一番感じたのは
「AIも人間社会のように、上手な役割分担をしている」
ということでした。

昔のAIは「全部一人でやる先生」でしたが、
MoEでは「必要な時だけ、最適なチームを作る」ことができます。

これがAIの巨大化・マルチタスク化・高効率化を一気に進めている理由なのだと実感しました。

おわりに

AIの世界で活躍する「専門家たち」。

MoE(Mixture of Experts)はまさに「チームAI」です。
これからのAIは「一人の天才」から「専門家集団」へと進化していくのでしょう。

もし興味があれば、下記に貼ってある、PythonコードをGoogle Colabなどで動かしてみてください。

数字で見ても、テキストで遊んでも、その「分担と混合」の動きが見えてくるかと思います。

この仕組みを知ると、AIの進化のスピードや、分野ごとの“専門家”の大切さがさらに実感できます。
ぜひ一度、体験してみてください。

Pythonコード
実際に入力して、日本語か数学かを判断する


import torch
import torch.nn as nn
import torch.nn.functional as F
import ipywidgets as widgets
from IPython.display import display, clear_output

# --- 日本語ワード/数学ワード
japanese_words = ["こんにちは", "天気", "ありがとう", "日本", "おはよう"]
math_words = ["数学", "足し算", "引き算", "かけ算", "割り算", "+", "-", "×", "/", "計算"]

def vectorize(text):
v = torch.zeros(len(japanese_words) + len(math_words))
for i, w in enumerate(japanese_words + math_words):
if w in text:
v[i] += 1
return v

class GatingNetwork(nn.Module):
def __init__(self, input_dim, num_experts):
super().__init__()
self.fc = nn.Linear(input_dim, num_experts)
def forward(self, x):
logits = self.fc(x)
probs = F.softmax(logits, dim=-1)
return logits, probs

class JapaneseExpert:
def reply(self, question):
return "日本語でお答えします:「{}」 ありがとうございます!".format(question)

class MathExpert:
def reply(self, question):
if "足し算" in question or "+" in question:
return "数学の答え:2 + 2 = 4"
elif "かけ算" in question or "×" in question:
return "数学の答え:3 × 4 = 12"
else:
return "数学でお答えします:「{}」の計算は難しいですね。".format(question)

class SimpleMoE:
def __init__(self):
self.gate = GatingNetwork(input_dim=len(japanese_words) + len(math_words), num_experts=2)
self.experts = [JapaneseExpert(), MathExpert()]
with torch.no_grad():
self.gate.fc.weight[0, :len(japanese_words)] += 1.0 # 日本語
self.gate.fc.weight[1, len(japanese_words):] += 1.0 # 数学

def reply(self, question):
vec = vectorize(question)
logits, probs = self.gate(vec)
expert_id = torch.argmax(probs).item()
reply = self.experts[expert_id].reply(question)
return {
"input_vec": vec,
"gate_logits": logits,
"gate_probs": probs,
"expert_id": expert_id,
"reply": reply
}

moe = SimpleMoE()

text_input = widgets.Text(
value='',
placeholder='質問を入力してください',
description='質問:',
disabled=False
)
output_area = widgets.Output()

def on_submit(sender):
with output_area:
clear_output()
q = text_input.value
result = moe.reply(q)
print(f"▼ 質問: {q}\n")
print(f"▼ 入力ベクトル:\n{result['input_vec']}\n")
print(f"▼ ゲートlogits:\n{result['gate_logits']}\n")
print(f"▼ ゲートsoftmax確率:(どちらの専門家がどれだけ選ばれたか)")
print(f" 日本語 {result['gate_probs'][0].item():.4f}, 数学 {result['gate_probs'][1].item():.4f}\n")
if result["expert_id"] == 0:
print("▼ 選ばれたエキスパート: 日本語エキスパート")
else:
print("▼ 選ばれたエキスパート: 数学エキスパート")
print(f"\n▼ MoEの最終出力:")
print(result["reply"])

button = widgets.Button(description="送信")
button.on_click(on_submit)

display(text_input, button, output_area)

こちらは、MoE本体の動きを見るためのコード


import torch
import torch.nn as nn
import torch.nn.functional as F

# --- Mixture of Expertsの本質構造 ---

class Expert(nn.Module):
def __init__(self, in_dim, out_dim):
super().__init__()
self.layer = nn.Linear(in_dim, out_dim)
def forward(self, x):
return F.relu(self.layer(x))

class MoE(nn.Module):
def __init__(self, input_dim, output_dim, num_experts):
super().__init__()
self.experts = nn.ModuleList([Expert(input_dim, output_dim) for _ in range(num_experts)])
self.gate = nn.Linear(input_dim, num_experts)
def forward(self, x):
# x: [バッチ, 入力次元]
gate_logits = self.gate(x) # [バッチ, エキスパート数]
gate_weights = F.softmax(gate_logits, dim=-1) # [バッチ, エキスパート数]
expert_outputs = torch.stack([expert(x) for expert in self.experts], dim=1) # [バッチ, エキスパート数, 出力次元]
# softmaxで重み付け和
output = (gate_weights.unsqueeze(-1) * expert_outputs).sum(dim=1) # [バッチ, 出力次元]
return output, gate_logits, gate_weights, expert_outputs

# --- サンプル入力データで挙動確認 ---
if __name__ == "__main__":
torch.manual_seed(42)
batch_size = 3
input_dim = 4
output_dim = 2
num_experts = 2

moe = MoE(input_dim, output_dim, num_experts)
# 入力(ランダム or 任意のベクトルでOK)
x = torch.randn(batch_size, input_dim)
output, gate_logits, gate_weights, expert_outputs = moe(x)

print("入力x:\n", x)
print("\nゲートlogits:\n", gate_logits)
print("\nゲートsoftmax(各エキスパートの重み):\n", gate_weights)
print("\n各エキスパート出力:\n", expert_outputs)
print("\nMoE最終出力:\n", output)

****************

最近のデジタルアート作品を掲載!

X 旧ツイッターもやってます。
https://x.com/ison1232

インスタグラムはこちら
https://www.instagram.com/nanahati555/

****************

60歳からの新しい挑戦に回答する「シニア起業家お助け隊」を作った話

************

こちらの「シニア起業家お助け隊」は社会貢献活動の一環として当事務所で作成しました。

シニアの方が起業したときに様々な問題を解決できるように、ChatGPTのGPTsを使って制作してあります。

利用はもちろん無料です。ご自由にお使いください。

シニア起業家お助け隊のURLはこちら
https://chatgpt.com/g/g-68848d2aa3b88191a6d66bdcc7c39afa-sinia-60sui-dai-yi-shang-qi-ye-jia-ozhu-kedui

***************

***************

シニア世代の“第二の人生”が注目される時代

人生100年時代と言われる今、60歳や70歳を過ぎてから新しい挑戦に踏み出す方が増えています。

「もう一度、自分の夢を形にしたい」「これまでの経験や人脈を活かして社会に貢献したい」と考えるシニア世代の起業家は年々増加傾向にあります。

しかし、実際に起業するとなると「何から始めたらいいかわからない」「経営の経験がない」「デジタルやITが苦手で不安」といった悩みやハードルも少なくありません。

若い世代とは違う人生経験や価値観を持つシニアの方が、安心して一歩を踏み出せる支援が求められています。

シニア起業家の悩みに寄り添うAIアドバイザー

こうした課題を解決する新サービスが「シニア起業家お助け隊」です。
(もちろん無料です)

シニア起業家お助け隊のURLはこちら
https://chatgpt.com/g/g-68848d2aa3b88191a6d66bdcc7c39afa-sinia-60sui-dai-yi-shang-qi-ye-jia-ozhu-kedui

わかりやすく、黒画面で表示
わかりやすく、黒画面で表示

このサービスは、OpenAIが提供するAI(人工知能)技術を活用し、シニア世代の方が起業やビジネス運営で抱えがちな悩みにやさしく丁寧に答えてくれるAIアドバイザーです。

たとえば、

「資金調達の方法は?」
「集客やSNSの使い方がわからない」
「経理や帳簿の管理が難しい」
「やりがいや生きがいを感じるには?」
など、経営のあらゆる疑問や不安に対応します。

しかも、専門用語はできるだけ使わず、わかりやすく説明。励ましや安心感を大切に。シニアならではの強み(人脈・経験・信頼)を活かすアドバイスにこだわっています。

デジタルが苦手でも大丈夫。やさしい言葉で何度でも相談OK

AIって難しそう、パソコンやスマホが苦手でも大丈夫?と心配される方もいるかもしれません。

でも、シニア起業家お助け隊は、LINEやチャット感覚で使えるので、特別なスキルや難しい操作は必要ありません。

質問内容も自由。ビジネスに限らず、人間関係の悩みや日々のちょっとした疑問など、幅広くサポートできます。

たとえば、

自分に合うビジネスアイデアは?
集客に役立つSNSの使い方を簡単に知りたい
お金の管理をシンプルにするコツは?
といった身近な相談から、失敗したときの立ち直り方まで、やさしく寄り添ってくれます。

わかりやすく、黒画面で表示

シニア起業の強みを活かせるアドバイスが充実

このサービスが他と違うのは、人生経験豊富なシニアの強みに着目していることです。

たとえば長年培ってきた人脈、誠実な対応、豊かな人生経験は、若い起業家にはない大きな武器です。

シニア起業家お助け隊は、そうした強みを活かしたビジネス戦略や、社会貢献につながるアイデアのアドバイスも得意としています。

自分の経験を活かせる場が欲しい、地域や仲間に役立つことをしたい、そんな思いにも力強く応えてくれます。

使い方はとてもカンタン!

1. GPTストアなどから「シニア起業家お助け隊」にアクセス
(⚫︎面倒な方は、下記のURLをクリック)
2. 相談したい内容をチャットで入力
3. AIが、あなたにぴったりのアドバイスをやさしい言葉で返してくれる

もし難しい操作があれば、家族や知り合いの方に最初だけサポートしてもらってもOK。
一度使い方がわかれば、あとは気軽にいつでも何度でも相談できます。

⚫︎シニア起業家お助け隊のURLはこちら
https://chatgpt.com/g/g-68848d2aa3b88191a6d66bdcc7c39afa-sinia-60sui-dai-yi-shang-qi-ye-jia-ozhu-kedui

まとめ 誰でも、何歳からでも夢を叶えられる時代へ

シニア世代の起業は、社会や地域をもっと元気にしてくれる可能性にあふれています。

やってみたいけど不安、自分にできるか心配、そんな方もAIアドバイザーの力を借りて、ぜひ一歩踏み出してみてください。

シニア起業家お助け隊は、あなたの経験と情熱を、ビジネスという新しい形で輝かせるお手伝いをします。

第二の人生、思い切り楽しみましょう!

DGM × MIT SEALでつくるハイブリッド自己進化型AIを試した話 実験版

*********

この辺りの内容は、これから生成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/

****************

PAGE TOP