Skip to content

14. 深層学習の基礎と活用

前章までは、BoW や TF-IDF に加えて、Sentence Transformers を用いたテキスト処理を学びました。このうち Sentence Transformers の背後にあるのが、深層学習(ディープラーニング)と呼ばれる技術です。本章では、深層学習の基礎概念を理解し、画像認識やテキスト処理への応用、さらには生成 AI の活用方法までを実践的に学びます。

14.1 深層学習の基礎

14.1.1 深層学習とは

深層学習(Deep Learning)とは、人間の脳の神経回路網を模した、多数の層を持つニューラルネットワークを用いた機械学習の手法です。従来の機械学習では、人間が特徴量を設計する必要がありましたが、深層学習ではデータから自動的に特徴を学習できる点が大きな強みです。深層学習は、画像認識・音声認識・自然言語処理・ゲーム AI など多くの分野で従来手法を上回る成果を上げ、現在の AI 技術を支えています。

14.1.2 ニューロン

ニューラルネットワークの基本単位は ニューロン(人工ニューロン)です。ニューロンは、複数の入力を受け取り、それぞれに 重み(weight)を掛けて合計し、バイアス(bias)を加えた値を 活性化関数(activation function)に通して出力します。

graph LR
    X1("入力 x₁") -->|w₁| S("重み付き和 Σ + バイアス b")
    X2("入力 x₂") -->|w₂| S
    X3("入力 x₃") -->|w₃| S
    S --> F("活性化関数 f")
    F --> Y("出力 y")

    classDef data fill:#D9EAF3,stroke:#0072B2,stroke-width:2px,color:#000
    classDef proc fill:#D9F0EA,stroke:#009E73,stroke-width:2px,color:#000
    classDef out fill:#F5ECE2,stroke:#BF8040,stroke-width:2px,color:#000
    class X1,X2,X3 data
    class S,F proc
    class Y out

数式で表すと、入力 \(x_1, x_2, \ldots, x_n\) に対して、ニューロンの出力 \(y\) は次のようになります。

\[ y = f\left(\sum_{i=1}^{n} w_i x_i + b\right) \]

ここで、\(w_i\) は重み、\(b\) はバイアス、\(f\) は活性化関数です。学習とは、データを正しく予測できるように、この重みとバイアスの値を調整するプロセスです。

14.1.3 活性化関数

活性化関数は、ニューロンの出力に非線形性を与える役割を持ちます。重み付き和だけでは線形な表現しかできませんが、非線形の活性化関数を導入することで、複雑なパターンや境界を学習できるようになります。

関数名 数式 主な用途
ReLU \(f(x) = \max(0, x)\) 隠れ層(最も一般的)
シグモイド \(f(x) = \dfrac{1}{1+e^{-x}}\) 2 値分類の出力層
ソフトマックス \(f(x_i) = \dfrac{e^{x_i}}{\sum_j e^{x_j}}\) 多クラス分類の出力層

ReLU(Rectified Linear Unit)は、入力が正ならそのまま、負なら 0 を返すシンプルな関数であり、現在の深層学習で最もよく使われています。シグモイド は出力を 0 から 1 の範囲に変換するため、確率として解釈できます。ソフトマックス は複数の出力を確率分布に変換し、各クラスに属する確率を出力します。

活性化関数はなぜ必要?

線形変換だけなら、何層重ねても全体を 1 つの線形変換にまとめられてしまい、表現できる形は増えません。ReLU のような非線形の活性化関数を間に挟むと、まっすぐな変換だけでは作れない複雑な関係を表せるようになり、層を重ねる効果が生まれます。

14.1.4 ニューラルネットワークの構造

ニューラルネットワークは、複数のニューロンを層状に配置した構造を持ちます。

  • 入力層(Input Layer): データを受け取る層。入力の次元数に対応するニューロンを持つ
  • 隠れ層(Hidden Layer): 入力層と出力層の間にある層。この層の数が多いほど「深い」ネットワークとなる
  • 出力層(Output Layer): 最終的な予測結果を出力する層。多クラス分類ではクラス数に対応するニューロンを持つ(二値分類では、シグモイド関数を使った 1 個の出力で表すこともある)
graph LR
    A("入力層") --> B("隠れ層1")
    B --> C("隠れ層2")
    C --> D("出力層")

    classDef data fill:#D9EAF3,stroke:#0072B2,stroke-width:2px,color:#000
    classDef proc fill:#D9F0EA,stroke:#009E73,stroke-width:2px,color:#000
    classDef out fill:#F5ECE2,stroke:#BF8040,stroke-width:2px,color:#000
    class A data
    class B,C proc
    class D out

隠れ層が 2 層以上あるニューラルネットワークを ディープニューラルネットワーク と呼び、これを用いた学習が深層学習です。

Playground:ニューラルネットワーク

ニューラルネットワーク の Playground では、層の数やニューロン数を変えながら学習を進め、決定境界が形づくられていく過程を観察できます。隠れ層を増やすほど複雑な境界を表現できる様子を確認してみましょう。

14.1.5 順伝播と逆伝播

ニューラルネットワークの学習は、順伝播(Forward Propagation)と 逆伝播(Backpropagation)の 2 つのステップを繰り返すことで行われます。

  1. 順伝播:入力データを入力層から出力層へ向かって順に計算し、予測値を得る
  2. 損失計算:予測値と正解ラベルの差(損失)を計算する
  3. 逆伝播:損失を出力層から入力層へ向かって逆方向にたどり、各重みが損失にどれだけ影響しているかを計算する
  4. 重みの更新:計算された影響度に基づいて、損失が小さくなる方向に重みを調整する
graph LR
    A("① 順伝播<br>入力から予測値を計算") --> B("② 損失を計算<br>予測と正解のずれを測る")
    B --> C("③ 逆伝播<br>各重みの影響を逆向きに計算")
    C --> D("④ 重みを更新<br>損失が小さくなる方向へ")
    D -->|繰り返す| A

    classDef data fill:#D9EAF3,stroke:#0072B2,stroke-width:2px,color:#000
    classDef proc fill:#D9F0EA,stroke:#009E73,stroke-width:2px,color:#000
    classDef model fill:#F7EBF2,stroke:#CC79A7,stroke-width:2px,color:#000
    classDef out fill:#F5ECE2,stroke:#BF8040,stroke-width:2px,color:#000
    class A data
    class B out
    class C model
    class D proc

この一連のプロセスを何度も繰り返すことで、ネットワークの予測精度が徐々に向上します。直感的には、「予測を間違えた分だけ、重みを少しずつ修正する」というプロセスです。

14.1.6 損失関数

損失関数(Loss Function)は、モデルの予測値と正解値の「ずれ」を数値化する関数です。学習の目標は、この損失関数の値を最小化することです。

  • 交差エントロピー損失(Cross-Entropy Loss):分類タスクで使用される。予測された確率分布と正解ラベルの差を測る
  • 平均二乗誤差(Mean Squared Error; MSE):回帰タスクで使用される。予測値と正解値の差の二乗の平均を計算する

14.1.7 オプティマイザ

オプティマイザ(Optimizer)は、損失関数を最小化するために重みをどのように更新するかを決定するアルゴリズムです。

  • SGD(確率的勾配降下法):最もシンプルなオプティマイザ。損失関数の勾配(傾き)と逆の方向に重みを少しずつ更新する
  • Adam:SGD を改良したオプティマイザ。学習率を自動的に調整する仕組みを持ち、多くの場合で SGD より高速に収束する。現在最もよく使われている

14.2 深層学習の実践

前節で学んだ深層学習の考え方を、実際にコードを書いて確かめていきます。深層学習フレームワークの準備から、手書き数字を分類するニューラルネットワークの構築・学習・評価までを一とおり体験します。

14.2.1 TensorFlowとKerasの準備

深層学習のフレームワークとして、TensorFlow と、その機能をより簡単に利用できる Keras を使用します。Google Colab には標準でインストールされていますが、ローカル環境で使用する場合は以下のコマンドでインストールします。

# Google Colabでは不要(プリインストール済み)
# ローカル環境の場合のみ実行
# !pip install tensorflow

バージョンを確認しましょう。

import tensorflow as tf
print(tf.__version__)
Output
2.21.0

なお、表示されるバージョン番号は実行時期により異なります。

14.2.2 MNISTデータセットによる手書き数字分類

第11章でも扱った MNIST(手書き数字 0〜9、28×28 ピクセルのグレースケール画像、訓練 60,000 枚・テスト 10,000 枚)を使い、深層学習による分類を行います。データ形式の確認やサンプル画像の表示は 11.1 節で行ったため、ここでは前処理から始めます。

画素値を 0〜255 から 0〜1 の範囲に正規化し、28×28 の 2 次元画像を 784 個の画素値を並べた 1 次元配列(784 次元ベクトル)に変換します(全結合層に入力するため)。

from tensorflow.keras.datasets import mnist

# データの読み込み(MNISTは第11章でも使用)
(X_train, y_train), (X_test, y_test) = mnist.load_data()

# 画素値を0〜1に正規化
X_train = X_train / 255.0
X_test = X_test / 255.0

