Google Colaboratoryを用いたディープラーニング入門

【概要】本稿は、Google Colaboratoryを用いたディープラーニング(ニューラルネットワークによる教師あり学習)の入門である。本稿で扱うのは、CNNによる画像分類、LSTMによる感情分析および時系列予測、データ可視化・統計分析である。古典的機械学習、教師なし学習、生成AIや大規模言語モデルは扱わない。Colabの基本操作とあわせて、コード例および演習とともに解説する。

【前提知識】Pythonの基本文法を理解していることを前提とする。

【データの扱いについて】各プロジェクトでは、データを訓練・検証・テストに分けて扱う。役割は「基本概念と用語」で説明する。

【実行環境について】Colabのプリインストール環境(Python・各ライブラリのバージョン)は更新される。本稿のコードは公開時点のColab(Python 3.12系、TensorFlow/Keras 3系)で動作を確認している。利用前に、実際のColab上で一度通し実行し、ライブラリのインストール部分が成功することを確認する。

【目次】

  1. Google Colaboratory
  2. 基本概念と用語
  3. 画像分類プロジェクト
  4. 自然言語処理による感情分析プロジェクト
  5. 時系列データ予測システムの構築
  6. データの可視化と分析プロジェクト
  7. 学習結果の共通評価方法

Google Colaboratory

Google Colaboratoryの基本情報

Google Colaboratory(以下、Colab)は、ブラウザ上でPythonコードを実行できるGoogleのサービスである。無料枠でGPUを利用でき、環境構築なしで機械学習を始められる。ColabはJupyter Notebookをクラウド上で提供するサービスであり、操作感はローカルのJupyter Notebookと共通する。

「Colaboratoryへようこそ」のページのURLは、https://colab.research.google.com/?hl=ja である。

事前インストール済みライブラリ

NumPy、Pandas、Matplotlib、TensorFlow、PyTorchなど、科学計算や機械学習に用いるライブラリが事前にインストールされている。import文を記述するだけで利用できる。

追加ライブラリのインストール

事前にインストールされていないライブラリは!pipコマンドでインストールする。インストールしたライブラリはセッション終了後に消去されるため、次回セッションで再インストールする。

クラウド実行(Colab)とローカル環境の使い分け

ColabはJupyter Notebookをクラウドで動かす形態であり、ローカルにJupyter Notebookを構築する形態とは実行環境が異なる。使い分けの目安は次のとおりである。

基本概念と用語

本セクションでは、以降のプロジェクトで使用するニューラルネットワークの基本用語を解説する。初めて触れる場合は、このセクションを一読してからプロジェクトに進む。

データの3分割とその役割

各プロジェクトでは、データを訓練データ検証データテストデータの3つに分けて使う。訓練データはモデルのパラメータを学習するために使う。検証データは学習の進み具合や設定(エポック数などのハイパーパラメータ)の良し悪しを確認するために使い、パラメータの学習には使わない。テストデータは最終的な性能を測るために使い、学習にも設定の調整にも使わない。

検証データを分ける理由は、訓練データだけを見ていると過学習に気づけないためである。テストデータをさらに分ける理由は、検証データが設定の調整を通じてモデルに間接的に影響するため、それも含めて一度も使っていないデータで最後に評価することで、未知のデータに対する性能を公平に見積もれるためである。訓練・テストの2分割のみを行うプロジェクトでは、テストデータが検証データの役割を兼ねる。

学習に関する基本概念

正規化(normalization)は、データの値を一定の範囲に変換する処理である。本稿では特に断りのない限り、Min-Max正規化により0から1の範囲に変換する処理を指す(標準化など他のスケーリング手法もあるが、本稿ではMin-Maxを用いる)。特徴量ごとに値の範囲が異なると、範囲が大きい特徴量が学習に過大な影響を与えるため、同一の範囲に揃えることで学習の安定化と高速化に寄与する。

エポック(epoch)は、全訓練データを1回学習することを指す。エポック数が多すぎると過学習、少なすぎると学習不足となる。

バッチサイズ(batch size)は、一度に処理するデータの数である。バッチサイズが大きいと学習が安定するがメモリ使用量が増加し、小さいと学習が不安定になるが汎化性能が向上することがある。

汎化性能(generalization)は、学習に使用していない未知のデータに対する予測精度である。

過学習(overfitting)は、訓練データに過度に適合し、未知のデータに対する予測精度が低下する現象である。訓練精度は高いが検証精度が低い場合に疑われる。

検証データ(validation data)は、学習の進捗確認に使用するデータであり、訓練には使用しない。過学習の早期発見に役立つ。

ドロップアウト(dropout)は、学習時にニューロンの一部を無作為に無効化し、過学習を防ぐ手法である。特定のニューロンへの依存を減らすことで汎化性能を高める。

損失関数(loss function)は、モデルの予測と正解とのズレの大きさを測る量である。学習はこの損失を小さくする方向に進む。分類タスクでは交差エントロピー(cross-entropy)、回帰や数値予測のタスクでは二乗誤差(mean squared error)を用いる。

最適化アルゴリズム(optimizer)は、損失が小さくなるようにモデルのパラメータを更新する手法である。本稿ではいずれのモデルでもadamを用いる。

ニューラルネットワークの種類

