忍者ブログ
統計、機械学習、AIを学んでいきたいと思います。 お役に立てば幸いです。

【TensorFlow】2次元のテンソル(行列)同士で四則演算と行列積を行う基本コード

前回のスカラー(単一の数値)の演算に続き、今回はAIや機械学習の計算で基本となる**「2次元のテンソル(行列)」**同士の演算方法を整理します。行列の足し算や、要素ごとの掛け算、そしてディープラーニングの内部で多用される「行列積(ドット積)」の具体的な書き方をコードと出力結果で確認していきましょう。

1. 【 概要 】

2次元のテンソルとは、行と列を持つ「行列(Matrix)」のデータ構造です。

tf.constant() に入れ子のリスト(リストのリスト)を渡すことで、2次元テンソルを簡単に作成できます。Python標準の数値計算と異なり、TensorFlowの関数や演算子は自動的に並列計算や効率的な行列処理を行ってくれます。


2. 【 基本手順(2つのステップ) 】

(1) 入れ子リストを用いた2次元テンソルの定義
matrix_a = tf.constant([[1, 2], [3, 4]]) のように、行と列を持つデータを定義する。
(2) 要素ごとの演算(要素積)と、数学的な行列積の実行
通常の四則演算子による「要素ごとの計算」と、tf.matmul() による「行列積」を使い分ける。

3. 整理:行列演算の「要素ごとの計算」と「行列積」の違い

行列を扱う際につまずきやすい「2つの計算方法の違い」を整理しておきましょう。

【 演算の種類と特徴 】

・要素ごとの演算(+, -, *, /)
同じ位置にある要素同士(例:1行1列目同士)で計算を行います。例えば A * B を行うと、それぞれの要素が掛け合わされます(アダマール積)。

・行列積(tf.matmul)
線形代数における通常の「行列のかけ算(行×列の計算)」を行います。ディープラーニングの全結合層などで頻繁に使用される極めて重要な計算です。

・出力値(shape)の確認
2次元テンソルを出力すると shape=(2, 2) のように表示され、何行何列のデータ構造であるかが一目でわかります。

4. 関連して押さえたい「2次元テンソル演算の主な関数」

TensorFlowで2次元テンソルを操作する際に役立つ代表的な関数です。

・要素ごとの掛け算:tf.multiply(a, b) (演算子: a * b)
・数学的な行列積:tf.matmul(a, b) (演算子: a @ b)
・形状の確認や変更:tensor.shape や tf.reshape()


5. 補足:Pythonでの実装コード例

Google Colab等でそのまま実行できる2次元テンソル演算のサンプルコードと実際の実行結果です。

# コード実行例
import tensorflow as tf

# 1. 2つの2次元テンソル(2x2行列)を定義
matrix_a = tf.constant([[1, 2],
[3, 4]])
matrix_b = tf.constant([[5, 6],
[7, 8]])

# 2. 要素ごとの加算と掛け算
add_result = matrix_a + matrix_b # 行列の足し算
element_mul = matrix_a * matrix_b # 要素ごとの掛け算(アダマール積)

# 3. 本格的な行列積(ドット積)
matmul_result = tf.matmul(matrix_a, matrix_b)

# 4. 結果の表示
print("--- 行列の足し算 ---")
print(add_result.numpy())
print("\n--- 要素ごとの掛け算 ---")
print(element_mul.numpy())
print("\n--- 行列積(tf.matmul) ---")
print(matmul_result.numpy())

# 出力結果
# --- 行列の足し算 ---
# [[ 6 8]
# [10 12]]
#
# --- 要素ごとの掛け算 ---
# [[ 5 12]
# [21 32]]
#
# --- 行列積(tf.matmul) ---
# [[19 22]
# [43 50]]

6. まとめ

TensorFlowにおける2次元テンソルは、tf.constant() で直感的に定義でき、通常の足し算や要素ごとの計算だけでなく、AI開発に不可欠な「行列積(tf.matmul)」を簡単に計算できます。データが何次元でどのように処理されているかを意識することで、AIモデルの構造もぐっと理解しやすくなります!


PR

【DS検定対策】パラメータごとに学習率を自動調整!「AdaGrad」の仕組み

ディープラーニングの学習において、すべてのパラメータに同じ「学習率」を使い続けるのは効率が悪い場合があります。そこで、過去の勾配の大きさに応じて「パラメータごとに個別の学習率を自動で更新・調整する」仕組みを持った最適化手法が「AdaGrad(アダグラッド)」です!