# 28×28の画像を784次元のベクトルに変換(Flatten)
X_train_flat = X_train.reshape(-1, 784)
X_test_flat = X_test.reshape(-1, 784)

print(f"変換後の訓練データ: {X_train_flat.shape}")
Output
変換後の訓練データ: (60000, 784)

14.2.3 Kerasによるモデルの構築

Keras の Sequential クラスを使って、シンプルな全結合ニューラルネットワークを構築します。

from tensorflow.keras.models import Sequential
from tensorflow.keras.layers import Dense, Input

# モデルの構築
model = Sequential([
    Input(shape=(784,)),              # 入力の形状を指定
    Dense(128, activation="relu"),    # 隠れ層1
    Dense(64, activation="relu"),     # 隠れ層2
    Dense(10, activation="softmax")   # 出力層(10クラス)
])

# モデルの概要を表示
model.summary()
Output
Model: "sequential"
 Layer (type)                    Output Shape           Param #
 dense (Dense)                   (None, 128)            100,480
 dense_1 (Dense)                 (None, 64)             8,256
 dense_2 (Dense)                 (None, 10)             650
 Total params: 109,386 (427.29 KB)
 Trainable params: 109,386 (427.29 KB)
 Non-trainable params: 0 (0.00 B)

なお、本章のコードと出力は TensorFlow 2.21(Keras 3)で確認したものです。summary() メソッドや学習ログの表示形式はバージョンによって異なります。

モデルの構造を説明します。

  • 入力:784 次元のベクトル(28×28 ピクセルを平坦化)
  • 隠れ層1:128 個のニューロン、活性化関数は ReLU
  • 隠れ層2:64 個のニューロン、活性化関数は ReLU
  • 出力層:10 個のニューロン(0〜9 の 10 クラスに対応)、活性化関数はソフトマックス

Param # はその層のパラメータ数(重みとバイアスの総数)を表しています。例えば、最初の層は \(784 \times 128 + 128 = 100{,}480\) 個のパラメータを持ちます。

14.2.4 モデルのコンパイルと学習

モデルを学習する前に、compile() メソッドでオプティマイザ、損失関数、評価指標を設定します。

# モデルのコンパイル
model.compile(
    optimizer="adam",                        # オプティマイザ
    loss="sparse_categorical_crossentropy",  # 損失関数
    metrics=["accuracy"]                     # 評価指標
)

# モデルの学習
history = model.fit(
    X_train_flat, y_train,
    epochs=10,           # 全データを10回繰り返し学習
    batch_size=32,       # 32サンプルずつまとめて計算
    validation_split=0.2 # 訓練データの20%を検証用に使用
)
Output
Epoch 1/10
1500/1500 [====] - 2s 1ms/step
 - accuracy: 0.9218 - loss: 0.2669 - val_accuracy: 0.9542 - val_loss: 0.1566
Epoch 2/10
1500/1500 [====] - 2s 1ms/step
 - accuracy: 0.9663 - loss: 0.1114 - val_accuracy: 0.9664 - val_loss: 0.1128
...
Epoch 10/10
1500/1500 [====] - 2s 1ms/step
 - accuracy: 0.9931 - loss: 0.0193 - val_accuracy: 0.9732 - val_loss: 0.1281

ここで用いた重要な概念を整理します。

  • エポック(epoch):訓練データ全体を 1 回学習に使うことを 1 エポックと呼ぶ。上の例では 10 エポック学習している
  • バッチサイズ(batch_size):1 回の重み更新で使用するサンプル数。バッチサイズを小さくするとメモリ消費が少ないが学習が不安定になりやすく、大きくすると安定するがメモリ消費が増える
  • 検証データ(validation):学習に使わないデータで過学習をモニタリングする。val_loss が下がらなくなったら過学習の兆候

14.2.5 モデルの評価

テストデータで最終的な精度を確認します。

# テストデータで評価
test_loss, test_accuracy = model.evaluate(X_test_flat, y_test)
print(f"テスト精度: {test_accuracy:.4f}")
Output
313/313 [====] - 0s 1ms/step
 - accuracy: 0.9748 - loss: 0.1037
テスト精度: 0.9748

シンプルな全結合ネットワークでも、約 97.5% の精度で手書き数字を分類できました。学習曲線(精度と損失の推移)をプロットして、学習の進み具合を確認しましょう。

import matplotlib.pyplot as plt

fig, axes = plt.subplots(1, 2, figsize=(12, 4))

# 精度の推移
axes[0].plot(history.history["accuracy"], label="訓練")
axes[0].plot(history.history["val_accuracy"], label="検証")
axes[0].set_title("精度の推移")
axes[0].set_xlabel("エポック")
axes[0].set_ylabel("精度")
axes[0].legend()

# 損失の推移
axes[1].plot(history.history["loss"], label="訓練")
axes[1].plot(history.history["val_loss"], label="検証")
axes[1].set_title("損失の推移")
axes[1].set_xlabel("エポック")
axes[1].set_ylabel("損失")
axes[1].legend()

plt.tight_layout()
plt.show()

訓練データの精度が上がり続ける一方で検証データの精度が頭打ちになる場合は、過学習(Overfitting)が起きている可能性があります。この場合は、エポック数を減らしたり、ドロップアウト(Dropout)などの正則化手法を導入する必要があります。

14.3 画像における深層学習:畳み込みニューラルネットワーク(CNN)

前節では、画像を 1 次元配列に変換して全結合層に入力しました。しかし、1 次元に並べ替えた画素は元の形に戻せるものの、全結合層は画像の空間的な構造(近くのピクセル同士の関係)を構造として利用しません。畳み込みニューラルネットワーク(Convolutional Neural Network; CNN)は、画像の空間構造を活かして特徴を抽出できるネットワークであり、画像認識タスクで広く使われています。

14.3.1 CNNが画像認識に適している理由

CNN が画像認識に適している理由は、以下の 2 つの性質を持つためです。

  • 局所的なパターンの検出:画像中のエッジや角などの局所的な特徴を、小さなフィルタ(カーネル)で検出できる
  • 位置によらない特徴検出:同じフィルタを画像全体で共有するため、画像中のどこに対象物が現れても同じ特徴を検出できる。例えば、画像の左上にある「7」も右下にある「7」も同じフィルタで捉えられる。後述のプーリングと組み合わせることで、位置のずれに対する頑健性(位置不変性)がさらに高まる

CNN は、畳み込みとプーリングを繰り返して特徴を抽出し、最後に全結合層で分類します。

graph LR
    A("入力画像") --> B("畳み込み<br>特徴を検出")
    B --> C("プーリング<br>小さくする")
    C --> D("畳み込み<br>特徴を組み合わせ")
    D --> E("プーリング<br>さらに縮小")
    E --> F("平坦化<br>1列に並べる")
    F --> G("全結合")
    G --> H("出力(0〜9)")

    classDef data fill:#D9EAF3,stroke:#0072B2,stroke-width:2px,color:#000
    classDef proc fill:#D9F0EA,stroke:#009E73,stroke-width:2px,color:#000
    classDef out fill:#F5ECE2,stroke:#BF8040,stroke-width:2px,color:#000
    classDef neu fill:#F2F2F2,stroke:#8C8C8C,stroke-width:2px,color:#000
    class A neu
    class B,D data
    class C,E proc
    class F,G,H out

14.3.2 畳み込み演算

畳み込み(Convolution)は、入力画像の上で小さな フィルタ(カーネル)をスライドさせながら、対応する要素同士を掛け算して合計する演算です。これは、第10章のフィルタ処理で行った畳み込み演算と同じものです。

例えば、3×3 のフィルタを使う場合、入力画像の 3×3 の範囲とフィルタの各要素を掛け合わせて合計し、1 つの値を出力します。フィルタを 1 ピクセルずつずらしながらこの処理を画像全体に適用することで、特徴マップ(Feature Map)が得られます。

フィルタの値は学習によって自動的に決まります。学習の結果、あるフィルタは縦のエッジを検出し、別のフィルタは横のエッジを検出するようになります。深い層に進むほど、エッジの組み合わせからより複雑なパターン(目、耳、顔全体など)を検出できるようになります。

14.3.3 プーリング

プーリング(Pooling)は、特徴マップのサイズを縮小する操作です。最もよく使われるのは 最大プーリング(MaxPooling)であり、一定の範囲内で最大値を取り出します。例えば、2×2 の MaxPooling では、2×2 の範囲から最大値を 1 つ選ぶため、特徴マップのサイズが縦横それぞれ半分になります。

プーリングにより、計算量が減るだけでなく、対象物の位置が多少ずれても同じ特徴として捉えられるようになります(位置不変性の向上)。

Playground:CNN の基礎

コードを書く前に、CNNの基礎 の Playground で「入力 → 畳み込み → プーリング」の流れを目で追っておくと、後の項の Conv2D クラスと MaxPooling2D クラスの対応が掴みやすくなります。カーネルを切り替えると、そのカーネルが反応した場所(縦線・横線・丸みなど)だけが特徴マップ上で白く光ります。プーリングを最大/平均で切り替えて、特徴マップのサイズがどれだけ削減されるかも確認してみましょう。