CNN(畳み込みニューラルネットワーク、Convolutional Neural Network)は、画像の特徴を段階的に学習するモデルである。畳み込み層で画像から特徴を抽出し、プーリング層で情報を圧縮しながら、エッジや形などの局所的な特徴から物体全体の認識へと進む。画像内の位置によらず特徴を抽出でき、画像認識タスクで高い性能を示す。

LSTM(長・短期記憶、Long Short-Term Memory)は、時系列データや文章の流れを扱うモデルである。従来のRNN(再帰型ニューラルネットワーク、Recurrent Neural Network)では、長い系列を処理する際に過去の情報が失われる勾配消失問題が発生する。勾配消失問題とは、学習時に誤差の勾配が層を遡るにつれて小さくなり、初期の層が学習できなくなる現象である。LSTMはゲート機構によりこの問題を緩和し、長期的な依存関係を学習できる。

LSTMなどの一部の層は、GPU向けに最適化された高速実装(cuDNN)を持ち、活性化関数の種類などの一定の条件を満たすと自動的にこの高速実装が使われる。逆に、一部のオプション(例えばLSTMのrecurrent_dropout)を指定すると高速実装が無効になり、学習が大幅に遅くなる。本稿のコードは高速実装が使われるように構成している。

データ処理用語

ワンホットエンコーディング(one-hot encoding)は、カテゴリ変数を数値ベクトルに変換する手法である。例えば「犬」「猫」「鳥」を[1,0,0]、[0,1,0]、[0,0,1]に変換する。多クラス分類の正解ラベル表現に用いる。

パディング(padding)は、文章の長さを揃えるため、短い文章に特定の値(通常は0)を追加する処理である。LSTMにバッチ単位で入力するには系列長を揃える必要があるため、この処理を行う。

埋め込み層(embedding layer)は、単語インデックスを密な数値ベクトルに変換する層である。意味が近い単語は近いベクトルを持つように学習される。

評価指標

シグモイド関数(sigmoid function)は、任意の入力値を0から1の範囲に変換する関数である。出力を確率として解釈できるため、二値分類の出力層で使用する。多クラス分類ではソフトマックス関数(softmax function)を用い、クラス数分の出力の合計が1になるよう各クラスの確率を出す。出力層の活性化関数と損失関数には対応関係があり、二値分類はシグモイド+binary_crossentropy、多クラス分類はソフトマックス+categorical_crossentropyを用いる。

混同行列(confusion matrix)は、分類結果を実際のクラスと予測したクラスの組み合わせで集計した表である。どのクラスをどのクラスに取り違えやすいかを把握できる。

適合率・再現率・F1スコアは、混同行列から計算される指標である。適合率(precision)はポジティブと予測したもののうち実際にポジティブだった割合、再現率(recall)は実際にポジティブなもののうちポジティブと予測できた割合、F1スコアは両者の調和平均である。正解率(accuracy)だけでは見えないクラスごとの性能を確認できる。

RMSE(二乗平均平方根誤差、Root Mean Squared Error)は、回帰・予測の精度を表す指標である。予測値と実際の値の差を二乗して平均し、その平方根を取る。値が小さいほど精度が高い。

画像分類プロジェクト

概要

このセクションでは、CNNを用いた画像分類の実装方法を学ぶ。完了後、画像データの前処理、CNNモデルの構築、学習結果の評価ができるようになる。

画像分類とは、コンピュータが画像の内容を自動的に分類する技術である。人間が写真を見て「これは犬」「これは車」と判断するように、コンピュータにも同様の判断を行わせる。

CIFAR-10データセットは、10種類の物体(飛行機、自動車、鳥、猫、鹿、犬、カエル、馬、船、トラック)の32×32ピクセルのカラー画像が60,000枚含まれている。低解像度のため計算が軽く教育用途に適しているが、実用では高解像度の画像を扱う。

実装例

以下のコードは、CIFAR-10データセットを用いてCNNによる画像分類を行う。コードは「環境設定」「データ準備」「モデル構築」「学習」「評価」の順に構成されている。

# CIFAR-10データセットを使用したCNN画像分類システム

# --- 環境設定 ---
# 日本語フォントのインストールと設定(IPAゴシックをmatplotlibに登録する)
!apt-get -qq install -y fonts-ipafont-gothic
import matplotlib
import matplotlib.pyplot as plt
matplotlib.font_manager.fontManager.addfont(
    '/usr/share/fonts/opentype/ipafont-gothic/ipagp.ttf')
plt.rcParams['font.family'] = 'IPAPGothic'
plt.rcParams['axes.unicode_minus'] = False

import tensorflow as tf
from tensorflow import keras
from tensorflow.keras import layers
import numpy as np
from sklearn.metrics import confusion_matrix, classification_report

# 再現性のための乱数シード設定
SEED, EPOCHS, BATCH_SIZE, NUM_CLASSES = 42, 5, 32, 10
CLASS_NAMES = ['飛行機', '自動車', '鳥', '猫', '鹿', '犬', 'カエル', '馬', '船', 'トラック']
np.random.seed(SEED)
tf.random.set_seed(SEED)

# --- データ準備 ---
# データの読み込みと正規化(0-255の値を0-1に変換)
(x_train_full, y_train_full), (x_test, y_test) = keras.datasets.cifar10.load_data()
x_train_full, x_test = x_train_full.astype('float32') / 255.0, x_test.astype('float32') / 255.0

