4
4

More than 1 year has passed since last update.

ゼロから始める機械学習 上位を狙う編

Last updated at Posted at 2021-08-19

最終目的

プログラミングを全くやったことがないという状態から,機械学習を行えるようになるまで,順に勉強する.

これまでの勉強会は下記

機械学習のチューニングの流れ

コンペで上位を狙うために特徴量を作成する.

※結果は9位だった.

流れ

  • ライブラリのインポート
  • GPU環境チェック
  • 関数設定
  • データ読み込み
  • 相関があるデータの作成
  • 特徴量作成
  • 学習モデルの作成
  • 結果の出力

#ライブラリのインポート

.py
import pandas as pd
import numpy as np
import glob #ファイル読み込み用
import os
import random
import matplotlib.pyplot as plt #画像表示のライブラリ
import seaborn as sns
import tensorflow as tf
from tensorflow.keras.models import Sequential #モデル定義用
from tensorflow.keras.optimizers import Adam, Nadam,SGD #最適化アルゴリズム
from tensorflow.keras.layers import Dense, Dropout, BatchNormalization #Kerasのレイヤーのインポート
from tensorflow.keras import backend as K #RMSEを定義するのに使用
from sklearn.model_selection import GroupKFold #交差検証用

GPU環境チェック

GPU環境を設定している場合はdeviceがGPUになっていることを確認.
※ GPU環境は設定が難しいので,うまくいってない場合があるため.
GPUがない場合は,CPUになる.GPUがなくてもプログラムは問題なく動く.

.py
tf.config.list_physical_devices('GPU')

関数設定

RMSEとランダムシードの関数を設定

.py
def root_mean_squared_error(y_true, y_pred):
    return K.sqrt(K.mean(K.square(y_pred - y_true))) 

def set_seed(seed=42):
    tf.random.set_seed(seed)
    np.random.seed(seed)
    random.seed(seed)
    os.environ["PYTHONHASHSEED"] = str(seed)

データ読み込み

.py
df = pd.DataFrame()
for path in glob.glob(str("./data/train/*.csv")):
    _df = pd.read_csv(path)
    df = pd.concat([df, _df])
df['name'] = df["Chamber_Temp_degC"].astype(str) + '_' + df["Drive Cycle"]
df = df.reset_index(drop=True)

test_df = pd.read_csv("./data/test.csv")
tmp = 0

test_df['name'] = test_df["Chamber_Temp_degC"].astype(str) + '_' + test_df["Drive Cycle"]
print(test_df["name"].unique())

# 欠損値を0埋め
df = df.fillna(0)
test_df = test_df.fillna(0)
df

相関があるデータの作成

非常に大事な作業.相関係数が1と表示されていても完全な相関ではない.
より,相関のあるデータを作ることが必要になる.

まずは,オリジナル訓練データの相関を確認.

.py
#各値の相関関係を確認
sns.heatmap(
    df.corr(),
    linewidths=0.1,
    vmax=1.0, 
    square=True,
    cmap=plt.cm.RdBu,
    linecolor='white',
    annot=True
)

index.png

AhとWhはテストデータにないので,使えない.
相関があるカラムを作る必要がある.

※ 特徴量の移動平均をとるべきか?

.py
columns = ['Battery_Temp_degC', 'Chamber_Temp_degC', 'Current', 'Power', 'Time', 'Voltage', 'pred_SOC', 'pred_Wh'] #累積和を求めるカラムを決める

#CurrentとPowerはAhとWhに近くなるように計測時間の間隔毎に求める.
df['pred_SOC'] = df['Current']*df.groupby('name')['Time'].diff()/3600
df['pred_Wh'] = df['Power']*df.groupby('name')['Time'].diff()/3600

cumsum_df = pd.DataFrame({
    'name': df['name'].values,
    'SOC': df['SOC'].values
})


