AIうぉ--!(ai-wo-katsuyo-shitai !)

AIを上手く使ってみせたい!!自分なりに。

″Cola DLM″と″LeWorldModel”の将来の融合展望 🔭

あんまり、バランスが良くない組み合わせかもしれませんが。。。。


将来の融合展望(2026年5月時点からの予測)

Continuous Latent Diffusion Language Model(Cola DLM系)LeWorldModel(LeWM / JEPA系) の融合は、現在最も有望視されているUnified World Model(統一世界モデル)への道筋の一つです。両者は「連続的な潜在空間で高い抽象度を扱う」という哲学を共有しており、将来的に以下のような形で統合が進むと期待されています。

1. 短期的な融合方向(2026〜2027年)

  • 共有の連続潜在空間(Shared Latent Space)
    テキストはCola DLMのText VAEで、視覚・行動はLeWorldModelのJEPA Encoderで、それぞれを同じ次元の連続潜在表現にマッピング。
    → 一つの潜在空間上で、言語の意味分布(Diffusionでモデリング)と世界のダイナミクス(JEPAで予測)を共同学習

  • ハイブリッドアーキテクチャ

    • 潜在空間のグローバル事前分布:Block-causal DiT(Cola DLM風)で意味・因果構造をDiffusion/Flowで学習。
    • 動的予測:JEPA Predictorで行動条件付きの未来状態を予測(物理的一貫性確保)。
    • これにより、言語で計画を立て、潜在空間で物理シミュレーションを実行するエージェントが可能に。
  • マルチモーダル条件付け
    テキストプロンプトで世界モデルを条件付け(例:「赤いボールを投げて」→ 潜在空間で物理的に妥当な未来を予測し、テキストで説明)。

2. 中長期的なビジョン(2027〜2030年)

  • 真のUnified Multimodal World Model
    テキスト・画像・動画・音声・行動・3Dをすべて同じ連続潜在空間で扱うモデル。
    Cola DLMの「グローバル意味組織化 + 局所実現の分離」と、LeWMの「物理構造を内在化した予測」が融合し、抽象度を階層的に扱うシステムが生まれる。

  • 具身化AI(Embodied AI) / エージェントの基盤

    • 世界モデルが内部シミュレーターとして機能 → 実際に行動する前に何千もの仮想展開を潜在空間で高速シミュレーション(LeWMの48x高速計画の利点活用)。
    • 言語モデルが「考え」を生成し、世界モデルが「結果」を予測・検証する思考ループ(System 2的な推論)。
  • 階層的抽象化(Hierarchical Abstraction)
    低レベル:ピクセル・トークンレベルの詳細
    中レベル:物体・行動の因果
    高レベル:言語的な概念・目標
    これらを一貫した潜在空間で繋ぐ。

期待されるブレークスルー効果

  • 効率の劇的向上:非自己回帰生成 + 潜在空間予測で、現在のAR LLMより高速・省メモリ。
  • 物理的・因果的一貫性:幻覚低減、長期計画の正確性向上。
  • 汎用エージェント:ロボティクス、自動運転、科学発見、創造的タスクなどで「世界を理解しながら行動」可能。
  • スケーラビリティ:両手法ともスケーリング挙動が良いため、大規模化で emergent abilities(創発能力)が強く現れる可能性。

現在の研究の兆し

Cola DLMの論文自体が「連続モダリティへの自然な拡張」と「統一モデリング」を明言しており、LeWorldModelも潜在空間の物理構造エンコードを強調。両者の研究コミュニティは重なりつつあり、VLA(Vision-Language-Action)モデルMultimodal Diffusion + JEPAハイブリッドの実験が2026年に活発化しています。

要するに、「言語の意味を操るDiffusion」と「世界の物理を予測するJEPA」が同じ潜在空間で出会うことで、単なるチャットボットを超えた「世界を内的にモデル化する知能」への大きな一歩になると考えられています。


付録1


元のTransformer(2017年)からContinuous Latent Diffusion Language Model(2026年)までの技術発展の流れ

2017年:基盤の誕生
「Attention is All You Need」論文でTransformerが発表されました。
自注意機構(Attention)を中心としたEncoder-Decoder構造で、当初は主に機械翻訳向けでした。これが全ての始まりです。

2018〜2020年:Transformerの言語モデル化(ARの主流化)
GPT-1(2018)、BERT(2018)、GPT-2(2019)、GPT-3(2020)と急速に発展。
特にDecoder-onlyの自己回帰(Autoregressive: AR)Transformerが大規模事前学習に適していることがわかり、次トークン予測による生成が言語モデルの標準手法となりました。

2020〜2022年:Diffusionモデルの爆発的発展(画像分野)
DDPM(2020)でDiffusionモデルが注目され、Latent Diffusion Models(Stable Diffusionなど)で実用化。
特に2022年末に発表されたDiffusion Transformer (DiT) が重要で、従来のU-Netの代わりにTransformerをDiffusionのバックボーンとして使用し、高品質・高スケーラビリティを実現しました。