# 評価用に整数ラベルを保持する(混同行列の作成に使う)
y_test_int = y_test.flatten()

# ラベルをワンホットエンコーディングに変換
y_train_full = keras.utils.to_categorical(y_train_full, NUM_CLASSES)
y_test = keras.utils.to_categorical(y_test, NUM_CLASSES)

# 訓練データと検証データの分割(80%:20%)
val_size = int(len(x_train_full) * 0.2)
x_train, x_val = x_train_full[:-val_size], x_train_full[-val_size:]
y_train, y_val = y_train_full[:-val_size], y_train_full[-val_size:]

# --- モデル構築 ---
# Keras 3 推奨に従い、先頭のInput層で入力形状を指定する
model = keras.Sequential([
    keras.Input(shape=(32, 32, 3)),
    layers.Conv2D(32, (3, 3), activation='relu'),   # 畳み込み層:画像から特徴を抽出
    layers.MaxPooling2D((2, 2)),                    # プーリング層:特徴マップを縮小
    layers.Conv2D(64, (3, 3), activation='relu'),
    layers.MaxPooling2D((2, 2)),
    layers.Conv2D(64, (3, 3), activation='relu'),
    layers.Flatten(),                               # 全結合層への変換
    layers.Dense(64, activation='relu'),
    layers.Dense(NUM_CLASSES, activation='softmax') # 出力層:10クラスの確率を出力
])
model.compile(optimizer='adam', loss='categorical_crossentropy', metrics=['accuracy'])

# --- 学習 ---
history = model.fit(x_train, y_train, epochs=EPOCHS, batch_size=BATCH_SIZE,
                    validation_data=(x_val, y_val), verbose=1)

# --- 評価 ---
# 予測結果の可視化
predictions = model.predict(x_test[:5])
plt.figure(figsize=(15, 3))
for i in range(5):
    plt.subplot(1, 5, i+1)
    plt.imshow(x_test[i])
    plt.title(f'予測: {CLASS_NAMES[np.argmax(predictions[i])]}\n実際: {CLASS_NAMES[y_test_int[i]]}')
    plt.axis('off')
plt.tight_layout()
plt.show()

# 学習履歴の可視化
fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(12, 4))
ax1.plot(history.history['accuracy'], label='訓練精度')
ax1.plot(history.history['val_accuracy'], label='検証精度')
ax1.set_title('モデル精度')
ax1.set_xlabel('エポック')
ax1.set_ylabel('精度')
ax1.legend()
ax2.plot(history.history['loss'], label='訓練損失')
ax2.plot(history.history['val_loss'], label='検証損失')
ax2.set_title('モデル損失')
ax2.set_xlabel('エポック')
ax2.set_ylabel('損失')
ax2.legend()
plt.tight_layout()
plt.show()

# 最終評価(正解率に加えて、クラスごとの指標と混同行列を確認する)
test_accuracy = model.evaluate(x_test, y_test, verbose=0)[1]
print(f"テスト精度(正解率): {test_accuracy:.4f}")

y_pred_int = np.argmax(model.predict(x_test, verbose=0), axis=1)
print("\nクラスごとの適合率・再現率・F1スコア:")
print(classification_report(y_test_int, y_pred_int, target_names=CLASS_NAMES))
print("混同行列(行=実際, 列=予測):")
print(confusion_matrix(y_test_int, y_pred_int))

実行結果の読み方

テスト精度(正解率)は0.0から1.0の範囲で表示され、1.0に近いほど予測精度が高い。10クラス分類のため、でたらめに答えた場合の正解率は約0.1である。この値を大きく上回っていれば学習が進んでいると判断できる。なお、適切な水準はデータやモデル、目的によって変わるため、以下の数値は目安である。

クラスごとの指標と混同行列では、正解率だけでは見えないクラスごとの性能や、取り違えやすいクラスの組み合わせ(例えば「猫」と「犬」の相互誤分類)を確認できる。

予測結果表示では、5枚の画像について予測ラベルと実際のラベルが表示され、一致していれば予測が成功している。

学習履歴グラフでは、訓練精度と検証精度が近い値で上昇していれば正常な学習である。検証精度が訓練精度より大幅に低い場合は過学習の可能性がある。損失については、両者が減少傾向であれば学習が進行しており、検証損失が上昇に転じた場合は過学習の兆候である。

演習

演習1.畳み込み層の数と精度の関係を調べる

手順

  1. 上記の実装例をColabで実行し、テスト精度を記録する。
  2. モデル構築部の3番目のConv2D層(layers.Conv2D(64, (3, 3), activation='relu')の3つ目)を削除する。
  3. 再度実行し、テスト精度と学習履歴グラフを記録する。

ヒント

層を削除するとFlattenに渡る特徴マップのサイズが変わるが、コードの修正は不要である。学習時間も比較する。

考察ポイント

層を減らすとテスト精度と訓練精度はどう変化したか。訓練精度と検証精度の差から、層の数が過学習や学習能力にどう影響するかを読み取る。

自然言語処理による感情分析プロジェクト

概要

このセクションでは、LSTMを用いたテキストの感情分析を学ぶ。完了後、テキストデータの前処理、LSTMモデルの構築、感情の二値分類ができるようになる。

感情分析とは、テキストデータから感情の極性(ポジティブまたはネガティブ)を自動判定する技術であり、映画レビューやSNSの投稿の分析に使用される。

