Gakushukun1’s diary

20代エンジニア, 統計的機械学習勉強中 twitter: @a96665004

ニューラルネットを用いた車の評価の推測

目的

階層型ニューラルネットをKerasで実装し, 実データに適用した学習の例を紹介する。


今回は, Kerasを用いて構築したニューラルネットのモデルを用いて, Car Evaluation Data Setから車の評価を推測してみる.

Car Evaluation Data Setには, それぞれの車が持つ6つの特徴と, その車に対する評価を示すラベルがセットとなったデータが1728個含まれている. このデータのうち半分を学習に用い, もう半分をテストデータとして検証に用いることとする.

各車が持つ6つの特徴は次の通り.

  • buying(価格) とても高い, 高い, 普通, 安いの四段階
  • maint(管理費) とても高い, 高い, 普通, 安いの四段階
  • doors(ドアの数) 2枚, 3枚, 4枚, 5枚以上の四段階
  • persons(乗員数) 2人, 4人, 5人以上の四段階
  • lug_boot(載せられる荷物の量) 少ない, 普通, 多いの三段階
  • safety(安全性) 高い, 普通, 低いの三段階

各車の評価を示すラベルは, unacceptable(良くない), acceptable(まあまあ), good(良い), very-good(とても良い)の四段階に分けられている.

さて, 今回はKerasを用いて次のようなネットワークを構築した.
f:id:gakushukun1:20190507232430p:plain
二つの中間層では, 活性化関数としてsigmoid関数を用いている. また, 過学習を抑えるために, 活性化の前にBatchNormalizaionを挟んだほか, L1正則化によるパラメータの罰則項を損失関数に加えている. 出力層では, softmax関数を用いて, 4個あるクラスそれぞれに所属する確率(合計で1になる)を出力するようにしている. モデルの学習には, Adamを用い, 交差エントロピーを損失関数とする.

6つの特徴を入力するにあたって, 各入力値が-1.0から1.0の範囲に含まれるように調整する. 例えば, 車の価格は[とても高い, 高い, 普通, 安い]の四段階で示されているが, [-1.0, -0.33, 0.33, 1.0]の四段階の値に対応づけることとする.

これらの条件を用いて, L1正則化のハイパーパラメータ \lambdaを変化させながら, 864個の学習データを2000epochミニバッチで学習させた. 2000epochループさせた後の, 残りのテストデータ864個についてのテスト誤差と正答率は次のように変化した.
f:id:gakushukun1:20190508155251p:plain

上のグラフが \lambdaごとのテスト誤差の変化, 下のグラフが \lambdaごとの正答率の変化である. この結果から,  \lambda = 1.0 \times 10^{-5}の時最も汎化性能が高くなったと判断できる.

 \lambda = 1.0 \times 10^{-5}とした時のepochごとの訓練誤差, テスト誤差, 訓練データとテストデータに対する正答率を示す.
f:id:gakushukun1:20190508161423p:plain

 \lambdaを上記のように選んだところ, 学習誤差もテスト誤差もepochごとに減っていき, 過学習するような挙動は見られなかった.

今回の実験で用いたコードを以下に示す.

import tensorflow as tf
import numpy as np
import csv
from keras.models import Sequential
from keras.layers import Dense, Input, BatchNormalization, Activation
from keras.activations import sigmoid, softmax
from keras.regularizers import l1
from keras.optimizers import Adam
from keras.utils import np_utils, plot_model
import matplotlib.pyplot as plt


information = [
    ['vhigh', 'high', 'med', 'low'],
    ['vhigh', 'high', 'med', 'low'],
    ['2', '3', '4', '5more'],
    ['2', '4', 'more'],
    ['small', 'med', 'big'],
    ['low', 'med', 'high'],
]


def convert_to_value(str_vec):
    size = len(str_vec)
    conv_dict = {}

    for i in range(size):
        conv_dict[str_vec[i]] = -1.0 + (2.0 / (size - 1)) * i

    return conv_dict


def convert_to_data(row):
    data = [0] * (len(row) - 1)

    for i in range(len(row) - 1):
        data[i] = convert_to_value(information[i])[row[i]]

    label = {'unacc': 0 , 'acc': 1, 'good': 2, 'vgood': 3}[row[- 1]]
    label = np_utils.to_categorical(label, 4)

    return np.array(data), label


def read_file():
    f = open('car.data', 'r')
    reader = csv.reader(f)
    inputs = []
    labels = []

    for row in reader:
        data, label = convert_to_data(row)
        inputs.append(data)
        labels.append(label)

    f.close()

    train_size = int(len(inputs))

    rand_idx = np.random.permutation(range(len(inputs)))

    inputs = np.array(inputs)[rand_idx]
    labels = np.array(labels)[rand_idx]

    return inputs[0:train_size], labels[0:train_size]


def plot_loss_accuracy(fit, l1_lambda):
    fig, (loss_f, acc_f) = plt.subplots(2, 1, figsize=(15, 20))
    loss_f.plot(fit.history['loss'], label="train")
    loss_f.plot(fit.history['val_loss'], label="test")
    loss_f.set_xlabel("epoch")
    loss_f.set_ylabel("loss")
    loss_f.legend()
    acc_f.plot(fit.history['acc'], label="train")
    acc_f.plot(fit.history['val_acc'], label="test")
    acc_f.set_xlabel("epoch")
    acc_f.set_ylabel("accuracy")
    acc_f.legend()

    filename = 'car_fit_plot' + str(l1_lambda) + '.png'

    fig.savefig(filename)
    plt.close()


if __name__ == '__main__':

    train_inputs, train_labels = read_file()

    l1_lambda = 0.000001

    model = Sequential()
    model.add(Dense(10, activity_regularizer=l1(l1_lambda), input_shape=(6,)))
    model.add(BatchNormalization())
    model.add(Activation(sigmoid))
    model.add(Dense(5, activity_regularizer=l1(l1_lambda)))
    model.add(BatchNormalization())
    model.add(Activation(sigmoid))
    model.add(Dense(4, activity_regularizer=l1(l1_lambda)))
    model.add(BatchNormalization())
    model.add(Activation(softmax))

    model.compile(optimizer=Adam(), loss='categorical_crossentropy', metrics=['accuracy'])

    model.summary()
    plot_model(model, to_file='model.png', show_shapes=True)

    fit = model.fit(train_inputs, train_labels, epochs=2000, validation_split=0.5)

    plot_loss_accuracy(fit, l1_lambda)
<||

**まとめ