14.3.4 CNNの典型的な構造

実装では、畳み込みを Conv2D クラス、プーリングを MaxPooling2D クラスという層で記述します。特徴抽出のあとは Flatten クラスで 2 次元の特徴マップを 1 次元配列に変換し、全結合層(Dense クラス)で分類します。

14.3.5 KerasによるCNNの実装

前節と同じ MNIST データセットを、今度は CNN で分類します。データは同じで、手法だけが異なります。CNN モデルを構築し、前節の全結合ネットワークと精度を比較しましょう。

まず、CNN に入力するためにデータの形状を変更します。CNN では、画像 1 枚を「高さ、幅、チャンネル数」で表される 3 次元配列の形式で扱います。MNIST はグレースケール画像なのでチャンネル数は 1 です。RGB 画像であればチャンネル数は 3 になります。データセット全体は (サンプル数, 高さ, 幅, チャンネル数) の 4 次元配列として表現されます。

from tensorflow.keras.datasets import mnist

# データの再読み込みと前処理
(X_train, y_train), (X_test, y_test) = mnist.load_data()
X_train = X_train / 255.0
X_test = X_test / 255.0

# CNNの入力形状に変換: (サンプル数, 高さ, 幅, チャンネル数)
X_train_cnn = X_train.reshape(-1, 28, 28, 1)
X_test_cnn = X_test.reshape(-1, 28, 28, 1)

print(f"CNN入力の形状: {X_train_cnn.shape}")
Output
CNN入力の形状: (60000, 28, 28, 1)

次に、CNN モデルを構築します。

from tensorflow.keras.models import Sequential
from tensorflow.keras.layers import (
    Input, Conv2D, MaxPooling2D, Flatten, Dense
)

# CNNモデルの構築
cnn_model = Sequential([
    Input(shape=(28, 28, 1)),
    Conv2D(32, (3, 3), activation="relu"),
    MaxPooling2D((2, 2)),
    Conv2D(64, (3, 3), activation="relu"),
    MaxPooling2D((2, 2)),
    Flatten(),
    Dense(64, activation="relu"),
    Dense(10, activation="softmax")
])

cnn_model.summary()
Output
Model: "sequential_1"
 Layer (type)                    Output Shape           Param #
 conv2d (Conv2D)                 (None, 26, 26, 32)     320
 max_pooling2d (MaxPooling2D)    (None, 13, 13, 32)     0
 conv2d_1 (Conv2D)               (None, 11, 11, 64)     18,496
 max_pooling2d_1 (MaxPooling2D)  (None, 5, 5, 64)       0
 flatten (Flatten)               (None, 1600)           0
 dense_3 (Dense)                 (None, 64)             102,464
 dense_4 (Dense)                 (None, 10)             650
 Total params: 121,930 (476.29 KB)
 Trainable params: 121,930 (476.29 KB)
 Non-trainable params: 0 (0.00 B)

モデルの構造を確認します。

14.3.6 CNNモデルの学習と評価

# コンパイルと学習
cnn_model.compile(
    optimizer="adam",
    loss="sparse_categorical_crossentropy",
    metrics=["accuracy"]
)

cnn_history = cnn_model.fit(
    X_train_cnn, y_train,
    epochs=5,
    batch_size=64,
    validation_split=0.2
)
Output
Epoch 1/5
750/750 [====] - 5s 7ms/step
 - accuracy: 0.9395 - loss: 0.2071 - val_accuracy: 0.9818 - val_loss: 0.0650
...
Epoch 5/5
750/750 [====] - 4s 6ms/step
 - accuracy: 0.9918 - loss: 0.0257 - val_accuracy: 0.9872 - val_loss: 0.0455
# テストデータで評価
test_loss, test_accuracy = cnn_model.evaluate(X_test_cnn, y_test)
print(f"CNNのテスト精度: {test_accuracy:.4f}")
Output
313/313 [====] - 0s 1ms/step
 - accuracy: 0.9885 - loss: 0.0360
CNNのテスト精度: 0.9885

14.3.7 全結合ネットワークとCNNの比較

結果を表にまとめます。

モデル テスト精度 エポック数
全結合ネットワーク(Dense) 97.48% 10
畳み込みニューラルネットワーク(CNN) 98.85% 5

この結果では、CNN は全結合ネットワークよりも少ないエポック数で高い精度を達成しています。画像の空間構造を活かして特徴を抽出できる CNN の性質が、この結果に表れていると考えられます。

14.3.8 カラー画像の分類:CIFAR-10

MNIST は 28×28 ピクセルの白黒画像でしたが、現実の画像認識で扱う画像の多くはカラーであり、被写体の形や向き、背景もさまざまです。ここでは CIFAR-10 データセット(32×32 ピクセルのカラー画像。飛行機・自動車・鳥・猫・鹿・犬・カエル・馬・船・トラックの 10 クラス、訓練 50,000 枚・テスト 10,000 枚)を使って、より現実に近いカラー画像の分類に挑戦してみましょう。

ダウンロードに時間がかかる場合

cifar10.load_data() は、初回にトロント大学のサーバから約 170MB のファイルをダウンロードします。混雑していて時間がかかる場合は、先に次のコードを実行すると、本書の Web サイトに置いたコピーから取得できます。cifar10.load_data() は、Keras のキャッシュ(~/.keras/datasets)にファイルがあればダウンロードを省きます。

# CIFAR-10 を本書の Web サイトから取得し、Keras のキャッシュに置く
import os
from urllib.request import urlopen

cache = os.path.expanduser("~/.keras/datasets")
os.makedirs(cache, exist_ok=True)
target = os.path.join(cache, "cifar-10-batches-py-target_archive")
if not os.path.exists(target):
    url = "https://ml.kano.ac/chapters/data/cifar-10/cifar-10-python.tar.gz"
    with open(target, "wb") as f:
        for part in (".part1", ".part2"):  # 2 つに分けて置いてある
            f.write(urlopen(url + part).read())
from tensorflow.keras.datasets import cifar10

# データの読み込み(初回はダウンロードに時間がかかる)
(X_train, y_train), (X_test, y_test) = cifar10.load_data()

print(f"訓練データ: {X_train.shape}, ラベル: {y_train.shape}")
print(f"テストデータ: {X_test.shape}, ラベル: {y_test.shape}")
Output
訓練データ: (50000, 32, 32, 3), ラベル: (50000, 1)
テストデータ: (10000, 32, 32, 3), ラベル: (10000, 1)

カラー画像なのでチャンネル数は 3(RGB)であり、データは最初から (サンプル数, 高さ, 幅, チャンネル数) の 4 次元配列になっています。MNIST のときのような形状変換は不要で、そのまま CNN に入力できます。サンプル画像を表示してみると、解像度が低く、人間の目でも判別に迷うような画像が含まれていることがわかります。

14.3.5 項で構築した MNIST 用 CNN と同じ構造のモデルを構築し、学習します。変更するのは入力形状を (32, 32, 3) にする点だけです。

from tensorflow.keras.models import Sequential
from tensorflow.keras.layers import (
    Input, Conv2D, MaxPooling2D, Flatten, Dense
)

# 画素値を0〜1に正規化
X_train = X_train / 255.0
X_test = X_test / 255.0

# MNISTのCNNと同じ構造(入力形状のみ変更)
cifar_model = Sequential([
    Input(shape=(32, 32, 3)),
    Conv2D(32, (3, 3), activation="relu"),
    MaxPooling2D((2, 2)),
    Conv2D(64, (3, 3), activation="relu"),
    MaxPooling2D((2, 2)),
    Flatten(),
    Dense(64, activation="relu"),
    Dense(10, activation="softmax")
])

cifar_model.compile(
    optimizer="adam",
    loss="sparse_categorical_crossentropy",
    metrics=["accuracy"]
)

cifar_history = cifar_model.fit(
    X_train, y_train,
    epochs=10,
    batch_size=64,
    validation_split=0.2
)
Output
Epoch 1/10
625/625 [====] - 6s 9ms/step
 - accuracy: 0.4326 - loss: 1.5735 - val_accuracy: 0.5429 - val_loss: 1.2996
Epoch 2/10
625/625 [====] - 5s 9ms/step
 - accuracy: 0.5735 - loss: 1.2096 - val_accuracy: 0.6072 - val_loss: 1.1231
...
Epoch 10/10
625/625 [====] - 6s 9ms/step
 - accuracy: 0.7608 - loss: 0.6826 - val_accuracy: 0.6958 - val_loss: 0.9010
# テストデータで評価
test_loss, test_accuracy = cifar_model.evaluate(X_test, y_test)
print(f"CIFAR-10のテスト精度: {test_accuracy:.4f}")
Output
313/313 [====] - 1s 2ms/step
 - accuracy: 0.6865 - loss: 0.9121
CIFAR-10のテスト精度: 0.6865

今回のモデルと学習条件では、MNIST で 99% 近くに達していた精度が、同じ構造の CNN でも CIFAR-10 では約 69% にとどまりました。カラーで被写体が多様な画像の分類は、手書き数字よりもはるかに難しい課題であることがわかります。このように課題が難しくなると、小さな CNN をそのまま適用するだけでは限界があり、層を深くする、訓練データに回転や反転などの変換を加えて水増しする データ拡張(Data Augmentation)を行うといった工夫が必要になります。このうち、層を深くする工夫を突き詰めたものが、次に紹介する有名な CNN アーキテクチャです。

