AI を変えた12論文:詳細解説とコード例付き

この記事には短い動画があります:TikTok

1. 概要

毎日使用しているChatGPT、Midjourney、AIが統合されたGoogle Searchなど、ほとんどのAIシステムは、数十の研究論文の上に成り立っています。この記事では、その12論文を紹介し、主要なアイデアを分かりやすく説明し、各アイデアをJavaコードで例示します。読み終わると、Transformerがなぜ自然言語処理を支配しているのか、RAGがなぜ幻覚問題を解決できるのか、そして「段階的に考えてください」というプロンプトがなぜそこまで強力なのかを理解できるようになります。

2. AlexNet(2012)— ディープラーニングが「見る」能力を獲得

AlexNetが登場する前、コンピュータビジョンは手動でのfeature engineeringに依存していました。AlexNetはCNN(畳み込みニューラルネットワーク)とReLU activationとdropoutを使用し、GPUで学習させ、ImageNet 2012大会ですべての競争相手を圧倒し、誤差率を約10ポイント削減しました。これがディープラーニング時代の幕開けでした。

public class ConvOutputSizeCalculator {

    public static int outputSize(int inputSize, int kernelSize, int stride, int padding) {
        return (inputSize - kernelSize + 2 * padding) / stride + 1;
    }

    public static void main(String[] args) {
        // AlexNetの入力画像: 227x227, kernel 11x11, stride 4
        int size = outputSize(227, 11, 4, 0);
        System.out.println(size);
    }
}
55

outputSize はconv layer後のfeature mapのサイズを計算します。入力227×227、kernel 11×11 stride 4の場合、出力は55×55×96 — AlexNetの最初のレイヤーのサイズと一致します。ReLUはsigmoidのようにgradientが消失(vanishing gradient)することがないため、より深いネットワークでも学習が可能です。

3. GAN(2014)— リアルな画像を生成するために対抗する2つのネットワーク

Generative Adversarial Network(生成的対抗ネットワーク)は2つのネットワークを対立させます:generatorが偽の画像を生成してdiscriminatorを騙し、discriminatorが本物と偽物を区別しようとします。両者は学習の各ラウンドを通じて改善されていきます。

public record GanLosses(double discriminatorLoss, double generatorLoss) {}

public class GanLossCalculator {

    public static GanLosses compute(double probReal, double probFake) {
        double dLoss = -(Math.log(probReal) + Math.log(1 - probFake));
        double gLoss = -Math.log(probFake);
        return new GanLosses(dLoss, gLoss);
    }
}

probReal はdiscriminatorが本物の画像を正しく判定する確率、probFake はgeneratorに騙される確率です。Discriminatorは probReal → 1 と probFake → 0 を望みます。generatorはその逆を望みます。これは典型的なゼロサムゲームです。

注記: GANの学習は悪名高く困難です — mode collapse(generatorが限定された種類の画像しか生成しない)が発生しやすいです。その後のDiffusion modelはより安定しているため、最新のほとんどの画像生成システム(DALL·E、Stable Diffusion)はGANをもう使用していません。

4. Transformer(2017)— “Attention Is All You Need”

Transformerはself-attentionを使用して、RNN/LSTMを完全に置き換えます:各tokenは文内のすべての他のtokenを直接見て関連度を計算でき、逐次処理が不要です。これはGPT、Claude、Llama、BERTの基本アーキテクチャです。

public class ScaledDotProductAttention {

    public static double[] attendFirstToken(double[][] query, double[][] key) {
        int seqLen = key.length;
        int dim = query[0].length;
        double scale = Math.sqrt(dim);
        double[] scores = new double[seqLen];
        for (int j = 0; j < seqLen; j++) {
            double dot = 0;
            for (int k = 0; k < dim; k++) dot += query[0][k] * key[j][k];
            scores[j] = dot / scale;
        }
        return softmax(scores);
    }

    private static double[] softmax(double[] row) {
        double max = Arrays.stream(row).max().orElse(0);
        double sum = 0;
        double[] exps = new double[row.length];
        for (int i = 0; i < row.length; i++) {
            exps[i] = Math.exp(row[i] - max);
            sum += exps[i];
        }
        for (int i = 0; i < exps.length; i++) exps[i] /= sum;
        return exps;
    }
}