IMDbデータセットは、映画レビューサイトIMDbのレビューデータで、ポジティブ・ネガティブに分類されている。50,000件のレビューからなり、訓練用25,000件、テスト用25,000件に分けられている。英語テキストであるため、日本語への直接適用はできない。

自然言語処理の特殊概念

単語インデックスは、各単語に割り当てられた固有の番号であり、コンピュータが文字を数値として扱うために用いる。例えば「the」=1、「movie」=2のように変換される。IMDbデータセットでは先頭の0、1、2が「パディング」「系列開始」「不明な単語」を表す特殊トークンに予約されているため、実際の単語インデックスはこれを考慮して3だけずれている。

前方パディングとマスキングについて、本稿の実装では短い文の前方(先頭側)を0で埋めて長さを揃える。系列の末尾に実際の単語が並ぶため、文を最後まで読んで判断するLSTMと相性がよい。さらに埋め込み層でmask_zero=Trueを指定することで、埋めた0(パディング)はLSTMの計算から除外される。これをマスキングと呼び、パディング部分が予測に影響しないようにする。

実装例

以下のコードは、IMDbデータセットを用いてLSTMによる感情分析を行う。コードは「環境設定」「データ準備」「モデル構築」「学習」「評価」の順に構成されている。

# IMDbレビューデータを使用したLSTM感情分析システム

# --- 環境設定 ---
# 日本語フォントのインストールと設定(IPAゴシックをmatplotlibに登録する)
!apt-get -qq install -y fonts-ipafont-gothic
import matplotlib
import matplotlib.pyplot as plt
matplotlib.font_manager.fontManager.addfont(
    '/usr/share/fonts/opentype/ipafont-gothic/ipagp.ttf')
plt.rcParams['font.family'] = 'IPAPGothic'
plt.rcParams['axes.unicode_minus'] = False

import tensorflow as tf
from tensorflow import keras
from tensorflow.keras import layers
from tensorflow.keras.preprocessing.sequence import pad_sequences
import numpy as np
from sklearn.metrics import confusion_matrix, classification_report

SEED, EPOCHS, BATCH_SIZE = 42, 5, 32
# MAX_WORDS: 使用する単語数の上限
# MAX_LENGTH: 入力文の最大長(これより長い文は切り詰められる)
MAX_WORDS, MAX_LENGTH, EMBEDDING_DIM, LSTM_UNITS, DROPOUT_RATE = 10000, 250, 128, 64, 0.5
np.random.seed(SEED)
tf.random.set_seed(SEED)

# --- データ準備 ---
# データの読み込み(単語は既にインデックスに変換済み)
(x_train_full, y_train_full), (x_test, y_test) = keras.datasets.imdb.load_data(num_words=MAX_WORDS)
# パディング:文章の長さを揃える(短い文章は前方を0で埋める)
x_train_full, x_test = pad_sequences(x_train_full, maxlen=MAX_LENGTH), pad_sequences(x_test, maxlen=MAX_LENGTH)

# 訓練データと検証データの分割(80%:20%)
val_size = int(len(x_train_full) * 0.2)
x_train, x_val = x_train_full[:-val_size], x_train_full[-val_size:]
y_train, y_val = y_train_full[:-val_size], y_train_full[-val_size:]

print(f"訓練データサイズ: {len(x_train)}")
print(f"検証データサイズ: {len(x_val)}")
print(f"テストデータサイズ: {len(x_test)}")

# --- モデル構築 ---
# Keras 3 推奨に従い、先頭のInput層で入力形状を指定する
# recurrent_dropoutは使わない(GPUの高速実装が無効になるのを避けるため)
model = keras.Sequential([
    keras.Input(shape=(MAX_LENGTH,)),
    # 埋め込み層:単語インデックスを密なベクトルに変換(mask_zero=Trueでパディングを除外)
    layers.Embedding(MAX_WORDS, EMBEDDING_DIM, mask_zero=True),
    layers.LSTM(LSTM_UNITS, dropout=DROPOUT_RATE),  # LSTM層:系列データの特徴を学習
    layers.Dropout(DROPOUT_RATE),
    layers.Dense(1, activation='sigmoid')           # 出力層:ポジティブ/ネガティブの確率を出力
])
model.compile(optimizer='adam', loss='binary_crossentropy', metrics=['accuracy'])

# --- 学習 ---
history = model.fit(x_train, y_train, epochs=EPOCHS, batch_size=BATCH_SIZE,
                    validation_data=(x_val, y_val), verbose=1)

# --- 評価 ---
predictions = model.predict(x_test[:5])
print("\n予測結果:")
for i in range(5):
    prob = predictions[i][0]
    sentiment = "ポジティブ" if prob > 0.5 else "ネガティブ"
    actual = "ポジティブ" if y_test[i] == 1 else "ネガティブ"
    print(f"レビュー {i+1}: 予測={sentiment} (予測確率: {prob:.3f}), 実際={actual}")

# レビュー例の復元表示(インデックスを単語に戻す。0,1,2は特殊トークンのため3だけずらす)
reverse_word_index = {v: k for k, v in keras.datasets.imdb.get_word_index().items()}
print(f"\nレビュー例:\n{' '.join([reverse_word_index.get(i - 3, '?') for i in x_test[0] if i != 0])}")