1. 【 問題 】

ニューラルネットワークの最適化アルゴリズムにおいて、これまでの学習で得られた勾配の二乗和を蓄積し、それを利用してパラメータごとに学習率を自動的に小さく調整しながら最適化を行う手法を何と呼ぶでしょうか?

① AdaGrad(Adaptive Gradient Algorithm)
② モーメンタム(Momentum)
③ 確率的勾配降下法(SGD)
④ バッチ正規化(Batch Normalization)


2. 【 解答 】

正解: ① AdaGrad(Adaptive Gradient Algorithm)

3. 整理:AdaGradの仕組みと画期的なポイント

通常のSGDでは固定だった「学習率」を、パラメータごとにどう変化させるのかを整理しましょう。

項目の特徴内容と動作
勾配の二乗和の蓄積 これまでに発生した勾配(の二乗)をすべて足し算して記憶していきます。たくさん大きく動いたパラメータほど、蓄積値が大きくなります。
学習率の自動調整
(適応的学習率)
分母に「勾配の二乗和の平方根」を置くことで、大きく激しく動いたパラメータの学習率は自動的に小さく(ブレーキ)なり、あまり動いていないパラメータの学習率は相対的に大きく(アクセル)なります。

4. AdaGradのメリットと「致命的な弱点」

【 メリット 】
・自分で学習率を手動で細かくチューニングしなくても、パラメータごとに最適なペースで効率よく学習が進みます。

【 致命的な弱点(学習の停滞) 】
・過去の勾配の二乗和を「ずっと無限に足し続け」ていくため、学習が進めば進むほど分母(過去の蓄積)がどんどん巨大になります。
・その結果、学習率が実質的にゼロに近づき、途中で学習が完全に止まってしまう(動かなくなる)という弱点があります。(※この弱点を解決したのが RMSprop や Adam です!)

5. DS検定形式:実戦4択クイズ

問:ディープラーニングの最適化アルゴリズムである「AdaGrad」に関する記述として、最も適切なものはどれか。

① 過去のすべての勾配の二乗和を分母に蓄積し、パラメータごとに個別の学習率を自動調整して最適化を行う手法である。
② 物理の慣性(運動量)の概念を取り入れ、前回のパラメータ更新の方向と勢いを次の更新に引き継ぐ手法である。
③ ミニバッチごとにデータの平均と分散を計算し、強制的にデータを標準化して勾配消失を防ぐ手法である。
④ ネットワークの過学習を防止するために、ランダムに一部のニューロンの結合を遮断しながら学習を進める手法である。

【 正解: ① 】

解説: AdaGradの定義と特徴を問う標準問題です。
①が正解です。勾配の二乗和を蓄積し、パラメータごとの学習率を自動調整します。
②は「モーメンタム」の説明です。
③は「バッチ正規化」の説明です。
④は「ドロップアウト」の説明です。


6. まとめ

DS検定や資格試験で「パラメータごとの学習率の自動調整」「過去の勾配の二乗和を蓄積」「学習が途中で止まってしまうリスク」といったキーワードが出たら、正解は「AdaGrad」です! 先ほどのモーメンタム(慣性)とあわせて、最適化アルゴリズムの代表例としてしっかり押さえておきましょう!

【DS検定対策】慣性の力で最適化を加速!「モーメンタム(Momentum)」の仕組み

ディープラーニングの学習で使われる通常の確率的勾配降下法(SGD)は、パラメータが非効率なジグザグ運動をしてしまい学習が遅くなる弱点があります。これを解決するために「物理の慣性(運動量)」の考え方を導入した最適化手法が「モーメンタム(Momentum)」です!

1. 【 問題 】

ニューラルネットワークの最適化アルゴリズムにおいて、通常の確率的勾配降下法(SGD)に「前回の更新の勢い(慣性)」を意味する項を加え、パラメータの振動を抑えて収束を高速化させる手法を何と呼ぶでしょうか?

① モーメンタム(Momentum)
② ドロップアウト(Dropout)
③ バッチ正規化(Batch Normalization)
④ グリッドサーチ(Grid Search)


2. 【 解答 】

正解: ① モーメンタム(Momentum)

3. 整理:なぜモーメンタムが必要なのか?(転がるボールの例え)

通常のSGDとモーメンタムの違いは、「坂道を転がり落ちるボール」に例えると非常にわかりやすくなります。

