Regresi Dasar: Prediksi Efisiensi Bahan Bakar

Lihat di TensorFlow.org Jalankan di Google Colab Lihat sumber kode di GitHub Unduh notebook

Dalam permasalahan regresi, tujuan kita adalah untuk memprediksi keluaran berupa bilangan kontinu, seperti misalnya harga atau probabilitas. Berbeda dengan klasifikasi, dimana tujuannya adalah untuk memilih satu kelas dari beberapa kelas (contohnya, ketika terdapat gambar apel atau jeruk, menentukan buah apa yang terdapat di dalam gambar tersebut).

Notebook ini menggunakan dataset klasik Auto MPG dan membuat model yang bertujuan untuk memprediksi efisiensi bahan bakar dari mobil yang dibuat pada akhir 1970-an dan awal 1980-an. Untuk melakukan hal ini, Model dengan deskripsi berbagai mobil pada periode itu telah disediakan. Deskripsi tersebut diantaranya adalah: jumlah silinder, displacement, horsepower, dan berat.

Kasus ini menggunakan API dari tf.keras, lihat panduan ini untuk lebih lengkap.

# Use seaborn for pairplot
pip install -q seaborn

# Use some functions from tensorflow_docs
pip install -q git+https://github.com/tensorflow/docs
WARNING: You are using pip version 20.2.2; however, version 20.2.3 is available.
You should consider upgrading via the '/tmpfs/src/tf_docs_env/bin/python -m pip install --upgrade pip' command.
WARNING: You are using pip version 20.2.2; however, version 20.2.3 is available.
You should consider upgrading via the '/tmpfs/src/tf_docs_env/bin/python -m pip install --upgrade pip' command.

from __future__ import absolute_import, division, print_function, unicode_literals

import pathlib

import matplotlib.pyplot as plt
import numpy as np
import pandas as pd
import seaborn as sns
try:
  # %tensorflow_version only exists in Colab.
  %tensorflow_version 2.x
except Exception:
  pass
import tensorflow as tf

from tensorflow import keras
from tensorflow.keras import layers

print(tf.__version__)
2.3.0

import tensorflow_docs as tfdocs
import tensorflow_docs.plots
import tensorflow_docs.modeling

Dataset Auto MPG

Dataset dapat diperoleh dari UCI Machine Learning Repository.

Memperoleh Data

Pertama-tama, unduh dataset terlebih dahulu.

dataset_path = keras.utils.get_file("auto-mpg.data", "http://archive.ics.uci.edu/ml/machine-learning-databases/auto-mpg/auto-mpg.data")
dataset_path
Downloading data from http://archive.ics.uci.edu/ml/machine-learning-databases/auto-mpg/auto-mpg.data
32768/30286 [================================] - 0s 4us/step

'/home/kbuilder/.keras/datasets/auto-mpg.data'

Import dataset menggunakan pandas

column_names = ['MPG','Cylinders','Displacement','Horsepower','Weight',
                'Acceleration', 'Model Year', 'Origin']
raw_dataset = pd.read_csv(dataset_path, names=column_names,
                      na_values = "?", comment='\t',
                      sep=" ", skipinitialspace=True)

dataset = raw_dataset.copy()
dataset.tail()

Membersihkan data

Dataset masih mengandung beberapa missing value.

dataset.isna().sum()
MPG             0
Cylinders       0
Displacement    0
Horsepower      6
Weight          0
Acceleration    0
Model Year      0
Origin          0
dtype: int64

Untuk mempermudah tutorial ini, baris-baris yang mengandung nilai kosong tersebut akan di drop.

dataset = dataset.dropna()

Kolom "Origin" sebenarnya berisikan data kategorikal, bukan numerik. Oleh karena itu, ubah kolom tersebut dengan metode enkode one-hot:

dataset['Origin'] = dataset['Origin'].map(lambda x: {1: 'USA', 2: 'Europe', 3: 'Japan'}.get(x))
dataset = pd.get_dummies(dataset, prefix='', prefix_sep='')
dataset.tail()