# 学習履歴の可視化
fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(12, 4))
ax1.plot(history.history['accuracy'], label='訓練精度')
ax1.plot(history.history['val_accuracy'], label='検証精度')
ax1.set_title('モデル精度')
ax1.set_xlabel('エポック')
ax1.set_ylabel('精度')
ax1.legend()
ax2.plot(history.history['loss'], label='訓練損失')
ax2.plot(history.history['val_loss'], label='検証損失')
ax2.set_title('モデル損失')
ax2.set_xlabel('エポック')
ax2.set_ylabel('損失')
ax2.legend()
plt.tight_layout()
plt.show()

# 最終評価(正解率に加えて、クラスごとの指標と混同行列を確認する)
test_accuracy = model.evaluate(x_test, y_test, verbose=0)[1]
print(f"\nテスト精度(正解率): {test_accuracy:.4f}")

y_pred_int = (model.predict(x_test, verbose=0).flatten() > 0.5).astype(int)
print("\nクラスごとの適合率・再現率・F1スコア:")
print(classification_report(y_test, y_pred_int, target_names=['ネガティブ', 'ポジティブ']))
print("混同行列(行=実際, 列=予測):")
print(confusion_matrix(y_test, y_pred_int))

実行結果の読み方

予測結果では、予測ラベルと実際のラベルが比較表示される。予測確率は0.0から1.0の範囲で、1.0に近いほどポジティブ、0.0に近いほどネガティブと予測していることを示す。0.5に近い場合は判断が曖昧である。

テスト精度(正解率)は全体の予測精度である。二値分類のため、でたらめに答えた場合の正解率は約0.5である。あわせて表示される混同行列とクラスごとの指標から、ポジティブ・ネガティブのどちらを取り違えやすいかを確認できる。適切な水準は目的によって変わるため、これらの数値は目安である。

学習履歴グラフは画像分類と同様に解釈する。自然言語処理では学習に時間がかかるため、エポック数を増やすことで精度が向上する場合がある。

演習

演習2.入力文の最大長と精度の関係を調べる

手順

  1. 上記の実装例をColabで実行し、テスト精度と学習時間を記録する。
  2. MAX_LENGTHの値を250から100に変更する。
  3. 再度実行し、テスト精度と学習時間を記録する。

ヒント

MAX_LENGTHを小さくすると、長いレビューは末尾までではなく途中までしか使われなくなる(前方を0で埋める設定のため、保持されるのは文の後半である)。

考察ポイント

入力文を短くするとテスト精度と学習時間はどう変化したか。精度の低下と学習時間の短縮のどちらが大きいかを読み取る。

時系列データ予測システムの構築

概要

このセクションでは、LSTMを用いた時系列データの予測を学ぶ。完了後、時系列データの前処理、予測モデルの構築、予測精度の評価ができるようになる。

時系列予測とは、時間とともに変化するデータから未来の値を予測する技術であり、株価、売上、気温の予測に使用される。

本例では実際の株価データを使用する。株価は経済情勢や企業業績など多くの要因に影響されるため、このモデルだけで投資判断を行うべきではない。

時系列分析の特殊概念

時系列データは、時間順に並んだデータである。過去のパターンから未来を予測する際に用いる。

スケールの復元(逆変換、inverse transform)は、正規化されたデータを元の範囲に戻す処理である。予測結果を実際の価格に戻して解釈するために必要となる。

シーケンス化は、過去の一定期間のデータから未来の値を予測するため、データを時系列の組に変換する処理である。例えば、過去60日分のデータから61日目を予測する形式に変換する。期間が短すぎると情報が不足し、長すぎると計算負荷が増大する。

実装例

以下のコードは、Yahoo Financeから取得した株価データを用いてLSTMによる価格予測を行う。コードは「環境設定」「データ取得」「前処理」「モデル構築」「学習」「評価」の順に構成されている。

# 株価データを使用したLSTM時系列予測システム

# --- 環境設定 ---
# 日本語フォントのインストールと設定(IPAゴシックをmatplotlibに登録する)
!apt-get -qq install -y fonts-ipafont-gothic
!pip install -q yfinance
import matplotlib
import matplotlib.pyplot as plt
matplotlib.font_manager.fontManager.addfont(
    '/usr/share/fonts/opentype/ipafont-gothic/ipagp.ttf')
plt.rcParams['font.family'] = 'IPAPGothic'
plt.rcParams['axes.unicode_minus'] = False

import yfinance as yf
import tensorflow as tf
from tensorflow import keras
from tensorflow.keras import layers
import numpy as np
from sklearn.preprocessing import MinMaxScaler
from sklearn.metrics import mean_squared_error

SEED, EPOCHS, BATCH_SIZE = 42, 10, 32
# TICKER_SYMBOL: 株式銘柄コード(AAPL=Apple)
# SEQUENCE_LENGTH: 予測に使用する過去のデータ数
TICKER_SYMBOL, PERIOD, SEQUENCE_LENGTH = "AAPL", "2y", 60
TRAIN_RATIO, VAL_RATIO = 0.7, 0.15
np.random.seed(SEED)
tf.random.set_seed(SEED)

# --- データ取得 ---
# auto_adjust=True(現行のyfinanceの既定)では、Close列は分割・配当を反映した
# 調整後終値になる。本例ではこの調整後終値を価格として扱う。
df = yf.Ticker(TICKER_SYMBOL).history(period=PERIOD, auto_adjust=True)
data = df['Close'].values.reshape(-1, 1)
print(f"データサイズ: {len(data)}")
print(df.head())