14.3.9 有名なCNNアーキテクチャと転移学習

実際の画像認識タスクでは、ゼロから CNN を設計するよりも、大規模なデータセットで事前に学習された 有名なCNNアーキテクチャ を活用するのが一般的です。

  • VGG(2014年):シンプルな構造で理解しやすい。3×3 の小さなフィルタを多数積み重ねる
  • ResNet(2015年):残差接続(Skip Connection)を導入し、非常に深いネットワーク(100 層以上)の学習を可能にした
  • EfficientNet(2019年):ネットワークの深さ・幅・解像度を効率的にスケーリングする手法

これらのモデルを ImageNet(100 万枚以上の画像、1,000 カテゴリ)で事前に学習したものが公開されており、自分のタスクに合わせて最後の層だけを再学習する 転移学習(Transfer Learning)という手法が広く使われています。転移学習を使えば、少量のデータでも高い精度を達成できます。

14.4 テキストにおける深層学習:Transformer

前章までに学んだ BoW や TF-IDF は、単語の出現頻度に基づく自然言語処理手法であり、単語の順序 を考慮できないという大きな制限がありました。例えば、「犬が猫を追いかけた」と「猫が犬を追いかけた」は BoW では同じベクトルになってしまいます。また、第13章の最後では、文脈を考慮した Embedding を生成できる Sentence Transformers を使い、文書分類の精度が大きく向上することを確認しました。本節では、その基盤となっている Transformer アーキテクチャの仕組みを学んだ後、Hugging Face の transformers ライブラリを使って実際に動かします。

14.4.1 RNN/LSTMからTransformerへ

単語の順序を考慮するために、深層学習の分野では RNN(Recurrent Neural Network; 再帰型ニューラルネットワーク)が長らく使われてきました。RNN は文を先頭から 1 単語ずつ順に処理し、それまでの文脈を「隠れ状態」として保持する仕組みです。しかし、RNN には以下の問題がありました。

  • 長い文では、文の先頭の情報が徐々に薄れてしまう(長距離依存の問題)
  • 1 単語ずつ順番に処理するため、並列計算ができず処理が遅い

RNN の改良版である LSTM(Long Short-Term Memory)により長距離依存の問題はある程度解決されましたが、逐次処理の遅さは残りました。

14.4.2 TransformerとAttention機構

Transformer は、2017 年に Google の研究チームが論文「Attention Is All You Need」で提案したニューラルネットワークのアーキテクチャです。Transformer の最大の特徴は、Attention機構(注意機構)と呼ばれる仕組みにより、入力系列中の各要素について、他のすべての要素との関連度を計算できることです。特に、同じ文の中の単語どうしで Attention を計算することを 自己注意(Self-Attention)と呼びます。

たとえば、「彼の計画は甘いと指摘された」という文において、Attention 機構は「甘い」という単語が「計画」「指摘された」と強い関連を持つことを学習できます。これにより、この「甘い」が味覚ではなく「見通しが楽観的すぎる」という意味であることを文脈から判断できます。

graph LR
    A("入力文<br>(単語列)") --> B("埋め込み + 位置情報<br>各単語をベクトルに変換")
    B --> C("自己注意 ×N層<br>各単語が文中の他の<br>すべての単語を参照")
    C --> D("文脈を反映した<br>各単語のベクトル")

    classDef data fill:#D9EAF3,stroke:#0072B2,stroke-width:2px,color:#000
    classDef proc fill:#D9F0EA,stroke:#009E73,stroke-width:2px,color:#000
    classDef model fill:#F7EBF2,stroke:#CC79A7,stroke-width:2px,color:#000
    classDef out fill:#F5ECE2,stroke:#BF8040,stroke-width:2px,color:#000
    class A data
    class B proc
    class C model
    class D out

RNN が文を先頭から 1 語ずつ順に処理するのに対し、Transformer は文中のすべての単語のペアについて Attention の計算を同時に行えます。これにより並列計算による学習の高速化と長い文脈の扱いが可能になり、後述する大規模言語モデルをはじめ、現在の自然言語処理モデルのほとんどが Transformer を基盤としています。

Playground:Attention

Attention の Playground では、Transformer の心臓部である自己注意機構の重み行列を可視化しています。各単語が自分の クエリ(何を探しているか)と相手の キー(何を持っているか)の内積を取り、softmax で重みに変えるため、行ごとの合計は必ず 1 になります。マスにホバーすると、「読んでいる」が「本」を、「甘い」が「りんご」を見つける対応関係が浮かび上がります。\(\sqrt{d_k}\) でのスケーリングを切り替えて、その効果も試してみましょう。

14.4.3 BERT

BERT(Bidirectional Encoder Representations from Transformers)は、2018 年に Google が発表した、Transformer を基盤とする言語モデルです。BERT の最大の特徴は、双方向(bidirectional)に文脈を考慮できることです。

左から右(または右から左)へ読む一方向の言語モデルでは片側の文脈だけを使うのに対し、BERT は文の両方向から同時に文脈を理解します。たとえば、「彼は__で昼食を食べた」という文の空欄を予測する場合、BERT は空欄の前後両方の情報を使って予測を行います。

BERT の重要な概念に、事前学習(pre-training)と ファインチューニング(fine-tuning)があります。

  1. 事前学習:大量のテキストデータ(Wikipedia 全文など)を用いて、言語の一般的な知識を学習します。この段階では、特定のタスク(分類や翻訳など)を意識せず、言語そのものの理解を深めます。
  2. ファインチューニング:事前学習済みのモデルを、特定のタスク(たとえば感情分析や質問応答)に特化させるために、少量のラベル付きデータで追加学習を行います。

この 2 段階のアプローチにより、大量のラベルなしデータから言語の知識を学び、少量のラベル付きデータで高い精度を達成できます。

第13章で学んだ Word2Vec と、BERT の主な違いを次の表に示します。

特徴 Word2Vec BERT
発表年 2013年 2018年
ベースアーキテクチャ 浅いニューラルネットワーク Transformer
文脈の考慮 できない(静的ベクトル) できる(動的ベクトル)
多義語の扱い 1単語1ベクトル 文脈に応じて変化
表現の単位 単語のみ 単語・文・文書
学習方法 自己教師あり(周辺語予測) 事前学習+ファインチューニング

第 13 章で利用した Sentence Transformers は、この BERT などの事前学習済みモデルを、文単位の Embedding 生成に特化させたライブラリです。以降では、事前学習済みモデルをより幅広いタスクに活用する方法を見ていきます。

14.4.4 Hugging Face transformersライブラリ

Hugging Face は、事前学習済みモデルを簡単に利用できるライブラリ transformers を提供しています。数行のコードで BERT などの強力なモデルを活用できます。TensorFlow と同様、Google Colab には標準でインストールされていますが、ローカル環境で使用する場合は以下のコマンドでインストールします。

# Google Colabでは不要(プリインストール済み)
# ローカル環境の場合のみ実行
# !pip install transformers

14.4.5 パイプラインによる感情分析

Hugging Face の pipeline 機能を使えば、事前学習済みモデルを使った推論を数行で実行できます。ここでは、英語テキストの感情分析を行います。

from transformers import pipeline

# 感情分析パイプラインの作成
classifier = pipeline("sentiment-analysis")

# 英語テキストの感情分析
texts = [
    "I love this movie! It was absolutely wonderful.",
    "This product is terrible. I want a refund.",
    "The weather is okay today."
]
results = classifier(texts)

for text, result in zip(texts, results):
    print(f"{text}: {result['label']} ({result['score']:.4f})")
Output
I love this movie! It was absolutely wonderful.: POSITIVE (0.9999)
This product is terrible. I want a refund.: NEGATIVE (0.9998)
The weather is okay today.: POSITIVE (0.9998)

pipeline を使うと、事前学習済みモデルによる感情分析を数行で試せます。pipeline の内部では、事前学習済みの Transformer モデルがテキストを解析しています。なお、このようにモデル名を指定しない場合は、「No model was supplied...」という警告とともに既定の英語モデルが使用されますが、動作に問題はありません。

14.4.6 日本語テキストの感情分析

日本語の感情分析には、日本語で学習されたモデルを指定する必要があります。Hugging Face Model Hub には、さまざまな言語に対応した事前学習済みモデルが公開されています。

from transformers import pipeline

# 日本語感情分析モデルの利用
classifier_ja = pipeline(
    "sentiment-analysis",
    model="lxyuan/distilbert-base-multilingual-cased-sentiments-student"
)

# 日本語テキストの感情分析
texts_ja = [
    "この映画は最高でした!感動しました。",
    "サービスが悪くて非常に不満です。",
    "今日のランチは普通でした。"
]

results_ja = classifier_ja(texts_ja)

for text, result in zip(texts_ja, results_ja):
    print(f"{text}")
    print(f"  → {result['label']} ({result['score']:.4f})")
    print()