#累積和を求める
cumsum_df = pd.concat([
    cumsum_df,
    df.groupby('name')[columns].cumsum().add_prefix('cumsum_') #わかりやすいようにprefixを付ける
], axis=1)

#Currentからで,SOCの予測を求める.
cumsum_df['est_SOC'] = (2.9+cumsum_df['cumsum_pred_SOC'])/2.9*100
cumsum_df['est_SOC'] = cumsum_df['est_SOC'].fillna(100)


#累積和の相関関係を確認
sns.heatmap(
    cumsum_df.corr(),
    linewidths=0.1,
    vmax=1.0, 
    square=True,
    cmap=plt.cm.RdBu,
    linecolor='white',
    annot=True
)

index.png

単純な累積和である,cumsum_Current, cumsum_Power.
時間を計算に含めた,cumsum_pred_SOC,cumsum_pred_Wh.
SOC計算式で,計算したest_SOC.
これらは,SOCと非常に相関高い.

一応オリジナルデータも加えて相関を確認してみる.

.py
#オリジナルデータも結合
a_df = pd.concat([cumsum_df.drop('SOC', axis=1), df], axis=1)

#全体の相関を確認
sns.heatmap(
    a_df.corr(),
    linewidths=0.1,
    vmax=1.0, 
    square=True,
    cmap=plt.cm.RdBu,
    linecolor='white',
    annot=True
)

index.png

いくつか,ペアプロットして確認してみる.
特に,est_SOCはSOCとの相関が高いことが分かる.

.py
temp_df = a_df[['SOC', 'Ah', 'cumsum_Current', 'est_SOC', 'cumsum_pred_Wh', 'cumsum_Voltage']]
sns.pairplot(temp_df)

index.png

ドライブサイクルとthermal chamberの温度毎に計測時間でのSOCを確認する.

.py
#ドライブサイクルとthermal chamberの温度毎に時間計測でのSOCの減退率が違う
fig = plt.figure(figsize=(15,40))

length = len(df["name"].unique())

for i, name in enumerate(df["name"].unique()):
    data = df[df['name'] == name]
    plt.subplot(length, 1, i+1)
    graph_title = data["Drive Cycle"].iloc[0] +" : "+str(data["Chamber_Temp_degC"].iloc[0])
    g = sns.lineplot(data=data[:],x="Time",y="SOC")
    g.set_title(f'{graph_title}', fontsize=10)
    plt.ylim(0,100)

    
plt.tight_layout()
plt.show()

index.png

ドライブサイクルとthermal chamberの温度の情報は,特徴量として,使用するのがよさそう.
しかしドライブサイクルは,訓練データとテストデータで違うため,使用できない.
thermal chamberの温度だけでもカテゴリデータとして使用すべきか.

特徴量作成

作成したデータのうち実際に使用する特徴量を作成する.

.py

# 特徴量作成
#use_colum = ['Current', 'Power', 'Time', 'Voltage']
#use_colum = ['Current', 'Power']
#use_colum = ['Current']
cumsum_use_colum = ['pred_Wh', 'pred_SOC', 'Voltage'] #累積和を求めるカラムを設定
#use_colum = ['cumsum_Power', 'est_SOC']
use_colum = ['est_SOC', 'cumsum_pred_Wh', 'cumsum_Voltage'] #使用するカラムを設定
#use_colum = ['est_SOC', 'cumsum_pred_Wh'] #使用するカラムを設定


#訓練データの成型
X = df.groupby('name')[cumsum_use_colum].cumsum().add_prefix('cumsum_')
X['est_SOC'] = (2.9+X['cumsum_pred_SOC'])/2.9*100 #予測SOCを計算
X.loc[X['est_SOC'] < 0, 'est_SOC'] = 0
X.loc[X['est_SOC'] > 100, 'est_SOC'] = 100

X = X[use_colum]
X = pd.concat([X[use_colum],df[['Chamber_Temp_degC']]], axis=1) #Chamber_Temp_degCのカラムを追加