# --- 前処理 ---
# 正規化:値を0-1の範囲に変換(学習の安定化のため)
scaler = MinMaxScaler(feature_range=(0, 1))
scaled_data = scaler.fit_transform(data)

# シーケンス化:過去60日のデータから翌日を予測する形式に変換
X = np.array([scaled_data[i:i+SEQUENCE_LENGTH, 0] for i in range(len(scaled_data)-SEQUENCE_LENGTH)])
y = np.array([scaled_data[i+SEQUENCE_LENGTH, 0] for i in range(len(scaled_data)-SEQUENCE_LENGTH)])

# 訓練・検証・テストデータの分割(70%:15%:15%)
train_size = int(len(X) * TRAIN_RATIO)
val_size = int(len(X) * VAL_RATIO)
X_train = X[:train_size].reshape(-1, SEQUENCE_LENGTH, 1)
X_val = X[train_size:train_size+val_size].reshape(-1, SEQUENCE_LENGTH, 1)
X_test = X[train_size+val_size:].reshape(-1, SEQUENCE_LENGTH, 1)
y_train, y_val, y_test = y[:train_size], y[train_size:train_size+val_size], y[train_size+val_size:]

# --- モデル構築 ---
# Keras 3 推奨に従い、先頭のInput層で入力形状を指定する
# recurrent_dropoutは使わない(GPUの高速実装が無効になるのを避けるため)
model = keras.Sequential([
    keras.Input(shape=(SEQUENCE_LENGTH, 1)),
    layers.LSTM(50, return_sequences=True),
    layers.LSTM(50, return_sequences=False),
    layers.Dense(25),
    layers.Dense(1)
])
model.compile(optimizer='adam', loss='mean_squared_error')

# --- 学習 ---
history = model.fit(X_train, y_train, batch_size=BATCH_SIZE, epochs=EPOCHS,
                    validation_data=(X_val, y_val), verbose=1)

# --- 評価 ---
# 予測の実行とスケールの復元(実際の価格に戻す)
train_pred = scaler.inverse_transform(model.predict(X_train))
test_pred = scaler.inverse_transform(model.predict(X_test))
y_train_actual = scaler.inverse_transform(y_train.reshape(-1, 1))
y_test_actual = scaler.inverse_transform(y_test.reshape(-1, 1))

# 結果の可視化
train_plot = np.full((len(scaled_data), 1), np.nan)
test_plot = np.full((len(scaled_data), 1), np.nan)
train_plot[SEQUENCE_LENGTH:SEQUENCE_LENGTH+len(train_pred)] = train_pred
test_start = SEQUENCE_LENGTH + train_size + val_size
test_plot[test_start:test_start+len(test_pred)] = test_pred

plt.figure(figsize=(15, 6))
plt.plot(scaler.inverse_transform(scaled_data), label='実際の価格', linewidth=2)
plt.plot(train_plot, label='訓練予測', alpha=0.7)
plt.plot(test_plot, label='テスト予測', alpha=0.7)
plt.title('株価予測結果')
plt.xlabel('日数')
plt.ylabel('価格')
plt.legend()
plt.grid(True, alpha=0.3)
plt.show()

# 精度の計算
train_rmse = np.sqrt(mean_squared_error(y_train_actual, train_pred))
test_rmse = np.sqrt(mean_squared_error(y_test_actual, test_pred))
print(f'訓練RMSE: {train_rmse:.2f}')
print(f'テストRMSE: {test_rmse:.2f}')

# 学習履歴の表示
plt.figure(figsize=(8, 4))
plt.plot(history.history['loss'], label='訓練損失')
plt.plot(history.history['val_loss'], label='検証損失')
plt.title('モデル損失')
plt.xlabel('エポック')
plt.ylabel('損失')
plt.legend()
plt.grid(True, alpha=0.3)
plt.show()

実行結果の読み方

データサイズでは取得したデータの件数が表示され、先頭5件の株価情報が表示される。ここで用いる価格は調整後終値である。

RMSE値について、訓練RMSEは訓練データに対する予測誤差、テストRMSEはテストデータに対する予測誤差である。値が小さいほど予測精度が高い。一般にテストRMSEは訓練RMSEより大きくなりやすいが、差が極端に大きい場合は過学習が疑われる。許容範囲はデータや目的によって変わるため、これは目安である。

時系列予測グラフでは、実際の価格は元の時系列データ、訓練予測は訓練期間での予測結果、テスト予測は未知データでの予測結果を示す。実際の値との乖離が小さいほど予測性能が高い。

学習損失グラフでは、訓練損失と検証損失が共に減少していれば正常な学習であり、検証損失が上昇に転じた場合は学習を止める目安となる。

演習

演習3.予測に使う過去データ数と予測精度の関係を調べる

手順

  1. 上記の実装例をColabで実行し、テストRMSEを記録する。
  2. SEQUENCE_LENGTHの値を60から20に変更する。
  3. 再度実行し、テストRMSEと時系列予測グラフを記録する。

ヒント

SEQUENCE_LENGTHは1つの予測に使う過去の日数である。値を変えてもコードの他の部分の修正は不要である。

考察ポイント