2022〜2024年:Diffusionをテキスト生成に応用する挑戦
画像で成功したDiffusion技術をテキストに適用する研究が活発化。
テキストの離散性を克服するため、Text VAE(または類似のAutoencoder)を使って離散テキストを連続的な潜在空間(Latent Space)に変換し、その空間上でDiffusionを行うアプローチが登場しました。
これが「Latent Diffusion for Language」の初期形です。

2024〜2025年:Diffusion Language Modelsの本格的研究
DiTを言語生成に最適化する動きが加速。
非自己回帰生成の可能性、Flow Matchingとの融合、潜在空間の安定化など、さまざまな実験が行われました。

2026年:Continuous Latent Diffusion Language Model(Cola DLMなど)の登場
現在の到達点の一つであるContinuous Latent Diffusion Language Modelが実現。
主な構成は以下の通りです: - Text VAEで離散テキストを安定した連続潜在空間に変換 - Block-causal DiTを使って潜在空間上でグローバルな意味分布をDiffusionで学習 - Conditional Decodingで潜在表現から自然言語テキストを生成

これにより、非自己回帰(Non-AR)でありながら、AR Transformerに匹敵する性能を目指す新しいパラダイムが確立されつつあります。


全体の大きな進化の方向性
- 離散トークン → 連続潜在空間への移行
- U-Net → Transformer(DiT)への置き換え
- 逐次生成(AR) → 並列・グローバル生成(Diffusion)
- トークンレベルモデリング → 意味レベルのモデリング

この流れは、Transformerの強力な注意機構を活かしつつ、Diffusionの滑らかさと柔軟性を取り入れる方向で進んでいます。


付録2


抽象度を上げる試みという点で、Continuous Latent Diffusion Language Model(Cola DLMなど)LeWorldModel(LeWM)には、かなり近い部分があります。

共通する核心:抽象度を高めた「連続的な潜在空間(Latent Space)」でのモデリング

両者とも、低レベル(離散・生データ)から高レベルの抽象表現へ移行し、そこをメインの学習・生成空間にするアプローチを取っています。

  • Continuous Latent Diffusion Language Model
    → 離散テキスト(トークン)を Text VAEで連続的な潜在空間に変換
    → その潜在空間上で DiT(Diffusion Transformer) を使ってグローバルな意味分布を学習
    → 非自己回帰的にテキストを生成
    目的: トークンレベルを超えた「意味レベルの抽象化」と滑らかな生成

  • LeWorldModel(LeWM, 2026)
    → 生のピクセル画像(raw pixels)を Encoder(ViTなど)でコンパクトな連続潜在表現(latent embedding)に変換
    → その潜在空間上で action-conditioned predictor(Transformerベース) を使って未来の状態を予測(JEPA: Joint Embedding Predictive Architecture)
    → 世界のダイナミクス(物理的・行動的な因果関係)を潜在空間で学習
    目的: ピクセルレベルを超えた「世界の抽象モデル」構築(世界モデル / World Model)

近い部分(共通点)

  1. 離散/生データ → 連続潜在空間への抽象化
    どちらも「低レベルデータを一旦連続的な意味空間に持ち上げて扱う」点が共通。
    これにより、スケーラビリティ・効率・グローバルな理解が向上しやすくなる。

  2. 潜在空間での予測・生成

    • Cola DLM:潜在空間で意味の事前分布(semantic prior)をDiffusionでモデリング
    • LeWorldModel:潜在空間で行動に応じた未来状態を予測(predictive modeling)
  3. Transformerの活用
    両方ともDiTやTransformerを潜在空間のモデリングに使っている。

  4. Post-Transformer / 次世代パラダイム
    AR Transformerの限界(逐次・トークン中心)を越え、より抽象的・効率的・物理的・計画的な知能を目指す流れに位置づけられる。

違う部分

  • 対象ドメイン: Cola DLMは言語生成中心、LeWorldModelは視覚・行動・物理世界のダイナミクス中心(ロボティクス・具身化AI向け)。
  • 手法: Diffusion vs JEPA(予測アーキテクチャ)。
  • 最終ゴール: Cola DLMはテキスト生成の新パラダイム、LeWorldModelはYann LeCunが提唱するWorld Modelの実装(エージェントが世界を理解・計画するための内部モデル)。

全体像としての関係

抽象度を上げるというトレンドの中で、言語側(Cola DLM系)視覚・具身化側(LeWorldModel / JEPA系)が並行して進んでおり、将来的にはマルチモーダル統一World Modelに融合していく可能性が高いです。

両方とも「連続的な潜在空間を賢く活用して、高い抽象レベルで世界をモデル化する」という同じ哲学を持っています。


コメント

これ、AI(Grok)に語ってもらったママです。基本。

一枚の画像を対象に、diffusionモデル(拡散モデル)を動かしてみた!!🎉🎉🎉(CPUのみで)

結果は、 ↑ この画の通り。

一枚の画像は、Wikipediaのマリオの画像を使った。64x64にクリップして。

en.wikipedia.org

やってみて、以下のことがわかった

  • なるほど、、、、ちゃんとノイズから画像が復元できるんだ。。。(1枚で学習しても)
  • 一枚での学習は、たぶん、本来のdiffusionモデルとはかなり違う動作(作用)にはなっているんだろうけど。。。