行 dot / scale は内積を sqrt(dim) で割り、値が大きすぎてsoftmaxが飽和するのを防ぎます。softmax の結果はattention weightsです — 検討中のtokenに関連度が高いtokenはより高い重みを持ち、value vectorを混合するために使用されます。

5. スケーリング則(2020)— lossが べき乗則に従って減少

OpenAIは発見しました:パラメータ数、データ、またはcomputeを指数関数的に増加させると、lossはかなり滑らかで予測可能なべき乗則に従って減少します — 「魔法」はなく、単に正しくスケーリングするだけです。

public class ScalingLawEstimator {

    // L(N) = (Nc / N)^alpha — N: パラメータ数(十億単位)、Nc、alpha: 実験定数
    public static double estimateLoss(double paramsBillion, double nc, double alpha) {
        return Math.pow(nc / paramsBillion, alpha);
    }
}

実験データから得られた固定の nc と alpha を使用して、paramsBillion を増加させると estimateLoss は段階的に減少しますが、減少速度は低下します(収穫逓減)。これがAI研究所が何億ドルもcomputeに投資する理由です:lossの減少は予測可能であり、ギャンブルではないからです。

注記: スケーリング則には限界があります。ある閾値を超えると、lossをさらに少し減らすために膨大な計算量の指数関数的増加が必要になります — これが業界が単に「より大きく」するのではなく、データとアーキテクチャの改善に注力を転換している理由です。

6. GPT-3(2020)— スケールが新しい能力を生み出す

1750億パラメータのGPT-3は、十分に大きければ、モデルはfew-shot learningが可能になることを示しています — プロンプト内のいくつかの例だけで十分であり、重みを再度fine-tuneする必要がありません。

<dependency>
    <groupId>org.springframework.ai</groupId>
    <artifactId>spring-ai-openai-spring-boot-starter</artifactId>
    <version>1.0.0</version>
</dependency>
@Service
public class FewShotClassifier {

    private final ChatClient chatClient;

    public FewShotClassifier(ChatClient.Builder builder) {
        this.chatClient = builder.build();
    }

    public String classifySentiment(String review) {
        String prompt = """
                レビュー: "商品が良く、配送が速い" -> ポジティブ
                レビュー: "商品が不良で使えない" -> ネガティブ
                レビュー: "%s" ->
                """.formatted(review);
        return chatClient.prompt(prompt).call().content();
    }
}

プロンプト内の2つの例は、context内の回答形式をモデルに「教える」役割を果たします — 再学習は不要です。これがまさにより小さいモデルが十分に実行できない出現能力(emergent ability)です。

7. InstructGPT&RLHF(2022)— 粗いLLMを有用なアシスタントに変える

元のGPT-3は次のtokenの予測は得意ですが、「指示に従う」ことができません — 話題から外れた回答や有害な回答をする可能性があります。InstructGPTは人間フィードバックからの強化学習(RLHF)を使用します:人間が回答をランク付けし、その結果からreward modelを学習させ、reward modelを使用してPPOでpolicyを最適化します。

public record Completion(String text, double rewardScore) {}

public class RewardBasedSelector {

    public Completion pickBest(List<Completion> candidates) {
        return candidates.stream()
                .max(Comparator.comparingDouble(Completion::rewardScore))
                .orElseThrow();
    }
}

上記のコードは最後のステップのみを示しています:reward modelの評価に基づいて最も高い報酬スコアを持つcompletionを選択します。実際には、PPOはpolicy modelの重みを段階的に更新して、最初から高得点の回答を生成するようにします。後から選別する必要がありません。

注記: RLHFは人間が注釈を付けた大量のデータ(ランキング)が必要であり、高額で評価者のバイアスに影響されやすいです。その後、DPO(直接嗜好最適化)などの技術がこのコストを削減するために登場しました。

8. ViT(2020)— 画像も「文」になる