手法動きの特徴・メリット / デメリット
通常のSGD
(確率的勾配降下法)
その瞬間の傾きだけで進むため、谷底に向かって左右に激しくジグザグと蛇行(振動)してしまい、なかなか効率よく進めない。
モーメンタム
(Momentum)
「前回の勢い(慣性)」を引き継ぐため、左右の無駄な揺れ(振動)が打ち消し合って相殺され、谷底の方向へまっすぐ加速(高速化)できる。

4. モーメンタムが持つ2つの強力なメリット

① 振動の抑制と学習の高速化:
・ジグザグ動くエネルギーが相殺されるため、効率的かつスピーディーに最適値(最小値)へ到達できます。

② 局所最適解(ローカルミニマム)の突破:
・学習途中で小さな窪み(行き止まり)にハマりそうになっても、これまでの「勢い(慣性)」があるため、勢いでその窪みを飛び越えて進むことができます。

5. DS検定形式:実戦4択クイズ

問:ディープラーニングの最適化アルゴリズムである「モーメンタム(Momentum)」に関する記述として、最も適切なものはどれか。

① 学習率を一定に保つのではなく、エポックが進むにつれて学習率を強制的にゼロまで直線的に減衰させる手法である。
② 物理の慣性(運動量)の概念を導入し、前回のパラメータ更新の方向と大きさを一定の割合で今回の更新に反映させることで、振動を抑え最適化を高速化する手法である。
③ ネットワークの過学習を防ぐため、ランダムに一部のニューロンを無効化しながら学習を進める正則化手法である。
④ ミニバッチごとのデータの偏りをなくすために、各層の入力データの平均と分散を強制的に正規化する手法である。

【 正解: ② 】

解説: モーメンタムの目的と仕組みを問う標準問題です。
②が正解です。慣性の力を取り入れて最適化をスムーズかつ高速にします。
①は学習率減衰(Learning Rate Decay)の説明です。
③は「ドロップアウト(Dropout)」の説明です。
④は「バッチ正規化(Batch Normalization)」の説明です。


6. まとめ

DS検定や資格試験で「物理の慣性」「前回の更新の勢いを引き継ぐ」「ジグザグした振動を抑える」「最適化の高速化」といったキーワードが出たら、正解は「モーメンタム(Momentum)」です! SGDの弱点である「非効率な蛇行」をカバーする必須の拡張手法として覚えておきましょう!

【DS検定対策】ビッグデータのメモリを救う!「特徴量ハッシング」の仕組み

テキストの単語数やカテゴリ変数の種類が数百万〜数千万個に膨れ上がると、コンピュータのメモリが足りなくなったり学習が極端に遅くなったりします。そんな高次元データを一瞬で圧縮・削減する巧妙な技術が「特徴量ハッシング(Feature Hashing)」(別名:ハッシュ化トリック)です!

1. 【 問題 】

機械学習の前処理において、元の特徴量(単語やカテゴリ)をハッシュ関数に通してあらかじめ決まった固定長のインデックス(番号)に変換し、メモリ消費を抑えながら次元数を削減する手法を何と呼ぶでしょうか?

① 特徴量ハッシング(Feature Hashing / ハッシュ化トリック)
② 主成分分析(PCA:Principal Component Analysis)
③ 埋め込み法(Embedded Method)
④ ラッパー法(Wrapper Method)


2. 【 解答 】

正解: ① 特徴量ハッシング(Feature Hashing / ハッシュ化トリック)

3. 整理:特徴量ハッシングの仕組みとメリット

通常の One-Hot エンコーディングや辞書作成(全単語を記憶する方式)とは異なり、ハッシングには大きな特徴があります。

特徴・仕組み内容
辞書が不要
(メモリ節約)
「どの単語がどの番号か」という全辞書をメモリに保持しておく必要がありません。ハッシュ関数に文字を通すだけで、その場で一意の番号(例:0〜1000のいずれか)に変換されます。
固定長の次元削減 どれだけ新しい単語や未知のデータが増えても、出力先の次元数をあらかじめ「1000次元」や「10000次元」など固定サイズに抑えることができます。

4. 注意点:ハッシュ衝突(Collision)

非常に便利なハッシングですが、致命的なトレードオフも存在します。

・ハッシュ衝突(Collision):
まったく異なる意味の2つの単語(例:「リンゴ」と「みかん」)が、ハッシュ関数によって偶然「同じ番号(インデックス)」に割り振られてしまう現象のことです。