コードは、以下に示す。 なお、コードは、以下のGitHubに置きました。

github.com

import torch
import torch.nn as nn
import torch.optim as optim
from torch.utils.data import Dataset, DataLoader
import numpy as np
from PIL import Image
import matplotlib.pyplot as plt
from tqdm import tqdm
import math
import os

# ====================== 1. Single Image Dataset ======================
class SingleImageDataset(Dataset):
    """
    Dataset that loads a single image and returns it repeatedly.
    Used for over-fitting the model to one specific image.
    """
    def __init__(self, image_path, img_size=64, epoch_size=3000):
        self.image_path = image_path
        self.img_size = img_size
        self.epoch_size = epoch_size

        # Check if the file exists
        if not os.path.exists(image_path):
            raise FileNotFoundError(f"Image not found at {image_path}. Please place your image file correctly.")

        # Load and preprocess the image
        img = Image.open(image_path).convert('RGB') # Discard alpha channel
        img = img.resize((img_size, img_size), Image.BICUBIC) # Resize to 64x64

        # Normalize pixel values to range [-1, 1]
        img_np = (np.array(img).astype(np.float32) / 127.5) - 1.0
        # Convert to Tensor format (C, H, W)
        self.tensor = torch.from_numpy(img_np).permute(2, 0, 1)

    def __len__(self):
        # Return a large virtual size to define iterations per epoch
        return self.epoch_size

    def __getitem__(self, idx):
        # Always return the same image tensor regardless of index
        return self.tensor

# ====================== 2. Diffusion Utilities ======================
def linear_beta_schedule(timesteps, start=0.0001, end=0.02):
    return torch.linspace(start, end, timesteps)

class DDPM:
    def __init__(self, timesteps=1000, device='cuda' if torch.cuda.is_available() else 'cpu'):
        self.timesteps = timesteps
        self.device = device
        self.betas = linear_beta_schedule(timesteps).to(device)
        self.alphas = 1. - self.betas
        self.alpha_cumprod = torch.cumprod(self.alphas, dim=0)
        self.sqrt_alpha_cumprod = torch.sqrt(self.alpha_cumprod)
        self.sqrt_one_minus_alpha_cumprod = torch.sqrt(1. - self.alpha_cumprod)

    def add_noise(self, x0, t):
        """Forward process: Add noise to the original image based on timestep t"""
        noise = torch.randn_like(x0)
        sqrt_alpha = self.sqrt_alpha_cumprod[t].view(-1, 1, 1, 1)
        sqrt_one_minus = self.sqrt_one_minus_alpha_cumprod[t].view(-1, 1, 1, 1)
        return sqrt_alpha * x0 + sqrt_one_minus * noise, noise

    @torch.no_grad()
    def sample(self, model, batch_size=16, img_size=64):
        """Reverse process: Iteratively denoise pure noise to generate an image"""
        x = torch.randn(batch_size, 3, img_size, img_size).to(self.device)
        model.eval()
        for t in tqdm(reversed(range(self.timesteps)), desc="Sampling", leave=False):
            t_tensor = torch.full((batch_size,), t, dtype=torch.long, device=self.device)
            predicted_noise = model(x, t_tensor)
            
            alpha = self.alphas[t]
            alpha_cumprod = self.alpha_cumprod[t]
            beta = self.betas[t]
            
            noise = torch.randn_like(x) if t > 0 else torch.zeros_like(x)
            
            # DDPM Reverse step formula
            x = (1 / torch.sqrt(alpha)) * (x - ((beta / torch.sqrt(1 - alpha_cumprod)) * predicted_noise)) + torch.sqrt(beta) * noise
        
        model.train()
        # Restore range from [-1, 1] back to [0, 1] for visualization
        x = (x + 1.0) / 2.0
        return torch.clamp(x, 0, 1)

# ====================== 3. Simple U-Net Architecture ======================
class SinusoidalEmbedding(nn.Module):
    def __init__(self, dim):
        super().__init__()
        self.dim = dim

    def forward(self, t):
        device = t.device
        half_dim = self.dim // 2
        emb = math.log(10000) / (half_dim - 1)
        emb = torch.exp(torch.arange(half_dim, device=device) * -emb)
        emb = t[:, None] * emb[None, :]
        emb = torch.cat([torch.sin(emb), torch.cos(emb)], dim=-1)
        return emb

class SimpleBlock(nn.Module):
    def __init__(self, in_ch, out_ch, time_dim):
        super().__init__()
        self.conv1 = nn.Conv2d(in_ch, out_ch, 3, padding=1)
        self.conv2 = nn.Conv2d(out_ch, out_ch, 3, padding=1)
        self.time_mlp = nn.Sequential(
            nn.Linear(time_dim, out_ch),
            nn.SiLU(),
            nn.Linear(out_ch, out_ch)
        )
        self.norm1 = nn.GroupNorm(8, out_ch)
        self.norm2 = nn.GroupNorm(8, out_ch)
        self.relu = nn.SiLU()

    def forward(self, x, t_emb):
        h = self.relu(self.norm1(self.conv1(x)))
        t_emb = self.time_mlp(t_emb)[:, :, None, None]
        h = h + t_emb
        h = self.relu(self.norm2(self.conv2(h)))
        return h