Vision Transformerは画像をパッチに分割し(例:16×16ピクセル)、各パッチをベクトルにフラット化し、Transformerエンコーダに入力するtokenのように扱います — テキスト処理と全く同じアーキテクチャです。

public class PatchEmbeddingCalculator {

    public static int numPatches(int imageSize, int patchSize) {
        int gridSize = imageSize / patchSize;
        return gridSize * gridSize;
    }
}

224×224の画像と16×16のパッチでは、numPatches は196を返します — Transformerに入力される196 tokenの「文」に相当します。このように、ViTはNLPで有効性が証明されたattention技術を全て再利用して画像タスクに適用し、学習データが十分に大きい場合はCNNを上回ります。

9. DDPM(2020)— ノイズを追加してから除去する方法を学ぶ

Denoising Diffusion Probabilistic Modelは現代のすべてのdiffusion modelの基礎を形成します:forward processは複数のステップを通じて段階的にGaussianノイズを画像に加え、reverse processはそのノイズを予測して除去し、元の画像を再構成することを学習します。

public class ForwardDiffusion {

    public static double[] addNoise(double[] x0, double alphaBarT, Random random) {
        double[] xt = new double[x0.length];
        for (int i = 0; i < x0.length; i++) {
            double noise = random.nextGaussian();
            xt[i] = Math.sqrt(alphaBarT) * x0[i] + Math.sqrt(1 - alphaBarT) * noise;
        }
        return xt;
    }
}

alphaBarT はステップ t で元の画像とノイズの比を制御します:1に近い場合は元の画像に近いままで、0に近い場合はほぼノイズです。画像生成モデルはこのプロセスを逆に学習します — 純粋なノイズから始めて、段階的に「ノイズを除去」して有意味な画像を作成します。

10. Latent Diffusion(2021)— 圧縮空間でのデノイジング

pixelレベルでdiffusionを直接実行するには、多くの計算が必要です。Latent Diffusionはautoencorderを使用して画像を小さいlatent spaceに圧縮し、そこで全体のdiffusionプロセスを実行し、その後画像にデコードします — これはStable Diffusionの背後にある設計です。

public class LatentCompressionCalculator {

    public static long pixelCount(int width, int height, int channels) {
        return (long) width * height * channels;
    }
}
pixelCount(512, 512, 3) = 786_432   // ピクセル空間
pixelCount(64, 64, 4)   = 16_384    // latent空間

48倍近い圧縮率により、diffusionはより高速に実行でき、pixel spaceの冗長情報(均一な色領域、小さな詳細)の大部分がautoencorderによって圧縮時に除去されるため、画像品質は保たれます。

11. CLIP(2021)— 画像とテキストを1つの空間に統合

CLIPは数億のペア(画像、キャプション)でcontrastive learningを学習させます:一致するペアはembeddingが近く、一致しないペアは離れています。その結果、cosine similarityを使用してテキストと画像を比較できる共有空間が得られます。

@Service
public class ImageTextMatcher {

    private final EmbeddingModel embeddingModel;

    public ImageTextMatcher(EmbeddingModel embeddingModel) {
        this.embeddingModel = embeddingModel;
    }

    public double similarity(String caption, float[] imageEmbedding) {
        float[] textEmbedding = embeddingModel.embed(caption);
        return cosine(textEmbedding, imageEmbedding);
    }

    private double cosine(float[] a, float[] b) {
        double dot = 0, normA = 0, normB = 0;
        for (int i = 0; i < a.length; i++) {
            dot += a[i] * b[i];
            normA += a[i] * a[i];
            normB += b[i] * b[i];
        }
        return dot / (Math.sqrt(normA) * Math.sqrt(normB));
    }
}

similarity はキャプションが画像のコンテンツと一致する場合1に近い値を返し、無関連の場合は0または負の値を返します。これが、多くのeコマースプロダクトで使用されている自然言語説明による画像検索(text-to-image search)の背後にあるメカニズムです。

12. Chain-of-Thought(2022)— 1つのプロンプト文が推論を開放

プロンプトに「段階的に考えてください」という句を追加するだけで、大規模なLLMは複雑な問題を小さなステップに分割し、直接回答する場合と比べて計算エラーと論理エラーを大幅に減らします。