・対策:
出力先の次元数を十分に大きく設定することで衝突確率を下げる、あるいは線形モデルなどの影響を受けにくいアルゴリズムと組み合わせることで実用上問題なく利用されます。

5. DS検定形式:実戦4択クイズ

問:機械学習における「特徴量ハッシング(ハッシュ化トリック)」に関する記述として、最も適切なものはどれか。

① 主成分分析(PCA)と同様に、データから共分散行列を計算して分散の最大化を図ることで新しい直交軸を作り出す教師なし次元削減手法である。
② ハッシュ関数を用いて高次元な特徴量を固定長の小さな空間にマッピング(変換)することで、単語の辞書を持たずにメモリを節約しながら次元削減を行う手法である。
③ 決定木の分岐をハッシュ化して高速に検索するアルゴリズムであり、ランダムフォレストの計算速度を何倍にも高速化する専用の前処理である。
④ 異なる特徴量が偶然同じインデックスに変換される「ハッシュ衝突」は理論上絶対に発生しないため、高精度なテキスト分類に最適である。

【 正解: ② 】

解説: 特徴量ハッシングの目的と仕組みを問う標準問題です。
②が正解です。ハッシュ関数で固定長にマッピングし、メモリを節約しつつ次元を削減します。
①は「主成分分析(PCA)」の説明です。
③決定木の高速化技術ではなく、主に高次元スパースデータ(テキスト等)の圧縮技術です。
④ハッシュ衝突は原理上発生する可能性があります(そのため次元数を適切に大きく取ります)。


6. まとめ

DS検定や資格試験で「ハッシュ関数」「辞書を持たない」「固定長への変換」「メモリ節約・次元削減」「ハッシュ衝突」といったキーワードが出たら、正解は「特徴量ハッシング(ハッシュ化トリック)」です! 主成分分析(PCA)との違い(計算をせず関数で一発変換する点)も含めて整理しておきましょう!

【Kaggle挑戦記】TPS Feb 2022:1.1万行の「重複データの罠」を破り、本番LBスコアを更新!

前回の検証では、手動での特徴量生成を行わずに素のデータをLightGBMへ投入した結果、手元CVが0.99553(約99.5%)という異次元の高精度を記録した一方で、Kaggle本番のLeaderboard(LB)では0.93937まで急降下するという大きなスコアギャップに直面しました。

今回は、この乖離の原因である「データ重複によるリーク(過学習)」を解消し、正しく検証環境を再構築してLBスコアの更新に挑みました。

1. なぜ手元スコアと本番スコアが大きくズレたのか?

分析を進めたところ、全20万行ある train.csv の中に、全く同じ特徴量(286列の数値)を持つ「重複行」が約1.1万行(全体の約5%以上)も存在していることが判明しました。

  • 丸暗記による過学習: 通常の StratifiedKFold で分割すると、全く同じデータが「学習用」と「検証用」の両方に分散して入り込んでしまいます。結果としてモデルがデータを暗記してしまい、手元のCVだけが異常に高く(0.99553)評価されていました。
  • 本番データでの失速: Kaggleのテストデータには未知のデータが含まれるため、暗記に頼ったモデルは本番環境(LB 0.93937)で威力を発揮できませんでした。

2. 対策:重複行の一元化と sample_weight の導入

単に重複行を削除(drop_duplicates)するだけでは、「そのデータがどれだけ高頻度で出現するか」という重要な確率情報(重み)が失われてしまいます。

そこで、groupby().size() を活用して重複行を1行に集約しつつ、出現回数を sample_weight(サンプルの重み)として LightGBM に与えて学習させるアプローチ(sample2.py)へコードを全修正しました。

import pandas as pd
import numpy as np
from sklearn.model_selection import StratifiedKFold
from sklearn.preprocessing import LabelEncoder
from sklearn.metrics import accuracy_score
import lightgbm as lgb
import warnings

warnings.filterwarnings("ignore")

# 1. データの読み込みと重複処理
print("Loading data...")
train = pd.read_csv('train.csv')
test = pd.read_csv('test.csv')

X_raw = train.drop(columns=['row_id'])
X_test = test.drop(columns=['row_id'])

# 特徴量とターゲットが全く同じ行を集計して重み(sample_weight)を作成
features = [col for col in X_raw.columns if col != 'target']
train_dedup = X_raw.groupby(features + ['target']).size().reset_index(name='sample_weight')