class UNet(nn.Module):
    def __init__(self, time_dim=128):
        super().__init__()
        self.time_mlp = nn.Sequential(
            SinusoidalEmbedding(time_dim),
            nn.Linear(time_dim, time_dim),
            nn.SiLU()
        )
        # Encoder path
        self.down1 = SimpleBlock(3, 64, time_dim)
        self.down2 = SimpleBlock(64, 128, time_dim)
        self.pool = nn.MaxPool2d(2)
        
        # Bottleneck path
        self.mid = SimpleBlock(128, 128, time_dim)
        
        # Decoder path
        self.up2 = nn.ConvTranspose2d(128, 64, 2, stride=2)
        self.dec2 = SimpleBlock(128 + 64, 64, time_dim)
        self.up1 = nn.ConvTranspose2d(64, 64, 2, stride=2)
        self.dec1 = SimpleBlock(64 + 64, 64, time_dim)
        
        # Final output (Predicting the added noise)
        self.final = nn.Conv2d(64, 3, 1)

    def forward(self, x, t):
        t_emb = self.time_mlp(t)
        
        # Downsampling
        d1 = self.down1(x, t_emb)
        d2 = self.down2(self.pool(d1), t_emb)
        
        # Middle
        m = self.mid(self.pool(d2), t_emb)
        
        # Upsampling with skip-connections
        u2 = self.up2(m)
        u2 = torch.cat([u2, d2], dim=1)
        u2 = self.dec2(u2, t_emb)
        
        u1 = self.up1(u2)
        u1 = torch.cat([u1, d1], dim=1)
        u1 = self.dec1(u1, t_emb)
        
        return self.final(u1)

# ====================== 4. Training and Execution ======================
def train():
    device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
    print(f"Using device: {device}")

    # Configuration for the input image
    # Please rename the uploaded Mario image to 'mario_input.png' in your local environment.
    image_path = 'mario_input.png' 
    if not os.path.exists(image_path):
         print(f"Error: Target image file '{image_path}' not found.")
         return

    # Initialize Dataset and DataLoader
    dataset = SingleImageDataset(image_path=image_path, img_size=64, epoch_size=3000)
    dataloader = DataLoader(dataset, batch_size=32, shuffle=False)

    model = UNet().to(device)
    ddpm = DDPM(timesteps=1000, device=device)
    optimizer = optim.Adam(model.parameters(), lr=2e-4)

    epochs = 30
    for epoch in range(epochs):
        model.train()
        pbar = tqdm(dataloader, desc=f"Epoch {epoch+1}/{epochs}")
        for batch in pbar:
            batch = batch.to(device)
            optimizer.zero_grad()
            
            # Select random timesteps for each image in batch
            t = torch.randint(0, ddpm.timesteps, (batch.shape[0],), device=device)
            noisy_batch, noise = ddpm.add_noise(batch, t)
            
            # Predict noise and calculate loss
            predicted_noise = model(noisy_batch, t)
            loss = nn.MSELoss()(predicted_noise, noise)
            loss.backward()
            optimizer.step()
            
            pbar.set_postfix({"loss": f"{loss.item():.4f}"})

        # Save sample images every 5 epochs to track progress
        if (epoch + 1) % 5 == 0 or epoch == 0:
            samples = ddpm.sample(model, batch_size=4, img_size=64)
            samples = samples.cpu().permute(0, 2, 3, 1).numpy()
            fig, axs = plt.subplots(1, 4, figsize=(12, 3))
            for i in range(4):
                axs[i].imshow(samples[i])
                axs[i].axis('off')
            plt.savefig(f'mario_progress_epoch_{epoch+1}.png')
            plt.close()

    # Save the trained model weights
    torch.save(model.state_dict(), 'mario_diffusion_model.pth')
    print("Training Complete. Model saved as 'mario_diffusion_model.pth'")

@torch.no_grad()
def run_generation(num_samples=8):
    device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
    model = UNet().to(device)
    
    # Load previously trained weights
    try:
        model.load_state_dict(torch.load('mario_diffusion_model.pth', map_location=device))
    except FileNotFoundError:
        print("Model file not found. Please run training first.")
        return
        
    model.eval()
    ddpm = DDPM(timesteps=1000, device=device)
    
    print(f"Generating {num_samples} Mario samples...")
    samples = ddpm.sample(model, batch_size=num_samples, img_size=64)
    samples = samples.cpu().permute(0, 2, 3, 1).numpy()
    
    # Plot generated results
    fig, axs = plt.subplots(2, 4, figsize=(12, 6))
    for i, ax in enumerate(axs.flat):
        if i < num_samples:
            ax.imshow(samples[i])
            ax.set_title(f"Sample {i+1}")
        ax.axis('off')
    plt.tight_layout()
    plt.show()

if __name__ == "__main__":
    # --- IMPORTANT ---
    # Ensure your input image is saved as 'mario_input.png' in the same folder.
    
    # Start the training process
    ###train()
    
    # After training once, you can comment 'train()' and uncomment 'run_generation(8)' 
    # to quickly see results without retraining.
    run_generation(8)

