見出し画像

畳み込みニューラルネットワークを用いた打撃結果の予測


 はじめに

xwOBAの計算式はおそらくk-NN(k近傍法)のような手法を用いている。しかし、単純にk-NNを用いてxwOBAの各打撃結果を予測しても面白くないので、今回は手始めとして畳み込みニューラルネットワークを用いて、打撃結果を予測することとし,最終的にはxwOBAのような指標nnwOBA(neural network wOBA)の作製を目的とした。

畳み込みニューラルネットワークとは

畳み込みニューラルネットワーク(Convolutional Neural Network、CNN)は、深層学習の一種で、主に画像認識や音声認識などのタスクに使用される。

なぜ畳み込みニューラルネットワークを用いるのか

今回のテーマである打球角度や打球速度は、音声のような時系列データや画像データとは異なり、特徴量間に空間的連続性は無いと考えられるが、それらはある種の「空間的連続性」を持つともいえる。
例えば、バレルゾーンなど、打球速度と角度によるものである。したがって、打撃の速度と角度は、それぞれ独立した特徴量であると同時に、打撃の結果を決定するための「空間」を形成していると考えられる。
よって、畳み込みニューラルネットワークは、打撃結果の分類問題にも適用できるのではないかと考えた。

データセット

2023年にIn Playとなった打球のデータを、Baseball Savantにより取得した。

特徴量

説明変数として、打球速度(launch_speed)、打球角度(launch_angle)を用いた。また、説明変数には標準化を行った。
目的変数として、打撃結果をアウト=0、単打=1、二塁打=2、三塁打=3、本塁打=4と分類した。

モデル

以下のような構造のモデルを構築した。
バッチサイズは128、エポック数は128、最適化アルゴリズムはAdam、学習率は0.001に設定した。

畳み込み層

64個のフィルターを持つ1次元の畳み込み層を用いた。カーネルサイズは2である。また、活性化関数として、Sigmoidを用いた。

ドロップアウト層

過学習を防ぐため、ドロップアウト層を設けた。

Flatten層

Flatten層により、畳み込み層の出力を1次元のベクトルに変換した。

バッチ正規化層

バッチ正規化を行った。

全結合層

ノード数64と32の全結合層を設けた。いずれも活性化関数として、Sigmoidを用いた。

出力層

最後に、出力層として全結合層を設けた。ノード数はクラス数と同じ5つである。今回は分類問題であるので、活性化関数はsoftmaxを用いた。

結果

精度

正解率は、0.7778であった。

学習曲線

以下に、学習曲線を示す(fig. 1)。

fig. 1 学習曲線

考察・まとめ

今回は、正解率0.7778のモデルを構築することができた。このモデルの作製時にかなりハイパーパラメータをいじり回したが、どのようにしても正解率が0.78を超えなかったので、残りの23%は守備や球場の影響により、変動するのではないかと考えた。
むしろ0.77といった精度は悪いことではなく、球場や守備の補正を行うことができているということであると考えることができる。
いずれにしろ出力層がsoftmax関数であるので、それぞれの打撃結果が割合で出力されるので、wOBAの係数を算出する際は、結果に重み付けを行うことができるということである。

今後の展望として、このモデルを用いて、打球角度と打球速度を用いたwOBA、nnwOBAの提案を行っていきたい。
また、「このような手法が良いのではないか」、「お前の知見浅すぎ」等の厳しい意見も含め、何かあればコメントで指摘していただけるとありがたい。

以上


おまけ

コード

# ライブラリをインポート
import pandas as pd
import tensorflow as tf  
from sklearn.model_selection import train_test_split
from sklearn.preprocessing import MinMaxScaler, LabelEncoder,StandardScaler
from tensorflow.keras.models import Sequential
from tensorflow.keras.layers import Dense, Dropout, BatchNormalization
import matplotlib.pyplot as plt
from tensorflow.keras.layers import Conv1D, Flatten

# データの読み込み
data = pd.read_csv('C:/Users/<username>/Documents/<your_project>/data/2023_use.csv')
data = data.dropna() #欠損値の削除

# 'catcher_interf' を除外
data = data[data['events'] != 'catcher_interf']
data.dropna(inplace=True)

# 必要な列を選択
features = data[['launch_speed', 'launch_angle']]
labels = data['events']

# ラベルのエンコーディング
label_encoder = LabelEncoder()
labels_encoded = label_encoder.fit_transform(labels)

# データの分割
X_train, X_temp, y_train, y_temp = train_test_split(features, labels_encoded, test_size=0.3, random_state=42)
X_val, X_test, y_val, y_test = train_test_split(X_temp, y_temp, test_size=0.5, random_state=42)

# データの標準化
scaler = StandardScaler()
X_train = scaler.fit_transform(X_train)
X_val = scaler.transform(X_val)
X_test = scaler.transform(X_test)

# データの形状を変更
X_train = X_train.reshape(-1, 2, 1)
X_val = X_val.reshape(-1, 2, 1)
X_test = X_test.reshape(-1, 2, 1)

model = Sequential([
    Conv1D(64, 2, activation='sigmoid', input_shape=(X_train.shape[1], X_train.shape[2])),
    Dropout(rate=0.5),
    Flatten(),
    BatchNormalization(),
    Dense(64, activation='sigmoid'),
    Dense(32, activation='sigmoid'),
    Dense(len(label_encoder.classes_), activation='softmax')  # 出力層
])

# オプティマイザの作成
optimizer = tf.keras.optimizers.Adam(learning_rate=0.001)

# モデルのコンパイル
model.compile(optimizer=optimizer,
              loss='categorical_crossentropy',  
              metrics=['accuracy'])


# モデルのトレーニング
history = model.fit(X_train, tf.keras.utils.to_categorical(y_train), epochs=128, batch_size=128, validation_data=(X_val, tf.keras.utils.to_categorical(y_val)))

# モデルの評価
loss, accuracy = model.evaluate(X_test, tf.keras.utils.to_categorical(y_test))
print(f'Test Accuracy: {accuracy:.4f}')

# モデルの保存
model.save('conv.h5')


# プロットのサイズを指定
plt.figure(figsize=(12, 6))

# トレーニングとバリデーションの精度のプロット
plt.subplot(1, 2, 1)
plt.plot(history.history['accuracy'], label='Train Accuracy')
plt.plot(history.history['val_accuracy'], label='Validation Accuracy')
plt.xlabel('Epoch')
plt.ylabel('Accuracy')
plt.title('Training and Validation Accuracy')
plt.legend()

# トレーニングとバリデーションの損失をプロット
plt.subplot(1, 2, 2)
plt.plot(history.history['loss'], label='Train Loss')
plt.plot(history.history['val_loss'], label='Validation Loss')
plt.xlabel('Epoch')
plt.ylabel('Loss')
plt.title('Training and Validation Loss')
plt.legend()

plt.tight_layout()

# 1つの画像に保存
plt.savefig('training_validation_loss_accuracy_combined_conv.png')
plt.show()





この記事が気に入ったらサポートをしてみませんか?