#テストデータの成型
test_df['pred_SOC'] = test_df['Current']*test_df.groupby('name')['Time'].diff()/3600 #予測Ahを計算
test_df['pred_Wh'] = test_df['Power']*test_df.groupby('name')['Time'].diff()/3600 #予測Whを計算
test = test_df.groupby('name')[cumsum_use_colum].cumsum().add_prefix('cumsum_')
test['est_SOC'] = (2.9+test['cumsum_pred_SOC'])/2.9*100 #予測SOCを計算
test.loc[test['est_SOC'] < 0, 'est_SOC'] = 0
test.loc[test['est_SOC'] > 100, 'est_SOC'] = 100
test = test[use_colum]
test = pd.concat([test[use_colum],test_df[['Chamber_Temp_degC']]], axis=1)

X['est_SOC'] = X['est_SOC'].fillna(100)
test['est_SOC'] = test['est_SOC'].fillna(100)
X = X.fillna(0)
test = test.fillna(0)

print(X)

#正規化
y = df['SOC']
all_df = pd.concat([X, test])
X = (X - all_df.mean()) / all_df.std()
test = (test - all_df.mean()) / all_df.std()

学習モデルの作成

パラメーターチューニングの箇所が多いので,注意しながら,学習モデルを作る.
学習方法は他にも試す.

実行したら非常に時間がかかるので,待つ.1日ぐらいかかる場合もある.

.py
models = []
train_check = np.zeros(len(X), dtype=np.float64)

set_seed(42) #出力を固定化するためにランダムシードを設定

es_cb = tf.keras.callbacks.EarlyStopping(monitor='val_loss', patience=20, verbose=1, mode='auto')

valid_scores = []

#set_seed(42) #出力を固定化するためにランダムシードを設定
print(tf.random.uniform([1]))
model = Sequential()
model.add(Dense(128, input_dim=X.shape[1]))
model.add(Dense(4096)) #ノード数を多くすることで,1階層で学習できるようにしてみる
model.add(Dense(1)) #出力層

optimizer = Adam(decay=0.001)
model.compile(loss=root_mean_squared_error, optimizer=optimizer, metrics=[root_mean_squared_error])

m = model

#実際の交差検証とは違うが,分割したデータごとに学習回数を変えて学習させる    
kf = GroupKFold(n_splits=5) # cross validationはGroupKFoldで行う
for fold, (tr_idx, va_idx) in enumerate(kf.split(X, y, df['name'])):
    x_train, y_train = X.iloc[tr_idx], y[tr_idx]
    x_valid, y_valid = X.iloc[va_idx], y[va_idx]
    m.fit(x_train, y_train, batch_size=512,  epochs=300, validation_data=(x_valid, y_valid), callbacks=[es_cb])
    
train_check += m.predict(X)[:, 0]    
train_check = np.where(train_check > 100, 100, train_check)
train_check = np.where(train_check < 0, 0, train_check)


score = root_mean_squared_error(y, train_check)
print(score)

結果出力

結果を計算しファイルに出力して,そのファイルを提出.

.py
SOC_data = np.zeros(len(test), dtype=np.float64)
#predictの結果が先ほどと同様に違うので,下記を変更.
SOC_data += np.array([model.predict(test)[:, 0] for model in models]).mean(axis=0) #深層学習の場合.
#SOC_data += np.array([model.predict(test) for model in models]).mean(axis=0) #深層学習以外の場合.
#CSVファイルに書き込み
csv_data = pd.DataFrame(data=SOC_data, columns=["SOC"])
csv_data.loc[csv_data['SOC'] < 0, 'SOC'] = 0
csv_data.loc[csv_data['SOC'] > 100, 'SOC'] = 100
csv_data.reset_index(inplace=True)
csv_data = csv_data.rename(columns={'index': 'ID'})
csv_data.to_csv("./data/submission.csv", index=False)
4
4
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
4
4