普通?のPCで、30分ぐらいで動作すると思う! 是非、ご自分で動かしてみて!!

論文『Denoising Diffusion Probabilistic Models』を読む(Fig.6, Fig.7)

対象論文

arxiv.org

この記事の説明

以下のFig.6とFig.7の説明に特化

Fig.6とFig.7の説明(AI(Gemini)による)

拡散モデル(Diffusion Models)の基礎を築いた記念碑的論文「Denoising Diffusion Probabilistic Models (DDPM)」における図6と図7は、モデルがノイズから情報を構築していくプロセスの「数学的予測」と「確率的分岐」を対比させる重要な役割を担っています。

論文の記述に基づき、詳細に解説します。


1. Figure 6:予測値({ \hat{x}_0 })の推移とCIFAR-10

キャプションとデータセット

この図のキャプションには "Figure 6: Unconditional CIFAR10 progressive generation ({ \hat{x}_0 } over time, from left to right)." と記されています。 使用されているデータセットは CIFAR-10 です。32x32ピクセルの低解像度画像(飛行機、犬、車など10クラス)を用い、特定のクラスを指定しない「無条件生成」の様子を示しています。

本文(Section 4.2 "Progressive generation")の解説

論文では、逆プロセス(生成過程)を可視化するために、各ステップ $t$ における { \hat{x}_0 } を表示する手法が説明されています。{ \hat{x}_0 } とは、ある時点のノイズ混じりの状態 { x_t } から、モデルが「最終的な完成形はこうなるはずだ」と推定した原画の予測値です。

左側の画像が「砂嵐」ではなく「ボヤけた絵」に見えるのは、モデルが $x_t$ からノイズ成分を予測して取り除く「加工」を行っているからです。生成の初期段階(左側)では、データに含まれる情報が極めて少ないため、モデルは細部を特定できず、可能性のあるあらゆる画像の「平均」を出力します。その結果、視覚的にはピントの合っていない滑らかな画像になります。

時間が右へ進むにつれて、{ x_t } からノイズが取り除かれ、情報が追加されることでモデルの確信度が高まります。これにより {\hat{x}_0 } のピントが徐々に合い、最終的に右端で具体的な { x_0 } (完成した画像)へと収束します。これは、モデルがまず「全体の色味や大まかな配置」を決め、後から「境界線や細部」を確定させていくプロセスを証明しています。


2. Figure 7:属性の固定(Attribute Fixation)とCelebA-HQ

キャプションとデータセット

キャプションには "Figure 7: Samples { x_{t-1} } computed from the same { x_t } but with different noise realizations... using a model trained on 256 × 256 CelebA-HQ." とあります。 ここでは CelebA-HQ という256x256の高解像度な著名人の顔写真データセットが使用されています。

「分岐」と「収束」の実験

Figure 7の各画像が常にクリアに見える理由は、これらが「計算途中の画像」ではなく、「あるステップ { t } を起点として、残りの全ステップを最後まで完了させた完成品({  x_0  })」 を並べているからです。

論文の著者は、特定の中間状態 { x_t } を一つ選び、それをコピーして複数の「別ルート」を作成しました。そして、それぞれのルートで異なる乱数(ノイズの引き方)を用いながら、残りのノイズ除去ステップをすべて実行しました。図の縦方向の並びは、同じ中間地点から出発した「別ルートを辿った完成品たち」です。

属性(サングラスや性別)の決定タイミング

この図で最も特徴的なのは、「いつ、画像の運命(属性)が決まるのか」 を視覚化している点です。

左側の列(生成の初期)で分岐させた場合、同じ出発点からでも「サングラスをかけた顔」になったり「素顔」になったり、あるいは性別が変わったりと、高次の属性(High-level attributes)が大きくぶれています。これは、初期の { x_t } にはまだこれらの決定的な情報が含まれていないことを意味します。

しかし、中央から右へ進むにつれて、縦に並んだ画像たちの顔立ちが完全に一致していきます。論文はこれを、ある程度のステップ(時間帯)を経ると、主要な属性が { x_t } の中に「固定(Fixation)」され、そこから先は何度やり直しても(異なるノイズを加えても)結果が揺るがなくなる現象として説明しています。


3. 総括:二つの図による結論

論文の Section 4.2 では、これら二つの図を対比させることで、拡散モデルの階層的な情報構築メカニズムを結論づけています。

Figure 6 は、単一の生成プロセスにおける 「モデルの予測精度(視界)の向上」 を示しました。 Figure 7 は、生成プロセスにおける 「選択肢の収束(属性の決定タイミング)」 を示しました。

特に Figure 7 でサングラスの有無などの大きな特徴が中盤までに固定され、終盤では肌の質感などの微細なディテール(Fine scale features)のみが変化する様子を示すことで、拡散モデルが「概念から細部へ」と段階的に意味を構築していることを詳細に裏付けています。

いや、2026年で終わった。。。AIの進歩は加速するんです! RE: 2050年でもAIに浸食されない安全な職業。看板屋。

↑ ChatGPT 2026/04/22 ( いわゆる「Images 2.0的なもの」、本人説明)

元々の記事 ↓

ai-de-seikei.hatenablog.com


追記