Output
この映画は最高でした!感動しました。
  → positive (0.9795)

サービスが悪くて非常に不満です。
  → negative (0.9312)

今日のランチは普通でした。
  → neutral (0.4548)

3 文目は neutral が最も高いものの、スコアは約 0.45 にとどまり、ほかのクラスにもスコアが割り振られていることがわかります。このモデルは positive・negative・neutral の 3 クラスに確率を割り振るため、はっきりした感情を持たない文では確率が分散しやすいと考えられます。

14.4.7 テキスト分類の実践

pipeline は幅広いタスクに対応しています。ゼロショット分類(タスク固有の追加学習なしでカテゴリを指定して分類)の例を見てみましょう。

from transformers import pipeline

# ゼロショット分類
zero_shot = pipeline(
    "zero-shot-classification",
    model="facebook/bart-large-mnli"
)

text = "Apple announced a new iPhone model with improved camera."
candidate_labels = ["technology", "sports", "politics", "entertainment"]

result = zero_shot(text, candidate_labels)

for label, score in zip(result["labels"], result["scores"]):
    print(f"  {label}: {score:.4f}")
Output
  technology: 0.9550
  entertainment: 0.0313
  sports: 0.0097
  politics: 0.0040

事前にカテゴリごとの訓練データを用意しなくても、テキストの分類ができています。これが、大規模な事前学習モデルの強力さです。

14.5 生成AIの活用

前節までに学んだニューラルネットワークを大規模化し、膨大なデータで学習させたモデルのうち、テキストや画像などのコンテンツを新たに生成できるものは 生成AI(Generative AI)と呼ばれます。ChatGPT や Claude などがその代表例です。本節では、その中核である大規模言語モデルを中心に、仕組みと API による利用方法、プロンプトの工夫、そして利用上の注意点を順に扱います。

14.5.1 生成AIとは

本書でこれまで扱ってきた分類や回帰のモデルが入力を判別するのに対し、生成 AI は入力(プロンプト)を手がかりに出力そのものを組み立てます。両者の違いを次の表に整理します。

項目 判別AI 生成AI
目的 入力の分類・予測 新しいコンテンツの生成
入力 データ プロンプト(指示文)
出力 カテゴリ・数値 テキスト・画像など
例 スパム判定、画像分類 文章生成、画像生成

14.5.2 大規模言語モデル(LLM)

大規模言語モデル(Large Language Model; LLM)は、大量のテキストデータで学習された Transformer ベースの巨大なモデルです。代表的な LLM には以下のものがあります。

  • GPTシリーズ(OpenAI):ChatGPT のベースとなっているモデル
  • Claudeシリーズ(Anthropic):安全性に配慮して設計された LLM
  • Geminiシリーズ(Google):検索をはじめとする同社の各サービスと統合された LLM
  • オープンウェイトモデル:Llama(Meta)や Qwen(Alibaba)、DeepSeek など、モデルの重みが公開されており、手元の計算機でも動かせる LLM

LLM は「次の単語を予測する」という学習を大規模に行うことで、文法、知識、推論能力など、言語に関する幅広い能力を獲得しています。現在の主要な LLM は、テキストに加えて画像や音声も扱える マルチモーダル なモデルであることが一般的です。また、回答を出力する前に内部で思考の過程を組み立てる「推論」の仕組みを備えたモデルが標準になり、数学やプログラミングのような複雑な課題の精度が大きく向上しています。

Playground:テキスト生成

テキスト生成 の Playground では、「次の単語を予測する」だけで文ができる様子を、ごく小さな bigram 言語モデルで体験できます。1 単語ずつ生成すると、次の単語の確率分布がバーで表示されます。temperature を下げると分布が尖っていつも同じ単語が選ばれ、上げるとなだらかになって意外な単語も出ます。LLM も規模はまるで違えど、確率分布から次の単語を選ぶ点は同じです。

14.5.3 APIを使ったテキスト生成

LLM は API を通じて簡単に利用できます。ここでは、OpenAI の API を使った例を示します。API を利用するには API キーが必要です。

# Google Colabでは不要(プリインストール済み)
# ローカル環境の場合のみ実行
# !pip install openai
from openai import OpenAI
import getpass
import os

# APIキーを画面に表示せず入力し、環境変数に設定
if "OPENAI_API_KEY" not in os.environ:
    os.environ["OPENAI_API_KEY"] = getpass.getpass("OpenAI API key: ")

# 環境変数 OPENAI_API_KEY を自動的に読み込む
client = OpenAI()

# テキスト生成
response = client.responses.create(
    model="gpt-5-mini",
    instructions="あなたは親切なアシスタントです。",
    input="機械学習とは何かを3行で説明してください。"
)

print(response.output_text)
Output
機械学習とは、コンピュータがデータからパターンやルールを自動的に学習する技術です。
明示的にプログラムしなくても、大量のデータを分析して予測や判断ができるようになります。
画像認識、自然言語処理、推薦システムなど、幅広い分野で活用されています。

instructions にはモデルの振る舞い、input パラメータにはユーザからの入力を指定します。生成された本文は response.output_text で取得できます。LLM の出力は実行のたびに変わるため、上の出力は一例です。

APIキーと利用料金

上のコードを実行するには OpenAI API キーが必要で、API 利用には料金が発生します。入力したキーはノートブックへ保存せず、共有前に出力や実行状態に残っていないことを確認してください。また、利用できるモデル名は更新されるため、実行時には公式ドキュメントで確認してください。

LLM API の活用

大規模言語モデル(LLM)API は、以下のような用途に活用できます。

  • 文章生成: レポート、メール、記事の自動生成
  • 質問応答: チャットボット、FAQシステム
  • 翻訳: 多言語翻訳
  • 要約: 長文の要約
  • コード生成: プログラムコードの生成・説明

Web補足: 機械学習APIの基本

ここからは書籍にない Web 版だけの補足として、LLM に限らない機械学習 API 全般の使い方を紹介します。

APIの基本概念

API(Application Programming Interface) は、ソフトウェア同士が通信するための仕組みです。機械学習APIを使うことで、クラウド上の学習済みモデルを呼び出して予測を行えます。

graph LR
    A("あなたのアプリ") -->|リクエスト| B("API")
    B -->|処理| C("機械学習モデル")
    C -->|結果| B
    B -->|レスポンス| A

    classDef proc fill:#D9F0EA,stroke:#009E73,stroke-width:2px,color:#000
    classDef model fill:#F7EBF2,stroke:#CC79A7,stroke-width:2px,color:#000
    classDef neu fill:#F2F2F2,stroke:#8C8C8C,stroke-width:2px,color:#000
    class A neu
    class B proc
    class C model

APIを使うメリット

メリット 説明
手軽さ モデルの構築・学習が不要
高精度 大規模データで学習された高性能モデル
スケーラビリティ サーバ側でスケール
最新技術 最新のAI技術をすぐに利用可能

主要な機械学習API

画像系API

  • Google Cloud Vision API
  • Amazon Rekognition
  • Azure Computer Vision

テキスト系API

  • OpenAI API(GPT)
  • Google Cloud Natural Language API
  • Amazon Comprehend

音声系API

  • Google Cloud Speech-to-Text
  • Amazon Transcribe
  • Azure Speech Services

REST APIの仕組み

多くの機械学習APIは REST API として提供されています。

graph LR
    A("クライアント") -->|HTTPリクエスト| B("APIサーバ")
    B -->|HTTPレスポンス| A

    A --> A1("GET: データ取得")
    A --> A2("POST: データ送信")

    classDef proc fill:#D9F0EA,stroke:#009E73,stroke-width:2px,color:#000
    classDef neu fill:#F2F2F2,stroke:#8C8C8C,stroke-width:2px,color:#000
    class A neu
    class B,A1,A2 proc

Python での API 呼び出し

Python の requests ライブラリを使って API を呼び出します。

import requests

# API エンドポイント
url = "https://api.example.com/predict"

# リクエストデータ
data = {
    "text": "This is a sample text."
}

# ヘッダー(認証情報など)
headers = {
    "Authorization": "Bearer YOUR_API_KEY",
    "Content-Type": "application/json"
}

# API 呼び出し
response = requests.post(url, json=data, headers=headers)

# レスポンスの確認
if response.status_code == 200:
    result = response.json()
    print(result)
else:
    print(f"エラー: {response.status_code}")

APIキーの管理

APIキーの取り扱い

APIキーは秘密情報です。以下の点に注意してください。

  • コードに直接書かない: 環境変数や設定ファイルを使用
  • Gitにコミットしない: .gitignore に追加
  • 公開しない: GitHubなどに公開しない
import os

# 環境変数から取得(推奨)
api_key = os.environ.get("API_KEY")

# または .env ファイルを使用
from dotenv import load_dotenv
load_dotenv()
api_key = os.getenv("API_KEY")

Web補足: 画像・テキストAPIの活用例

画像認識APIの概要

画像認識APIを使うと、以下のようなタスクが簡単に実現できます。

  • 物体検出: 画像内の物体を検出・分類
  • 顔検出: 顔の位置や表情を検出
  • OCR: 画像内のテキストを抽出
  • ラベル付け: 画像の内容を自動でタグ付け