過去データ数を減らすとテストRMSEはどう変化したか。グラフ上で予測が実際の価格にどの程度追従しているかを読み取る。

データの可視化と分析プロジェクト

概要

このセクションでは、データの可視化と統計分析の基本を学ぶ。完了後、データの基本統計量の算出、各種グラフの作成、相関分析ができるようになる。

データ分析とは、データに含まれる傾向やパターンを発見し、有用な情報を抽出する技術である。

Irisデータセットは、アヤメの花の測定データであり、統計学や機械学習の教材として広く使用されている。データが整理されており欠損値がないため、データ分析の基本手法を学ぶのに適しているが、実際のデータはより複雑である。

データ分析の特殊概念

基本統計量は、平均、標準偏差、最大値、最小値などデータの基本的な特徴を表す数値である。

ヒストグラムは、データの分布を表示するグラフである。横軸に値の範囲、縦軸に頻度を取り、データがどの範囲に集中しているかを確認できる。

散布図は、2つの変数の関係を点で表示するグラフである。点の配置から変数間の関係を視覚的に把握できる。

箱ひげ図(box plot)は、データの分布を四分位数で表示するグラフである。四分位数とは、データを小さい順に並べて4等分した位置の値であり、第1四分位数(25%点)、中央値(50%点)、第3四分位数(75%点)からなる。

相関係数は、2つの変数間の線形関係の強さを-1から1の値で表す指標である。1に近いほど正の相関(一方が増えると他方も増える)、-1に近いほど負の相関(一方が増えると他方は減る)、0に近いほど関係が弱いことを示す。線形関係のみを測定するため、非線形の関係は検出できない。

ヒートマップは、数値データを色の濃淡で表現したグラフであり、複数の変数間の関係を一覧できる。

実装例

以下のコードは、Irisデータセットを用いて統計分析と可視化を行う。コードは「環境設定」「データ読み込み」「基本情報表示」「可視化」「相関分析」の順に構成されている。

# Irisデータセットの統計分析・可視化システム

# --- 環境設定 ---
# 日本語フォントのインストールと設定(IPAゴシックをmatplotlibに登録する)
# seabornはフォント設定を上書きするため、rcParamsはseaborn設定の後に当てる
!apt-get -qq install -y fonts-ipafont-gothic
import matplotlib
import matplotlib.pyplot as plt
import seaborn as sns
matplotlib.font_manager.fontManager.addfont(
    '/usr/share/fonts/opentype/ipafont-gothic/ipagp.ttf')
sns.set_theme(font='IPAPGothic')
plt.rcParams['font.family'] = 'IPAPGothic'
plt.rcParams['axes.unicode_minus'] = False

import numpy as np
import pandas as pd
from sklearn.datasets import load_iris

np.random.seed(42)

# --- データ読み込み ---
iris = load_iris(as_frame=True)
df = iris.frame.copy()
feature_cols = list(iris.feature_names)             # 数値特徴量の列名
df['species'] = iris.target_names[iris.target]      # 種名の列を追加

# --- 基本情報表示 ---
print("=== データセット基本情報 ===")
print(f"データサイズ: {df.shape}")
print(f"欠損値: {df.isnull().sum().sum()}")
print(f"数値特徴量: {len(feature_cols)}")
print("\nデータ例:")
print(df.head())
print("\n基本統計量:")
print(df[feature_cols].describe())

# --- 可視化 ---
fig, axes = plt.subplots(3, 2, figsize=(15, 18))

# ヒストグラム:データの分布を確認
axes[0, 0].set_title('がく片の長さ分布')
for species in df['species'].unique():
    axes[0, 0].hist(df[df['species'] == species]['sepal length (cm)'], alpha=0.7, label=species, bins=15)
axes[0, 0].set_xlabel('長さ (cm)')
axes[0, 0].set_ylabel('頻度')
axes[0, 0].legend()
axes[0, 0].grid(True, alpha=0.3)

# 散布図:2変数間の関係を確認
axes[0, 1].set_title('がく片の長さ vs 幅')
for species in df['species'].unique():
    subset = df[df['species'] == species]
    axes[0, 1].scatter(subset['sepal length (cm)'], subset['sepal width (cm)'], label=species, alpha=0.7, s=60)
axes[0, 1].set_xlabel('がく片の長さ (cm)')
axes[0, 1].set_ylabel('がく片の幅 (cm)')
axes[0, 1].legend()
axes[0, 1].grid(True, alpha=0.3)

# 箱ひげ図:グループ間の分布を比較
axes[1, 0].set_title('種別の花弁長さ比較')
df.boxplot(column='petal length (cm)', by='species', ax=axes[1, 0])
axes[1, 0].set_xlabel('種')
axes[1, 0].set_ylabel('花弁の長さ (cm)')
axes[1, 0].grid(True, alpha=0.3)

# ヒートマップ:変数間の相関を一覧表示
axes[1, 1].set_title('特徴量間の相関')
sns.heatmap(df[feature_cols].corr(), annot=True, cmap='coolwarm', center=0, ax=axes[1, 1], fmt='.3f')

# 散布図(花弁)
axes[2, 0].set_title('花弁の長さ vs 幅')
for species in df['species'].unique():
    subset = df[df['species'] == species]
    axes[2, 0].scatter(subset['petal length (cm)'], subset['petal width (cm)'], label=species, alpha=0.7, s=60)