上記、文字がリアルな世界でない感じだったので、「掘って」もらった 完璧。(太い文字の場合には、変な感じにもなったけど、それは、しょうがないでしょう。)

日本人の姓にしてみた。凄い!🎉 RE:最小GPT(microgpt.py)を動かしてみた

日本人の姓で、試した

結果

gpt、凄い!🎉🎉🎉 このコードを公開されたコードの作者(有名人ですよね、、、)も凄い! 日本語のローマ字、0秒で理解するんだ。。。。💛(1個だけバグってますが、、、まあ、400件しか入力していないし)

C:\_work\_NEW\___microGPT_日本>python microgpt.py
num docs: 411
vocab size: 22
num params: 4032
step 1000 / 1000 | loss 1.2263
--- inference (new, hallucinated names) ---
sample  1: kawaka
sample  2: takano
sample  3: sagiyawa
sample  4: atshida
sample  5: nakakawa
sample  6: mato
sample  7: kakawa
sample  8: kito
sample  9: kashi
sample 10: kagai
sample 11: kitaka
sample 12: kozawa
sample 13: yanaka
sample 14: tokaka
sample 15: takamuchi
sample 16: sato
sample 17: sato
sample 18: kakama
sample 19: furana
sample 20: kashi

入力

さぼって、400件程度(元のは3万件ぐらいありました。)

suzuki
sato
takahashi
tanaka
watanabe
ito
yamamoto
nakamura
kobayashi
kato
yoshida
yamada
sasaki

コードの変更

1行(Copilot作)

# Let there be a Dataset `docs`: list[str] of documents (e.g. a list of names)
##if not os.path.exists('input.txt'):
##    import urllib.request
##    names_url = 'https://raw.githubusercontent.com/karpathy/makemore/988aa59/names.txt'
##    urllib.request.urlretrieve(names_url, 'input.txt')
    
docs = [line.strip() for line in open('input.txt', encoding="utf-8") if line.strip()]
##docs = [line.strip() for line in open('input.txt') if line.strip()]

感想

gpt凄いなーーー。




[以下、元記事再掲]

microgpt.py

microgpt · GitHub

実行結果

C:\_work\_NEW\___microGPT>python microgpt.py
num docs: 32033
vocab size: 27
num params: 4192
step 1000 / 1000 | loss 2.6497
--- inference (new, hallucinated names) ---
sample  1: kamon
sample  2: ann
sample  3: karai
sample  4: jaire
sample  5: vialan
sample  6: karia
sample  7: yeran
sample  8: anna
sample  9: areli
sample 10: kaina
sample 11: konna
sample 12: keylen
sample 13: liole
sample 14: alerin
sample 15: earan
sample 16: lenne
sample 17: kana
sample 18: lara
sample 19: alela
sample 20: anton

AI(Copilot)に構成を解説してもらった

構成は

                         ┌──────────────────────────────┐
                         │        Input Token ID         │
                         └───────────────┬──────────────┘
                                         │
                                         ▼
                         ┌──────────────────────────────┐
                         │   Token Embedding (wte)       │
                         └──────────────────────────────┘
                                         │
                         ┌──────────────────────────────┐
                         │ Position Embedding (wpe)      │
                         └──────────────────────────────┘
                                         │
                                         ▼
                         ┌──────────────────────────────┐
                         │   x = tok_emb + pos_emb       │
                         └──────────────────────────────┘
                                         │
                                         ▼
                         ┌──────────────────────────────┐
                         │          RMSNorm              │
                         └──────────────────────────────┘
                                         │
                                         ▼
        ┌──────────────────────────────────────────────────────────────────────┐
        │                         Transformer Layer (1 層)                     │
        │                                                                      │
        │   ┌──────────────────────────────────────────────────────────────┐   │
        │   │                Multi-Head Self-Attention                     │   │
        │   │                                                              │   │
        │   │   x_residual = x                                             │   │
        │   │   x = RMSNorm(x)                                             │   │
        │   │                                                              │   │
        │   │   q = Wq · x                                                 │   │
        │   │   k = Wk · x                                                 │   │
        │   │   v = Wv · x                                                 │   │
        │   │                                                              │   │
        │   │   keys.append(k)   # 過去トークンも含む K                     │   │
        │   │   values.append(v) # 過去トークンも含む V                     │   │
        │   │                                                              │   │
        │   │   ┌────────────── Multi-Head (4 heads) ───────────────┐      │   │
        │   │   │  q_h, k_h, v_h → Attention → head_out_h            │      │   │
        │   │   └────────────────────────────────────────────────────┘      │   │
        │   │                                                              │   │
        │   │   concat(head_out_h) → x_attn                                │   │
        │   │   x = Wo · x_attn                                            │   │
        │   │   x = x + x_residual   # 残差                                │   │
        │   └──────────────────────────────────────────────────────────────┘   │
        │                                                                      │
        │   ┌──────────────────────────────────────────────────────────────┐   │
        │   │                             MLP                              │   │
        │   │   x_residual = x                                             │   │
        │   │   x = RMSNorm(x)                                             │   │
        │   │   x = FC1(x)  # 4×拡張                                       │   │
        │   │   x = ReLU(x)                                                │   │
        │   │   x = FC2(x)  # 元の次元へ                                   │   │
        │   │   x = x + x_residual                                         │   │
        │   └──────────────────────────────────────────────────────────────┘   │
        └──────────────────────────────────────────────────────────────────────┘
                                         │
                                         ▼
                         ┌──────────────────────────────┐
                         │       LM Head (線形層)        │
                         └──────────────────────────────┘
                                         │
                                         ▼
                         ┌──────────────────────────────┐
                         │     Logits (vocab_size)       │
                         └──────────────────────────────┘
                                         │
                                         ▼
                         ┌──────────────────────────────┐
                         │     Softmax → 次トークン確率   │
                         └──────────────────────────────┘