public String solveWithReasoning(ChatClient chatClient, String question) {
    String prompt = """
            回答する前に、段階的に考えてください。
            質問: %s
            推論:
            """.formatted(question);
    return chatClient.prompt(prompt).call().content();
}

モデルに回答を確定する前に「推論」するよう要求することで、中間tokenを生成させ、それが足がかりとなります — 難しい質問に答える前に下書きを書く人間のやり方と同じです。

注記: Chain-of-Thoughtは生成されるtoken数を増加させ、より高いコストと遅延をもたらします。単純な質問(直接的なfact検索)の場合、CoTを強制するだけで遅くなり、精度は向上しません。

13. RAG(2020)— 検索してから生成、もう幻覚はない

Retrieval-Augmented Generation(検索強化生成)はLLMに回答を生成する前に実際のドキュメントを検索するステップを接続し、幻覚を減らし、元の学習範囲外の最新データに基づいて回答することを可能にします。

<dependency>
    <groupId>org.springframework.ai</groupId>
    <artifactId>spring-ai-pgvector-store-spring-boot-starter</artifactId>
    <version>1.0.0</version>
</dependency>
@Service
public class DocumentAnsweringService {

    private final VectorStore vectorStore;
    private final ChatClient chatClient;

    public DocumentAnsweringService(VectorStore vectorStore, ChatClient.Builder builder) {
        this.vectorStore = vectorStore;
        this.chatClient = builder.build();
    }

    public String answer(String question) {
        List<Document> relevant = vectorStore.similaritySearch(
                SearchRequest.query(question).withTopK(3));

        String context = relevant.stream()
                .map(Document::getContent)
                .collect(Collectors.joining("\n---\n"));

        String prompt = """
                以下のドキュメントに基づいて、質問に回答してください。情報がない場合は「不明です」と言ってください。
                ドキュメント:
                %s

                質問: %s
                """.formatted(context, question);

        return chatClient.prompt(prompt).call().content();
    }
}

similaritySearch は質問と最も関連度の高い3つのドキュメント段落を取得し、promptのcontextとして統合し、学習時からの曖昧な「記憶」に頼るのではなく、そのcontextに基づいて回答するようモデルに指示します。

class DocumentAnsweringServiceTest {

    @Test
    void shouldReturnUnknownWhenNoRelevantDocument() {
        VectorStore emptyStore = mock(VectorStore.class);
        when(emptyStore.similaritySearch(any())).thenReturn(List.of());

        ChatClient stubClient = mock(ChatClient.class, RETURNS_DEEP_STUBS);
        when(stubClient.prompt(anyString()).call().content()).thenReturn("不明です");

        ChatClient.Builder builder = mock(ChatClient.Builder.class);
        when(builder.build()).thenReturn(stubClient);

        DocumentAnsweringService service = new DocumentAnsweringService(emptyStore, builder);

        String result = service.answer("会社はいつ設立されましたか?");

        assertThat(result).contains("不明です");
    }
}

このテストは、関連ドキュメントが見つからない場合、回答は「不明です」と認める必要があり、むやみに答えを作るべきではないことを確認します — これはRAGの基盤化の目標です。

注記: ドキュメント保管庫が数十ページに過ぎない場合、それをcontext windowに直接詰め込む方が、vector storeを使用した完全なRAG pipelineを構築するより単純で安価です。RAGはデータが大量または頻繁に変更される場合にのみ使用する価値があります。

14. 結論

これら12論文は独立していません — 相互に構築されています:Transformerはgpt-3とViTの基礎です。スケーリング則はGPT-3がなぜ機能するかを説明します。RLHFはGPT-3をChatGPTに変えます。DDPMはLatent Diffusionの起源です。CLIPはdiffusion modelがtextプロンプトを理解するための橋です。それぞれの構成要素を理解することで、新しい論文をより速く読むことができます。なぜなら、今日のほとんどの「新しい」アイデアは、これらの基礎の変形または組み合わせだからです。

llmdeep-learningtransformerragai