画像分類APIの使用例

import requests
import base64

# 画像を Base64 エンコード
def encode_image(image_path):
    with open(image_path, "rb") as f:
        return base64.b64encode(f.read()).decode("utf-8")

# 画像のエンコード
image_base64 = encode_image("sample.jpg")

# API リクエスト
url = "https://api.example.com/vision/classify"
headers = {
    "Authorization": "Bearer YOUR_API_KEY",
    "Content-Type": "application/json"
}
data = {
    "image": image_base64
}

response = requests.post(url, json=data, headers=headers)
result = response.json()

# 結果の表示
for label in result["labels"]:
    print(f"{label['name']}: {label['confidence']:.2%}")

顔検出APIの使用例

# 顔検出 API の呼び出し例
url = "https://api.example.com/vision/faces"
data = {
    "image": image_base64,
    "return_attributes": ["age", "emotion", "gender"]
}

response = requests.post(url, json=data, headers=headers)
result = response.json()

# 検出された顔の情報を表示
for face in result["faces"]:
    print(f"年齢: {face['age']}")
    print(f"性別: {face['gender']}")
    print(f"感情: {face['emotion']}")
    print("---")

テキスト分析APIの概要

テキスト分析APIを使うと、以下のようなタスクが実現できます。

  • 感情分析: テキストのポジティブ/ネガティブを判定
  • エンティティ抽出: 人名、地名、組織名などを抽出
  • キーフレーズ抽出: 重要なフレーズを抽出
  • 言語検出: テキストの言語を判定
  • 要約: 長文を要約

感情分析APIの使用例

# 感情分析 API の呼び出し例
url = "https://api.example.com/text/sentiment"
data = {
    "text": "この製品は素晴らしいです!大満足しています。"
}

response = requests.post(url, json=data, headers=headers)
result = response.json()

print(f"感情: {result['sentiment']}")
print(f"ポジティブスコア: {result['positive_score']:.2%}")
print(f"ネガティブスコア: {result['negative_score']:.2%}")

Web補足: ローカルLLMサーバ(LM Studio)の活用

外部 API の代わりに、LM Studio などで運用されるローカル LLM サーバを使って LLM 機能を試すこともできます。LM Studio の API は OpenAI 互換 なので、エンドポイントの形も前述の OpenAI API とほぼ同じです。授業でこのテキストを使用している場合は、教員が運用するサーバ(URL は授業でアナウンスされます)を利用できます。