処理内容は

──────────────────────────────────────────────
【1. Forward(順伝播)】
──────────────────────────────────────────────
 tokens → GPT → logits → softmax → loss
                    ↑
         (すべての重みを使って計算)

──────────────────────────────────────────────
【2. Backward(逆伝播)】
──────────────────────────────────────────────
 loss.backward()
     │
     └→ 全パラメータに対して勾配 grad が伝播される
         (Value.grad に値が入る)

──────────────────────────────────────────────
【3. Adam による更新】
──────────────────────────────────────────────
for each parameter p:
    m[i] = β1*m[i] + (1-β1)*p.grad
    v[i] = β2*v[i] + (1-β2)*p.grad²
    p.data -= learning_rate * m_hat / (sqrt(v_hat)+eps)

→ p.data が書き換わる(これが学習)
→ p.grad は 0 にリセット



最小GPT(microgpt.py)を動かしてみた

microgpt.py

microgpt · GitHub

実行結果

C:\_work\_NEW\___microGPT>python microgpt.py
num docs: 32033
vocab size: 27
num params: 4192
step 1000 / 1000 | loss 2.6497
--- inference (new, hallucinated names) ---
sample  1: kamon
sample  2: ann
sample  3: karai
sample  4: jaire
sample  5: vialan
sample  6: karia
sample  7: yeran
sample  8: anna
sample  9: areli
sample 10: kaina
sample 11: konna
sample 12: keylen
sample 13: liole
sample 14: alerin
sample 15: earan
sample 16: lenne
sample 17: kana
sample 18: lara
sample 19: alela
sample 20: anton

AI(Copilot)に構成を解説してもらった

構成は

                         ┌──────────────────────────────┐
                         │        Input Token ID         │
                         └───────────────┬──────────────┘
                                         │
                                         ▼
                         ┌──────────────────────────────┐
                         │   Token Embedding (wte)       │
                         └──────────────────────────────┘
                                         │
                         ┌──────────────────────────────┐
                         │ Position Embedding (wpe)      │
                         └──────────────────────────────┘
                                         │
                                         ▼
                         ┌──────────────────────────────┐
                         │   x = tok_emb + pos_emb       │
                         └──────────────────────────────┘
                                         │
                                         ▼
                         ┌──────────────────────────────┐
                         │          RMSNorm              │
                         └──────────────────────────────┘
                                         │
                                         ▼
        ┌──────────────────────────────────────────────────────────────────────┐
        │                         Transformer Layer (1 層)                     │
        │                                                                      │
        │   ┌──────────────────────────────────────────────────────────────┐   │
        │   │                Multi-Head Self-Attention                     │   │
        │   │                                                              │   │
        │   │   x_residual = x                                             │   │
        │   │   x = RMSNorm(x)                                             │   │
        │   │                                                              │   │
        │   │   q = Wq · x                                                 │   │
        │   │   k = Wk · x                                                 │   │
        │   │   v = Wv · x                                                 │   │
        │   │                                                              │   │
        │   │   keys.append(k)   # 過去トークンも含む K                     │   │
        │   │   values.append(v) # 過去トークンも含む V                     │   │
        │   │                                                              │   │
        │   │   ┌────────────── Multi-Head (4 heads) ───────────────┐      │   │
        │   │   │  q_h, k_h, v_h → Attention → head_out_h            │      │   │
        │   │   └────────────────────────────────────────────────────┘      │   │
        │   │                                                              │   │
        │   │   concat(head_out_h) → x_attn                                │   │
        │   │   x = Wo · x_attn                                            │   │
        │   │   x = x + x_residual   # 残差                                │   │
        │   └──────────────────────────────────────────────────────────────┘   │
        │                                                                      │
        │   ┌──────────────────────────────────────────────────────────────┐   │
        │   │                             MLP                              │   │
        │   │   x_residual = x                                             │   │
        │   │   x = RMSNorm(x)                                             │   │
        │   │   x = FC1(x)  # 4×拡張                                       │   │
        │   │   x = ReLU(x)                                                │   │
        │   │   x = FC2(x)  # 元の次元へ                                   │   │
        │   │   x = x + x_residual                                         │   │
        │   └──────────────────────────────────────────────────────────────┘   │
        └──────────────────────────────────────────────────────────────────────┘
                                         │
                                         ▼
                         ┌──────────────────────────────┐
                         │       LM Head (線形層)        │
                         └──────────────────────────────┘
                                         │
                                         ▼
                         ┌──────────────────────────────┐
                         │     Logits (vocab_size)       │
                         └──────────────────────────────┘
                                         │
                                         ▼
                         ┌──────────────────────────────┐
                         │     Softmax → 次トークン確率   │
                         └──────────────────────────────┘