axes[2, 0].set_xlabel('花弁の長さ (cm)')
axes[2, 0].set_ylabel('花弁の幅 (cm)')
axes[2, 0].legend()
axes[2, 0].grid(True, alpha=0.3)

# バイオリンプロット:分布の形状を表示
axes[2, 1].set_title('特徴量分布の比較')
melted = df.melt(id_vars=['species'], value_vars=feature_cols,
                 var_name='feature', value_name='value')
sns.violinplot(data=melted, x='species', y='value', ax=axes[2, 1])
axes[2, 1].set_xlabel('種')
axes[2, 1].set_ylabel('測定値')
axes[2, 1].tick_params(axis='x', rotation=45)

plt.tight_layout()
plt.show()

# --- 相関分析 ---
corr_matrix = df[feature_cols].corr()
upper = corr_matrix.where(np.triu(np.ones(corr_matrix.shape), k=1).astype(bool))
max_corr = upper.abs().max().max()
max_corr_idx = upper.abs().stack().idxmax()

print("\n=== 分析結果サマリー ===")
print(f"最大相関係数: {max_corr:.3f}")
print(f"最大相関ペア: {max_corr_idx[0]} - {max_corr_idx[1]}")
print("\n種別件数:")
for species in df['species'].unique():
    print(f"{species}: {len(df[df['species'] == species])}件")
print("\n相関の目安:")
print("- 相関係数の絶対値が0.7以上: 強い相関")
print("- 相関係数の絶対値が0.3-0.7: 中程度の相関")
print("- 相関係数の絶対値が0.3未満: 弱い相関")

実行結果の読み方

基本統計量は平均、標準偏差、最大値、最小値などデータの基本的な特徴であり、外れ値の有無や分布の偏りを確認できる。

相関係数は-1.0から1.0の範囲で、絶対値が0.7以上であれば強い相関、0に近い場合は関係が弱い(目安である)。

グラフの読み方として、ヒストグラムでは各特徴量の分布形状を確認し、正規分布に近いか偏りがあるかを判断する。散布図では2つの特徴量間の関係を確認し、点が直線状に並んでいれば相関があることを示す。箱ひげ図ではグループ間の特徴量の違いを比較し、箱の位置や大きさから分布の差を読み取る。ヒートマップでは色の濃淡で相関の強さを表示し、赤は正の相関、青は負の相関を示す。

演習

演習4.相関の強い特徴量ペアを特定する

手順

  1. 上記の実装例をColabで実行し、ヒートマップと最大相関ペアの出力を確認する。
  2. 最大相関ペアにあたる2つの特徴量を、花弁または がく片の散布図と照らし合わせる。
  3. 3種のアヤメが散布図上でどの程度分かれているかを確認する。

ヒント

ヒートマップの数値(絶対値)が大きいほど相関が強い。散布図上で点が直線状に並ぶ特徴量ペアが、相関の強いペアに対応する。

考察ポイント

最大相関ペアは散布図上でどのように見えるか。相関の強い特徴量と、3種の分離に役立つ特徴量が一致するかを読み取る。

学習結果の共通評価方法

本セクションでは、ニューラルネットワークモデルの評価方法をまとめる。各プロジェクトの結果を解釈する際の参考とする。以下の数値は目安であり、適切な水準はデータ・モデル・目的によって変わる。

精度評価の共通指標

過学習をどの指標で判断するかについて、過学習は原則として、訓練データに対する指標と検証データに対する指標の乖離で判断する。訓練指標は良いのに検証指標が悪い(または途中から悪化する)場合が過学習の典型である。検証データを分けず訓練・テストの2分割のみを行ったプロジェクト(本稿の時系列予測)では、検証指標の代わりにテスト指標との乖離で判断するが、本来は検証指標で学習中に早期に気づくのが望ましい。

訓練精度と検証精度の関係について、両者が近い値で上昇している場合は正常な学習である。検証精度が訓練精度より大幅に低い場合は過学習の可能性があり、検証精度が上昇しない場合は学習パラメータの調整が必要である。

損失値の推移について、訓練損失と検証損失が共に減少している場合は正常な学習進行である。検証損失が上昇に転じた場合は過学習の兆候であり、学習を止める目安となる。損失が減少しない場合はパラメータの再調整が必要である。

分類タスクの追加指標について、正解率だけでなく、混同行列・適合率・再現率・F1スコアを併せて確認することで、クラスごとの性能や取り違えの傾向を把握できる。クラスごとのデータ件数に偏りがある場合に特に有効である。

各タスクの性能の目安

画像分類(CIFAR-10)では、10クラス分類のため、でたらめに答えた場合の正解率は約0.1である。これを大きく上回っていれば学習が進んでいる。目安として0.7程度を一つの到達点とする。

感情分析(IMDb)では、二値分類のため、でたらめに答えた場合の正解率は約0.5である。目安として0.8程度を一つの到達点とする。

時系列予測(株価)では、テストRMSEが小さいほど予測精度が高い。テストRMSEが訓練RMSEより大きくなること自体は珍しくないが、差が極端に大きい場合は過学習を疑う。

うまくいかない場合の対処

精度が向上しない場合は、エポック数の増加、バッチサイズの調整、モデル構造の変更(層の追加・削除)を試す。過学習が発生する場合は、ドロップアウト率の増加、訓練データの増加、エポック数の削減を検討する。