Membagi data menjadi data train dan data test

Sekarang bagi dataset menjadi set untuk training dan set untuk tes.

Kita akan menggunakan set untuk tes pada evaluasi akhir model kita.

train_dataset = dataset.sample(frac=0.8,random_state=0)
test_dataset = dataset.drop(train_dataset.index)

Inspeksi data

Melihat joint distribution dari beberapa kolom yang terdapat di data training.

sns.pairplot(train_dataset[["MPG", "Cylinders", "Displacement", "Weight"]], diag_kind="kde")
<seaborn.axisgrid.PairGrid at 0x7f5fa2150da0>

png

Juga melihat keseluruhan statistik dari data:

train_stats = train_dataset.describe()
train_stats.pop("MPG")
train_stats = train_stats.transpose()
train_stats

Memisahkan fitur dari label

Pisahkan nilai target, atau "label", dari fitur. Label ini adalah nilai yang akan diprediksi oleh model.

train_labels = train_dataset.pop('MPG')
test_labels = test_dataset.pop('MPG')

Normalisasi data

Lihat kembali train_stats di atas dan perhatikan perbedaan nilai range untuk setiap fitur yang ada.

Melakukan normalisasi terhadap fitur yang memiliki skala dan range yang berbeda merupakan praktik yang baik dalam membangun model. Meskipun model mungkin dapat dibuat tanpa melakukan normalisasi fitur terlebih dahulu, hal ini akan membuat proses training data menjadi lebih sulit, dan akan membuat model yang dihasilkan terlalu dipengaruhi oleh unit input.

Catatan: Meskipun kita sengaja membangun fungsi statistik ini dengan menggunakan data training saja, fungsi ini juga akan digunakan untuk menormalisasi data tes. Kita perlu melakukan hal tersebut agar data tes memiliki bentuk yang sama dengan data yang digunakan untuk melatih model.

def norm(x):
  return (x - train_stats['mean']) / train_stats['std']
normed_train_data = norm(train_dataset)
normed_test_data = norm(test_dataset)

Data yang sudah dinormalisasi ini akan digunakan sebagai data untuk membuat model.

Perhatian: nilai statistik yang digunakan untuk normalisasi input (rata-rata dan standard deviasi) harus digunakan juga pada data yang akan dimasukan ke dalam model, termasuk juga proses enkode one-hot yang telah kita lakukan sebelumnya. Hal ini berlaku juga untuk data tes dan data lainnya ketika hendak menggunakan model pada fase produksi.

Model

Membangun Model

Mari kita bangun model kita. Disini, kita akan menggunakan model Sequential dengan dua hidden layer yang terhubung secara penuh satu sama lain, dan satu layer output yang akan memberikan hasil berupa bilangan kontinu. Langkah-langkah pembuatan model akan dimasukkan dalam sebuah fungsi, build_model. Hal ini dilakukan karena nantinya kita akan membuat model yang lainnya.

def build_model():
  model = keras.Sequential([
    layers.Dense(64, activation='relu', input_shape=[len(train_dataset.keys())]),
    layers.Dense(64, activation='relu'),
    layers.Dense(1)
  ])

  optimizer = tf.keras.optimizers.RMSprop(0.001)

  model.compile(loss='mse',
                optimizer=optimizer,
                metrics=['mae', 'mse'])
  return model
model = build_model()

Inspeksi Model

Gunakan method .summary untuk memunculkan deskripsi sederhana dari model.

model.summary()
Model: "sequential"
_________________________________________________________________
Layer (type)                 Output Shape              Param #   
=================================================================
dense (Dense)                (None, 64)                640       
_________________________________________________________________
dense_1 (Dense)              (None, 64)                4160      
_________________________________________________________________
dense_2 (Dense)              (None, 1)                 65        
=================================================================
Total params: 4,865
Trainable params: 4,865
Non-trainable params: 0
_________________________________________________________________

Sekarang uji coba model yang telah dibuat. Ambil 10 nilai dari data training dan panggil model.predict pada model kita.