X = train_dedup[features]
y_raw = train_dedup['target']
sample_weight = train_dedup['sample_weight']

print(f"元のデータ数: {len(train)} 行")
print(f"重複除去後のデータ数: {len(X)} 行")  # 123,993 行へ圧縮

# ターゲットの数値変換
le = LabelEncoder()
y = le.fit_transform(y_raw)

# 2. 多クラス分類パラメータ
params = {
    'objective': 'multiclass',
    'num_class': 10,
    'metric': 'multi_logloss',
    'verbosity': -1,
    'boosting_type': 'gbdt',
    'learning_rate': 0.1,
    'random_state': 42
}

# 3. Stratified K-Fold(sample_weight付き)
skf = StratifiedKFold(n_splits=5, shuffle=True, random_state=42)
oof_preds = np.zeros((len(X), 10))
test_preds = np.zeros((len(test), 10))

for fold, (train_idx, val_idx) in enumerate(skf.split(X, y)):
    X_train, y_train, w_train = X.iloc[train_idx], y[train_idx], sample_weight.iloc[train_idx]
    X_val, y_val, w_val = X.iloc[val_idx], y[val_idx], sample_weight.iloc[val_idx]
    
    model = lgb.LGBMClassifier(**params, n_estimators=1000)
    
    # fit時に sample_weight を適用
    model.fit(
        X_train, y_train,
        sample_weight=w_train,
        eval_set=[(X_val, y_val)],
        eval_sample_weight=[w_val],
        callbacks=[lgb.early_stopping(stopping_rounds=50, verbose=False)]
    )
    
    oof_preds[val_idx] = model.predict_proba(X_val)
    test_preds += model.predict_proba(X_test) / skf.n_splits

# 4. 正しい重み付き評価(Accuracy)の算出
oof_classes = np.argmax(oof_preds, axis=1)
cv_accuracy = accuracy_score(y, oof_classes, sample_weight=sample_weight)
print(f"修正後の手元交差検証スコア(Accuracy): {cv_accuracy:.5f}")

# 5. 提出用ファイルの作成
test_classes_num = np.argmax(test_preds, axis=1)
test_classes_str = le.inverse_transform(test_classes_num)

submission = pd.DataFrame({
    'row_id': test['row_id'],
    'target': test_classes_str
})
submission.to_csv('submission_tps_feb2022_dedup_lgb.csv', index=False)

3. 実行結果とスコア比較

重複一元化を行ったモデルの実行ログおよびKaggleへ再提出(Late Submit)した結果の推移です。

Loading data...
Processing duplicates...
元のデータ数: 200000 行
重複除去後のデータ数: 123993 行

Starting Cross Validation with sample_weight...
 Fold 1 finished.
 Fold 2 finished.
 Fold 3 finished.
 Fold 4 finished.
 Fold 5 finished.

========================================
修正後の手元交差検証スコア(Accuracy): 0.95766
========================================

【実験結果の比較表】

手法手元CV (Accuracy)Kaggle Public LB評価
1. 生データ (無加工) 0.99553 0.94192 (0.93937) 重複データによるリークでCVが歪んでいた
2. 重複除去 + sample_weight 0.95766 0.94317 (Private: 0.94024) CVが健全化し、本番スコアも着実に向上!

4. 考察とエンジニアとしての気づき

手元のCVスコアは前回の「丸暗記による異常値(0.99553)」から0.95766へと下がり、現実的で信頼できる検証値へと正常化されました。そして何より、Kaggle本番(Public LB)のスコアが 0.94192 → 0.94317 へとしっかり向上したことが大きな成果です。

  • 検証環境(CV)の重要性: 「CVスコアが高ければ良い」わけではなく、適切な前処理によって本番環境(テストデータ)の挙動を正しく反映する検証環境を作ることが最優先であると再認識しました。
  • 重み付けの有効性: 単純な drop_duplicates でデータ量を減らすだけでなく、出現回数を sample_weight としてモデルに伝えることで、情報量を維持したまま過学習を防止できました。

手元検証の基盤が整ったため、いよいよ次回はこのコンペの「真のキーパーツ」であるドメイン知識(GCD:最大公約数を用いた実験スケールの復元)を取り入れ、本番スコアの上位帯(0.98以上)への到達を目指します。


次回は、GCD(最大公約数)特徴量の計算・追加によるモデルの飛躍的な精度向上に挑戦します。