項目 内容
サーバ URL 授業でアナウンスされる URL(https://~.trycloudflare.com)
チャットモデル qwen/qwen3.5-9b(日本語に強い 9B モデル)
API エンドポイント POST /v1/chat/completions (OpenAI 互換)
import requests

LLM_URL = "https://xxxxxxxx.trycloudflare.com"  # 授業でアナウンスされる URL に置き換える
CHAT_MODEL = "qwen/qwen3.5-9b"

response = requests.post(
    f"{LLM_URL}/v1/chat/completions",
    json={
        "model": CHAT_MODEL,
        "messages": [
            {"role": "system", "content": "あなたは親切なアシスタントです。"},
            {"role": "user", "content": "機械学習とは何ですか?簡潔に説明してください。"},
        ],
        "stream": False,
    },
)

result = response.json()
print(result["choices"][0]["message"]["content"])

授業用サーバの現在の構成では、学生側で外部APIのキーや利用料金を用意せずに試せます。応答時間はサーバの機材、利用人数、モデルによって変わります。サーバ停止中は利用できず、使えるモデルも運用者の設定に依存します。

レスポンスの形が OpenAI と同じ

チャットでは result["choices"][0]["message"]["content"] に応答本文が入ります。これは OpenAI の Chat Completions API と同系統の形式ですが、対応するパラメータや挙動はサーバ実装とバージョンによって異なります。

ローカル LLM と外部 API のどちらを使うか

  • 授業内の実験:授業用サーバなら環境構築を減らせる
  • 個人での継続利用:自分で運用するローカルモデルか外部APIを選ぶ
  • 本番運用:可用性、費用、データ管理、保守体制を比較して決める

まずローカル LLM で「LLM API はこう叩く」を体感し、その後で OpenAI などの外部 API も同じ呼び出しパターンで使えることを学ぶのが効率的です。

感情分析プロンプトの例

LLM に「ポジティブかネガティブか」を判定させることもできます。チャット形式で指示するだけで分類器として動きます。

import requests
import json

LLM_URL = "https://xxxxxxxx.trycloudflare.com"  # 授業でアナウンスされる URL に置き換える
CHAT_MODEL = "qwen/qwen3.5-9b"

# 出力させたい JSON の構造をスキーマで定義
SENTIMENT_SCHEMA = {
    "type": "json_schema",
    "json_schema": {
        "name": "sentiment",
        "strict": True,
        "schema": {
            "type": "object",
            "properties": {
                "label": {"type": "string", "enum": ["positive", "negative"]},
                "confidence": {"type": "number"},
            },
            "required": ["label", "confidence"],
        },
    },
}

def classify_sentiment(text):
    response = requests.post(
        f"{LLM_URL}/v1/chat/completions",
        json={
            "model": CHAT_MODEL,
            "messages": [
                {
                    "role": "system",
                    "content": "あなたは日本語の感情分析モデルです。入力テキストが positive か negative かを判定してください。",
                },
                {"role": "user", "content": text},
            ],
            "stream": False,
            "response_format": SENTIMENT_SCHEMA,
        },
    )
    return json.loads(response.json()["choices"][0]["message"]["content"])

result = classify_sentiment("この製品は素晴らしいです!大満足しています。")
print(result)
Output
{'label': 'positive', 'confidence': 0.98}

response_format に json_schema を指定すると、LM Studio がモデルの出力を 指定した構造の JSON に強制 してくれるので、機械的に扱えます。事前にベクトル化・分類器学習をしなくても、LLM のプロンプトだけで分類が成立するのが面白いところです。

Web補足: APIの実践的な活用

エラーハンドリング

API 呼び出しではエラーが発生する可能性があるため、適切なエラーハンドリングが重要です。

import requests
from requests.exceptions import RequestException

# リトライ機能付き API 呼び出し
def call_api(url, data, headers, max_retries=3):
    for attempt in range(max_retries):
        try:
            response = requests.post(url, json=data, headers=headers, timeout=30)
            response.raise_for_status()  # HTTPエラーをチェック
            return response.json()
        except RequestException as e:
            print(f"試行 {attempt + 1}/{max_retries} 失敗: {e}")
            if attempt == max_retries - 1:
                raise
    return None

レート制限への対応

多くの API にはレート制限(一定時間内の呼び出し回数制限)があります。

import time

# レート制限を考慮した API 呼び出し
def call_api_with_rate_limit(items, api_func, calls_per_minute=60):
    results = []
    interval = 60.0 / calls_per_minute

    for i, item in enumerate(items):
        result = api_func(item)
        results.append(result)

        if i < len(items) - 1:
            time.sleep(interval)

    return results

バッチ処理

大量のデータを処理する場合は、バッチ処理が効率的です。

# バッチ処理
def batch_process(items, batch_size=10):
    results = []

    for i in range(0, len(items), batch_size):
        batch = items[i:i + batch_size]
        # バッチ単位で API 呼び出し
        batch_results = call_api_batch(batch)
        results.extend(batch_results)

    return results

14.5.4 プロンプトエンジニアリング

LLM の出力品質は、入力する プロンプト(指示文)の書き方に大きく左右されます。出力を意図に近づけるための指示の工夫を プロンプトエンジニアリング と呼びます。基本的なテクニックを以下にまとめます。

テクニック 説明
具体的な指示 「要約してください」ではなく「3つの箇条書きで100文字以内に要約してください」のように具体的に指示する
役割の設定 「あなたはデータサイエンティストです」のように役割を与えることで、専門的な回答を引き出す
例の提示 入力と出力の例を1〜2個示すことで、期待する出力形式を伝える(Few-shot学習)
手順の指定 必要な観点や出力手順を明示し、回答の抜けや形式のずれを減らす

実際に、同じ「リスト内包表記」という話題について、プロンプトの書き方だけを変えて出力の違いを見てみましょう。

# 曖昧なプロンプト
response1 = client.responses.create(
    model="gpt-5-mini",
    input="リスト内包表記について教えてください。"
)
print("【曖昧なプロンプト】")
print(response1.output_text[:200])
print("...")

# 具体的なプロンプト
response2 = client.responses.create(
    model="gpt-5-mini",
    instructions="あなたはプログラミング講師です。初心者向けに説明してください。",
    input="Pythonのリスト内包表記について、具体例を1つ含めて"
          "3行以内で説明してください。"
)
print("\n【具体的なプロンプト】")
print(response2.output_text)
Output
【曖昧なプロンプト】
リスト内包表記(list comprehension)とは、Pythonにおいてリストを簡潔に生成するための
構文です。基本の形は [式 for 変数 in イテラブル] で、if による条件の指定や、for の
入れ子など、さまざまな書き方があります。まず基本形から説明します。リスト内包表記は…
...

【具体的なプロンプト】
リスト内包表記は、リストを簡潔に作成するための書き方です。
例えば、`[x**2 for x in range(5)]` と書くと `[0, 1, 4, 9, 16]` が得られます。
for文を1行にまとめたものと考えるとわかりやすいです。

この実行例では、曖昧なプロンプトでは網羅的で長い説明が始まってしまった(先頭 200 文字のみ表示しています)のに対し、役割と制約を指定した具体的なプロンプトでは、目的に合った簡潔な回答が得られました。

14.5.5 生成AIの倫理と限界

生成 AI の出力をそのまま採用すると、事実誤認や権利上の問題を見落とす可能性があります。利用時は次の点を確認します。

生成AI利用上の注意点

  • ハルシネーション:事実と異なる内容を、もっともらしい体裁で出力することがあります。生成された情報は必ず一次資料で裏を取ります
  • 著作権:生成物の権利の帰属や、学習データに含まれる著作物の扱いについては、法整備が追いついていない部分があります
  • バイアス:学習データの偏りが出力に反映され、特定の属性に不公平な結果を生むことがあります
  • プライバシー:個人情報や機密情報をプロンプトに入力すると外部へ送信されるため、入力内容には注意します
  • 悪用のリスク:フェイクニュースやなりすましなど、意図的な悪用の手段にもなり得ます

これらは生成 AI を使わない理由ではなく、使ううえで前提とすべき制約です。出力をそのまま信用せず、用途に応じて人間が検証する運用を組み込むことが欠かせません。

14.5.6 深層学習と生成AIの今後

生成 AI の研究は急速に進展しています。テキスト・画像・音声を 1 つのモデルで扱うマルチモーダル化のように、少し前まで「今後の方向」とされていたものが、すでに標準になった例も少なくありません。現在特に活発な方向をいくつか挙げます。

  • AIエージェント:モデルが外部のツールを呼び出し、複数の手順からなるタスクを自律的に進める。プログラミング支援などの分野ではすでに実用段階にあり、外部ツールとの接続を標準化する MCP(Model Context Protocol)のような共通規格も広がっている
  • 推論の強化:回答の前に思考の過程をどれだけ深く組み立てるかを、課題の難しさに応じて調整する
  • 小型化・オープン化:量子化や蒸留により少ない計算資源で動くモデルを作る。重みが公開されたオープンウェイトモデルの性能は商用モデルに迫りつつある
  • 専門特化:医療・法律・教育など、分野固有の知識やルールに合わせて調整したモデル

ここで挙げた技術はいずれも、本書で扱った「入力を特徴としてどのように表し、何を最適化し、どう評価するか」という枠組みの延長線上にあります。新しいモデルが登場しても、この問いの立て方は変わりません。学んだ技術を組み合わせて、実際に動くアプリケーションに仕上げる方法は、付録で紹介しています。

14.6 演習問題

演習 14-1: 活性化関数の理解

演習 14-1

以下のコードを実行し、ReLU、シグモイド、ソフトマックスの各活性化関数の出力を確認してください。それぞれの関数がどのような変換を行っているか説明してください。

import numpy as np

x = np.array([-2.0, -1.0, 0.0, 1.0, 2.0])

# ReLU
relu = np.maximum(0, x)
print(f"ReLU: {relu}")

# シグモイド
sigmoid = 1 / (1 + np.exp(-x))
print(f"Sigmoid: {sigmoid}")

# ソフトマックス
softmax = np.exp(x) / np.sum(np.exp(x))
print(f"Softmax: {softmax}")
print(f"Softmax合計: {np.sum(softmax)}")
解答例
import numpy as np

x = np.array([-2.0, -1.0, 0.0, 1.0, 2.0])

# ReLU
relu = np.maximum(0, x)
print(f"ReLU: {relu}")

# シグモイド
sigmoid = 1 / (1 + np.exp(-x))
print(f"Sigmoid: {sigmoid}")

# ソフトマックス
softmax = np.exp(x) / np.sum(np.exp(x))
print(f"Softmax: {softmax}")
print(f"Softmax合計: {np.sum(softmax)}")
ReLU: [0. 0. 0. 1. 2.]
Sigmoid: [0.11920292 0.26894142 0.5        0.73105858 0.88079708]
Softmax: [0.01165623 0.03168492 0.08612854 0.23412166 0.63640865]
Softmax合計: 1.0

考察:

  • ReLU は負の値を0にし、正の値はそのまま通します(-2, -1, 0 → 0)。計算が単純で勾配消失が起きにくいので、隠れ層でよく使われます。
  • シグモイドはどんな値も0〜1の範囲に収めます。入力0で0.5になり、正に大きいほど1、負に大きいほど0に近づきます。2値分類の出力層で、出力を確率として使います。
  • ソフトマックスは複数の値をまとめて変換し、合計が1になる確率分布を作ります。入力が大きい要素ほど確率も大きくなります(2.0 → 0.636)。多クラス分類の出力層で、各クラスの確率を出すのに使います。

演習 14-2: Fashion-MNISTでのCNN

演習 14-2

以下のコードを使って、Fashion-MNIST データセットを読み込み、CNN モデルで分類を行ってください。テスト精度はいくつになるか確認してください。また、テストデータの先頭数枚について、予測結果を label_names のラベル名で表示してみましょう。

from tensorflow.keras.datasets import fashion_mnist

# データの読み込み
(X_train, y_train), (X_test, y_test) = fashion_mnist.load_data()
X_train = X_train / 255.0
X_test = X_test / 255.0
X_train = X_train.reshape(-1, 28, 28, 1)
X_test = X_test.reshape(-1, 28, 28, 1)

# ラベル名
label_names = ["Tシャツ", "ズボン", "セーター", "ドレス", "コート",
               "サンダル", "シャツ", "スニーカー", "バッグ", "ブーツ"]

# 本節のCNNモデルと同じ構造でモデルを構築し、学習・評価してください
解答例
import numpy as np
from tensorflow.keras.datasets import fashion_mnist
from tensorflow.keras.models import Sequential
from tensorflow.keras.layers import (
    Input, Conv2D, MaxPooling2D, Flatten, Dense
)

# データの読み込み(問題文のコード)
(X_train, y_train), (X_test, y_test) = fashion_mnist.load_data()
X_train = X_train / 255.0
X_test = X_test / 255.0
X_train = X_train.reshape(-1, 28, 28, 1)
X_test = X_test.reshape(-1, 28, 28, 1)

label_names = ["Tシャツ", "ズボン", "セーター", "ドレス", "コート",
               "サンダル", "シャツ", "スニーカー", "バッグ", "ブーツ"]

# 本節のCNNモデルと同じ構造
cnn_model = Sequential([
    Input(shape=(28, 28, 1)),
    Conv2D(32, (3, 3), activation="relu"),
    MaxPooling2D((2, 2)),
    Conv2D(64, (3, 3), activation="relu"),
    MaxPooling2D((2, 2)),
    Flatten(),
    Dense(64, activation="relu"),
    Dense(10, activation="softmax")
])
cnn_model.compile(
    optimizer="adam",
    loss="sparse_categorical_crossentropy",
    metrics=["accuracy"]
)
cnn_model.fit(X_train, y_train, epochs=5, batch_size=64,
              validation_split=0.2)

# テストデータでの評価
test_loss, test_acc = cnn_model.evaluate(X_test, y_test, verbose=0)
print(f"テスト精度: {test_acc:.4f}")

# 先頭5枚の予測結果をラベル名で表示
pred = cnn_model.predict(X_test[:5], verbose=0)
for i, p in enumerate(pred):
    print(f"{i+1}枚目: 予測 {label_names[np.argmax(p)]}, "
          f"正解 {label_names[y_test[i]]}")
テスト精度: 0.9006
1枚目: 予測 ブーツ, 正解 ブーツ
2枚目: 予測 セーター, 正解 セーター
3枚目: 予測 ズボン, 正解 ズボン
4枚目: 予測 ズボン, 正解 ズボン
5枚目: 予測 シャツ, 正解 シャツ

考察:テスト精度は 0.9006 でした(重みの初期値が乱数なので、実行ごとに少し変わります)。同じ構造の CNN は 14.3.6 項の MNIST で 0.9885 だったので、約9ポイント低くなります。Fashion-MNIST にはセーター、コート、シャツのように形の似たクラスがあり、手書き数字より難しいためです。データの形(28×28 のグレースケール、10クラス)が同じなので、モデルのコードは変えずに使えます。

演習 14-3: Hugging Faceパイプラインの活用

演習 14-3

Hugging Face の pipeline を使って、以下のタスクを実行してください。

  1. 以下の 3 つの日本語テキストに対して感情分析を行い、結果を確認してください。
    • 「この本はとても勉強になりました」
    • 「電車が遅延して最悪でした」
    • 「今日は特に何もありませんでした」
  2. pipeline("text-generation") を使って、英語のテキスト生成を試してください。プロンプトとして "Machine learning is" を入力し、生成されるテキストを確認してください。
解答例
from transformers import pipeline

# 1. 日本語テキストの感情分析(本章と同じ多言語モデル)
classifier_ja = pipeline(
    "sentiment-analysis",
    model="lxyuan/distilbert-base-multilingual-cased-sentiments-student"
)
texts = [
    "この本はとても勉強になりました",
    "電車が遅延して最悪でした",
    "今日は特に何もありませんでした",
]
for text in texts:
    scores = classifier_ja(text, top_k=None)
    dist = ", ".join(f"{s['label']} {s['score']:.3f}" for s in scores)
    print(f"{text} → {dist}")

# 2. 英語のテキスト生成
# モデルを指定しない場合は大きなモデルがダウンロードされるため、
# ここでは軽量な gpt2 を指定しています
generator = pipeline("text-generation", model="gpt2")
result = generator("Machine learning is", max_new_tokens=30,
                   num_return_sequences=1)
print(result[0]["generated_text"])
この本はとても勉強になりました → negative 0.401, positive 0.328, neutral 0.271
電車が遅延して最悪でした → negative 0.877, positive 0.063, neutral 0.060
今日は特に何もありませんでした → negative 0.421, neutral 0.336, positive 0.243
Machine learning is not just about working with large datasets. It's also about
learning from big data.

When I was starting out, there were a lot of

考察:「電車が遅延して最悪でした」は negative 0.877 で、正しく判定されました。「この本はとても勉強になりました」は negative 0.401、positive 0.328 で、positive のはずが negative になっています。「勉強になりました」のような遠回しなほめ方は、このモデルには難しいようです。「今日は特に何もありませんでした」も negative 0.421、neutral 0.336 と割れました。スコアが低い判定は誤りのことが多いので、ラベルだけでなくスコアも見ます。テキスト生成は単語をサンプリングで選ぶため、実行のたびに文章が変わります。gpt2 は 2019 年の小さなモデルで文章の質は高くありませんが、続きの単語を順に予測する動きは確かめられます。

演習 14-4: ニューラルネットワークの構造変更

演習 14-4

本章の MNIST 全結合ネットワークの構造を以下のように変更し、テスト精度がどのように変化するか比較してください。

  1. 隠れ層を 1 層(Dense(128) のみ)にした場合
  2. 隠れ層を 3 層(Dense(256), Dense(128), Dense(64))にした場合
  3. 隠れ層のニューロン数を少なくした場合(Dense(32), Dense(16))

結果を表にまとめ、ネットワーク構造と精度の関係を考察してください。

解答例
from tensorflow.keras.datasets import mnist
from tensorflow.keras.models import Sequential
from tensorflow.keras.layers import Input, Dense

# 本章と同じデータ準備
(X_train, y_train), (X_test, y_test) = mnist.load_data()
X_train = (X_train / 255.0).reshape(-1, 784)
X_test = (X_test / 255.0).reshape(-1, 784)

# 隠れ層の構成を変えた4つのモデルを比較
architectures = {
    "本章の構成 (128, 64)": [128, 64],
    "1層 (128)": [128],
    "3層 (256, 128, 64)": [256, 128, 64],
    "小規模 (32, 16)": [32, 16],
}
for name, hidden in architectures.items():
    layers = [Input(shape=(784,))]
    layers += [Dense(n, activation="relu") for n in hidden]
    layers += [Dense(10, activation="softmax")]
    model = Sequential(layers)
    model.compile(optimizer="adam",
                  loss="sparse_categorical_crossentropy",
                  metrics=["accuracy"])
    model.fit(X_train, y_train, epochs=10, batch_size=32,
              validation_split=0.2, verbose=0)
    _, acc = model.evaluate(X_test, y_test, verbose=0)
    print(f"{name}: パラメータ数 {model.count_params():,}, "
          f"テスト精度 {acc:.4f}")
本章の構成 (128, 64): パラメータ数 109,386, テスト精度 0.9738
1層 (128): パラメータ数 101,770, テスト精度 0.9714
3層 (256, 128, 64): パラメータ数 242,762, テスト精度 0.9762
小規模 (32, 16): パラメータ数 25,818, テスト精度 0.9594
構成 パラメータ数 テスト精度
本章の構成 (128, 64) 109,386 0.9738
1層 (128) 101,770 0.9714
3層 (256, 128, 64) 242,762 0.9762
小規模 (32, 16) 25,818 0.9594

考察(数値は実行のたびに多少変動します):どの構成も 0.96〜0.98 の範囲に収まりました。3層にしてパラメータ数を2倍以上にしても、本章の構成より約0.2ポイントしか上がりません。MNIST は、中くらいの規模の全結合ネットワークで精度が頭打ちになるためです。ニューロン数を減らした (32, 16) は、本章の構成より約1.4ポイント下がりました。大きいほどよいわけではなく、精度が同じくらいなら、学習も予測も速い小さいモデルを選ぶ手もあります。

演習 14-5: プロンプトエンジニアリングの実践

演習 14-5

以下の課題について、異なるプロンプトを 3 パターン以上作成し、LLM(ChatGPT や Claude など)に入力して、出力の違いを比較してください。

  • 課題:「機械学習でよく使われる分類アルゴリズムを比較する表を作成してください」

どのようなプロンプトを書いたときに最も有用な出力が得られたか、その理由とともに考察してください。

解答例

解答例として、3つのプロンプトとその出力傾向を比較します(出力はLLMやバージョンによって変わるため、傾向として捉えてください)。

パターン1(そのまま依頼)

機械学習でよく使われる分類アルゴリズムを比較する表を作成してください

出力例の傾向:アルゴリズムの選定も比較項目もLLM任せになり、対象読者が不明のまま一般的な表(アルゴリズム名・特徴・利点・欠点)が返ってきます。内容は正しくても、粒度が細かすぎたり専門用語が多すぎたりと、用途に合うかは運頼みです。

パターン2(対象・項目・数を指定)

機械学習の入門者向けに、分類アルゴリズムを比較する表を作成してください。対象はロジスティック回帰・k近傍法・SVM・ランダムフォレストの4つ、比較項目は「仕組みの直感的な説明」「長所」「短所」「向いているデータ」の4項目としてください。

出力例の傾向:行と列が指定どおりに揃い、内容の粒度も「入門者向け」に調整されます。パターン1と比べて、そのまま資料に使える完成度になります。

パターン3(役割・形式・制約を追加)

あなたは機械学習の講師です。第7〜8章まで学習した学生(ロジスティック回帰・ナイーブベイズ・kNN・SVM・決定木・ランダムフォレストを学習済み)の復習用に、これらを比較する表をMarkdown形式で作成してください。各セルは30字以内で簡潔に。表の後に「アルゴリズム選択の目安」を3行で添えてください。

出力例の傾向:役割(講師)と読者の前提知識を与えたことで、説明が学習済みの用語の範囲に収まり、文字数制約により表が読みやすくなります。表の後の補足指定など、出力の構成まで制御できます。

考察:使いやすい出力が得られやすいのは、パターン3のように役割、読者の前提知識、内容(対象アルゴリズムと比較項目)、出力形式と制約(Markdown、字数)を書いたプロンプトです。LLM は指示があいまいなところを一般的な答えで埋めるので、任せたくない部分ははっきり指定します。ただし制約を増やしすぎると、自分が思いつかなかった比較項目のような提案が出にくくなります。最初はパターン2くらいで試し、出力を見ながら条件を足していく方法もあります。

14.7 まとめ

本章では、深層学習の基礎概念から、Keras による画像分類、Transformer によるテキスト処理、生成 AI の活用までを学びました。

第 14 章のまとめ

深層学習の基礎

  • ニューロン:重み付き和+バイアスを活性化関数に通す
  • 活性化関数(ReLU・シグモイド・ソフトマックス)が非線形性を与える
  • 順伝播で予測し、損失を逆伝播させて重みを更新する
  • 損失関数(交差エントロピー・MSE)とオプティマイザ(SGD・Adam)

深層学習の実践(Keras)

  • Sequential クラスを使ったモデルで層を積み重ねて構築
  • compile() メソッドでオプティマイザ・損失関数・評価指標を設定し、fit() メソッドで学習
  • エポック・バッチサイズ・検証データが重要パラメータ
  • 検証精度が頭打ちなら過学習を疑う(Dropout などで対策)

CNN

  • 畳み込みとプーリングで画像の空間構造を活かした特徴抽出
  • MNIST では全結合(97.48%)より CNN(98.85%)が少ないエポックで高精度
  • CIFAR-10 のような現実的な課題では工夫(データ拡張など)が必要
  • 実務では有名アーキテクチャ(VGG・ResNet・EfficientNet)の転移学習が主流

Transformer

  • Attention 機構により文中のすべての単語ペアの関連度を同時に計算
  • BERT は双方向の文脈理解と「事前学習+ファインチューニング」が特徴
  • Hugging Face transformers の pipeline で感情分析やゼロショット分類を数行で実行

生成AI

  • LLM は「次の単語を予測する」学習を大規模化した Transformer ベースのモデル
  • API(OpenAI など)からプログラムで呼び出せる
  • プロンプトエンジニアリングで出力の品質を制御
  • ハルシネーション・著作権・バイアスなどの限界を前提に、人間の検証を組み込む