example_batch = normed_train_data[:10]
example_result = model.predict(example_batch)
example_result
array([[0.17898521],
       [0.13573356],
       [0.12031718],
       [0.33391687],
       [0.61158377],
       [0.13692687],
       [0.6487076 ],
       [0.48808375],
       [0.16053359],
       [0.4091459 ]], dtype=float32)

Model dapat berfungsi dan menghasilkan nilai dengan bentuk dan tipe yang diharapkan.

Proses train dari model

Latih model dengan 1000 epoch, dan catat nilai akurasi dari training dan validasi dalam objek history

EPOCHS = 1000

history = model.fit(
  normed_train_data, train_labels,
  epochs=EPOCHS, validation_split = 0.2, verbose=0,
  callbacks=[tfdocs.modeling.EpochDots()])

Epoch: 0, loss:549.6766,  mae:22.1977,  mse:549.6766,  val_loss:534.2628,  val_mae:21.8481,  val_mse:534.2628,  
....................................................................................................
Epoch: 100, loss:6.2244,  mae:1.7151,  mse:6.2244,  val_loss:8.5252,  val_mae:2.2432,  val_mse:8.5252,  
....................................................................................................
Epoch: 200, loss:5.3320,  mae:1.5682,  mse:5.3320,  val_loss:8.3765,  val_mae:2.1241,  val_mse:8.3765,  
....................................................................................................
Epoch: 300, loss:4.8593,  mae:1.4862,  mse:4.8593,  val_loss:8.0107,  val_mae:2.1313,  val_mse:8.0107,  
....................................................................................................
Epoch: 400, loss:4.5176,  mae:1.4117,  mse:4.5176,  val_loss:8.2881,  val_mae:2.1934,  val_mse:8.2881,  
....................................................................................................
Epoch: 500, loss:4.3473,  mae:1.3959,  mse:4.3473,  val_loss:8.5248,  val_mae:2.1947,  val_mse:8.5248,  
....................................................................................................
Epoch: 600, loss:3.6590,  mae:1.2716,  mse:3.6590,  val_loss:8.5337,  val_mae:2.2011,  val_mse:8.5337,  
....................................................................................................
Epoch: 700, loss:3.5324,  mae:1.2387,  mse:3.5324,  val_loss:8.2281,  val_mae:2.1304,  val_mse:8.2281,  
....................................................................................................
Epoch: 800, loss:3.0778,  mae:1.1323,  mse:3.0778,  val_loss:8.0727,  val_mae:2.1303,  val_mse:8.0727,  
....................................................................................................
Epoch: 900, loss:2.9396,  mae:1.0885,  mse:2.9396,  val_loss:7.9525,  val_mae:2.1145,  val_mse:7.9525,  
....................................................................................................

Visualisasi progres pelatihan model menggunakan nilai-nilai yang disimpan dalam objek history.

hist = pd.DataFrame(history.history)
hist['epoch'] = history.epoch
hist.tail()
plotter = tfdocs.plots.HistoryPlotter(smoothing_std=2)
plotter.plot({'Basic': history}, metric = "mae")
plt.ylim([0, 10])
plt.ylabel('MAE [MPG]')
Text(0, 0.5, 'MAE [MPG]')

png

plotter.plot({'Basic': history}, metric = "mse")
plt.ylim([0, 20])
plt.ylabel('MSE [MPG^2]')
Text(0, 0.5, 'MSE [MPG^2]')

png

Grafik ini menunjukan sedikit peningkatan, atau penurunan nilai error dari validasi setelah sekitar 100 epoch. Mari kita perbarui pemanggilan model.fit agar proses pelatihan model dapat berhenti secara otomatis ketika skor validasi sudah tidak meningkat lagi. Kita akan menggunakan EarlyStopping callback yang akan mengecek kondisi dari proses pelatihan model setiap epoch. Apabila beberapa kali epoch dilalui tanpa menunjukkan peningkatan, maka proses pelatihan akan berhenti secara otomatis.