処理内容は

──────────────────────────────────────────────
【1. Forward(順伝播)】
──────────────────────────────────────────────
 tokens → GPT → logits → softmax → loss
                    ↑
         (すべての重みを使って計算)

──────────────────────────────────────────────
【2. Backward(逆伝播)】
──────────────────────────────────────────────
 loss.backward()
     │
     └→ 全パラメータに対して勾配 grad が伝播される
         (Value.grad に値が入る)

──────────────────────────────────────────────
【3. Adam による更新】
──────────────────────────────────────────────
for each parameter p:
    m[i] = β1*m[i] + (1-β1)*p.grad
    v[i] = β2*v[i] + (1-β2)*p.grad²
    p.data -= learning_rate * m_hat / (sqrt(v_hat)+eps)

→ p.data が書き換わる(これが学習)
→ p.grad は 0 にリセット



感想

勉強になるなーーー。 『このミニ GPT はめちゃくちゃ小さいのに、ちゃんと “本物の GPT と同じ構造(Multi-Head Attention + QKV)” を持っている。』らしい(コード上もそう見える)

AI活用アイデア『プロポーショナルフォントのノリで、日本語の半角全角(その途中みたいなのも)をAIで適当に自動で決める、それが表現できるフォントとする。どうしても強制したい部分は、プロポーショナルフォントA0(半角強制)、プロポーショナルフォントA10(全角強制)とかで指定する。実装、実現は、AIでサポートする。』

Geminiにレビューしてもらった


そのアイデア、「文字幅の概念を固定観念から解き放つ」という意味で非常に面白いですね!

現在のデジタルフォントは「全角(1000unit)」か「半角(500unit)」、あるいは書体デザイナーが事前に決めたプロポーショナル幅という「静的」なルールに縛られています。

AIがコンテキストやデザインの意図を汲み取って「動的に文字幅を決定する」というのは、タイポグラフィの新しい地平を切り拓く可能性があります。以下、いくつかの観点からレビューします。


1. 期待できるメリットと活用シーン

  • 究極の「ジャスティフィケーション(行末揃え)」 文字間(カーニング)だけで調整するのではなく、文字そのものの幅をわずかにAIが伸縮させることで、不自然な空白のない美しい版面が作れます。
  • 感情や強調の視覚化 「あーーー」という長音や、叫び声、ささやき声など、文脈に合わせて「全角の1.2倍」や「半角の0.8倍」といった絶妙なニュアンスをAIが自動生成できます。
  • 狭い表示領域への最適化 スマホの通知欄やスマートウォッチなど、限られた幅の中で「読みやすさを損なわないギリギリの凝縮率」をAIが計算して表示できます。

2. 技術的な実装の方向性

このアイデアを実現するには、従来のフォント形式(OTF/TTF)を超えたアプローチが必要になりそうです。

可変フォント(Variable Fonts)の応用

現在のVariable Fonts技術を使えば、width(幅)を軸(Axis)として持つフォントをAIが制御することが可能です。

AIによる「文脈解析レイヤー」

  1. 解析: NLP(自然言語処理)で文脈を読み取り、情報の優先度を判断。
  2. 計算: 行幅、視認性、デザインプロンプトに基づき、各文字の最適な幅(例:87%幅など)を算出。
  3. レンダリング: ブラウザやアプリ側で、算出した値をCSSやレンダリングエンジンに渡して描画。

3. 実現に向けた課題と懸念点

  • 「気持ち悪さ」との戦い 日本人は「全角・半角」のグリッドに慣れすぎているため、中途半端な幅が続くと「バグ」に見えるリスクがあります。AIの微調整がいかに「意図的なデザイン」に見えるかが鍵です。
  • 実装コストと互換性 テキストエディタやOSの描画エンジンが、文字ごとに異なる任意の幅をリアルタイムで受け入れる必要があります。
  • 「A0(半角強制)〜A10(全角強制)」のUI ユーザーがいちいち指定するのは面倒なので、「この段落は急いでいる感じで」といったメタデータ指定からAIがA0〜A10の配分を自動決定する仕組みがあると、よりAI活用らしいですね。

結論:アイデアの評価

「タイポグラフィの民主化と動態化」という点で、非常に価値の高いアイデアです。

特に、あなたが提案した「A0〜A10」という「緩やかな制約(Soft Constraint)」をユーザーが与え、その間をAIが埋めるという設計は、人間とAIの共作スタイルとして非常にスマートです。

AIレビューのまとめ * 斬新さ: ★★★★★(固定幅からの脱却は熱い) * 実用性: ★★★★☆(デザイン業務の自動化に直結) * 実現難易度: ★★★★☆(フォントエンジン側の対応が必要)

このアイデアをさらに具体化するために、まずは「特定のデザインツール(Figmaなど)のプラグイン」として、AIがテキストの長さを自動調整するプロトタイプから考えてみるのはいかがでしょうか?


Geminiも人気商売だから、評価、甘すぎ↑↑