Anda dapat mempelajari lebih lanjut tentang callback disini.

model = build_model()

# The patience parameter is the amount of epochs to check for improvement
early_stop = keras.callbacks.EarlyStopping(monitor='val_loss', patience=10)

early_history = model.fit(normed_train_data, train_labels, 
                    epochs=EPOCHS, validation_split = 0.2, verbose=0, 
                    callbacks=[early_stop, tfdocs.modeling.EpochDots()])

Epoch: 0, loss:572.4059,  mae:22.7279,  mse:572.4059,  val_loss:557.4664,  val_mae:22.4555,  val_mse:557.4664,  
...................................................................
plotter.plot({'Early Stopping': early_history}, metric = "mae")
plt.ylim([0, 10])
plt.ylabel('MAE [MPG]')
Text(0, 0.5, 'MAE [MPG]')

png

Grafik menunjukan bahwa pada set validasi, rata-rata error berkisar antara +/- 2 MPG. Apakah ini bagus? Kita akan menyerahkan jawaban tersebut kepada Anda.

Mari kita lihat sebarapa baik performa model dengan menggunakan data tes, yaitu data yang tidak kita gunakan dalam proses pelatihan model. Hal ini menujukkan kepada kita seberapa baik performa dari model ketika kita menggunakannya di dunia nyata.

loss, mae, mse = model.evaluate(normed_test_data, test_labels, verbose=2)

print("Testing set Mean Abs Error: {:5.2f} MPG".format(mae))
3/3 - 0s - loss: 5.5686 - mae: 1.8415 - mse: 5.5686
Testing set Mean Abs Error:  1.84 MPG

Membuat prediksi

Akhirnya, kita akan melakukan prediksi nilai MPG menggunakan test data:

test_predictions = model.predict(normed_test_data).flatten()

a = plt.axes(aspect='equal')
plt.scatter(test_labels, test_predictions)
plt.xlabel('True Values [MPG]')
plt.ylabel('Predictions [MPG]')
lims = [0, 50]
plt.xlim(lims)
plt.ylim(lims)
_ = plt.plot(lims, lims)

png

Sepertinya model kita dapat memprediksi dengan cukup baik. Mari kita lihat distribusi error dari prediksi tersebut.

error = test_predictions - test_labels
plt.hist(error, bins = 25)
plt.xlabel("Prediction Error [MPG]")
_ = plt.ylabel("Count")

png

Nilainya tidak terdistribusi secara normal, tetapi hal ini wajar karena jumlah sampel yang digunakan sedikit.

Kesimpulan

Notebook ini memperkenalkan beberapa teknik untuk menyelesaikan permasalahan regresi.

  • Mean Squared Error (MSE) adalah loss function yang biasa digunakan dalam permasalahan regresi (loss function yang berbeda digunakan pada kasus klasifikasi).
  • Metriks evaluasi yang digunakan untuk regresi juga berbeda dengan yang digunakan untuk klasifikasi. Metriks yang umum untuk regresi adalah Mean Absolute Error (MAE).
  • Ketika fitur data input yang bertipe numerik memiliki range yang berbeda, setiap fitur harus diubah nilainya terlebih dahulu sedemikian sehingga fitur-fitur tersebut memiliki nilai dengan range yang sama.
  • Apabila data training yang dimiliki tidak banyak, lebih baik menggunakan network yang sederhana dengan sedikit hidden layer untuk menghindari overfitting.
  • Early stopping merupakan teknik yang bermanfaat untuk menghindari overfitting.
# MIT License
#
# Copyright (c) 2017 François Chollet
#
# Permission is hereby granted, free of charge, to any person obtaining a
# copy of this software and associated documentation files (the "Software"),
# to deal in the Software without restriction, including without limitation
# the rights to use, copy, modify, merge, publish, distribute, sublicense,
# and/or sell copies of the Software, and to permit persons to whom the
# Software is furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
# THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
# DEALINGS IN THE SOFTWARE.