fbpx
Blog Banner End To End Machine Learning With Python

Proyek Machine Learning dari Hulu ke Hilir (End-to-End) – Part 6: Mempersiapkan Data untuk Algoritma Machine Learning II: Custom Transformers, Feature Scaling, dan Transformation Pipelines

“In Data Science, 80% of time spent prepare data, 20% of time spent complain about need for prepare data.”

~ Big Data Borat

“Data Science is 99% preparation, 1% misinterpretation.”

~ Big Data Borat

1. Custom Transformers

hkaLabs: hakim-azizul.comscikit-learn menyediakan banyak transformer data yang berguna, namun kita seringkali tetap perlu untuk membuat transformer sendiri untuk kebutuhan pembersihan data custom, atau mengkombinasikan atribut-atribut secara spesifik.

Kita juga menginginkan agar transformer yang kita buat tersebut dapat bekerja secara mulus bersama dengan fungsionalitas scikit-learn (misalnya pipelines), dan karena scikit-learn berdasarkan pada prinsip duck typing (bukan inheritance), maka yang kita butuhkan adalah membuat class dan menerapkan tiga methods, yaitu: fit() (returning self),  transform(), dan fit_transform().

Baca juga tentang “duck typing vs inheritance” di sini.

Kita akan memperoleh method fit_transform() secara cuma-cuma dengan menambahkan TransformerMixin sebagai base class. Jika kita menambahkan BaseEstimator sebagai base class (dan menghindari penggunaan *args dan **kwargs pada constructor); kita akan mendapatkan tambahan dua methods, yaitu get_params() dan set_params() yang akan berguna untuk hyperparameter tuning secara otomatis.

Baca juga tentang *args dan **kwargs di sini.

Sebelum kita lanjutkan pembahasan, kita rekap dahulu kolom atau features pada dataset kita (agar kita tahu indeks features yang akan kita transformasikan):

housing.columns

Berikut ini adalah contoh class transformer yang menambahkan beberapa kombinasi atribut seperti yang kita bahas sebelumnya:

from sklearn.base import BaseEstimator, TransformerMixin

rooms_ix, bedrooms_ix, population_ix, household_ix = 3, 4, 5, 6

class CombinedAttributesAdder(BaseEstimator, TransformerMixin):
    def __init__(self, add_bedrooms_per_room = True): #No *args or **kwargs
        self.add_bedrooms_per_room = add_bedrooms_per_room
    def fit(self, X, y=None):
        return self #Nothing else to do
    def transform(self, X, y=None):
        rooms_per_household = X[:, rooms_ix] / X[:, household_ix]
        population_per_household = X[:, population_ix] / X[:, household_ix]
        if self.add_bedrooms_per_room:
            bedrooms_per_room = X[:, bedrooms_ix] / X[:, rooms_ix]
            return np.c_[X, rooms_per_household, population_per_household,
                        bedrooms_per_room]
        else:
            return np.c_[X, rooms_per_household, population_per_household]

#Tambahan hyperparameter        
attr_adder = CombinedAttributesAdder(add_bedrooms_per_room=False)
housing_extra_attribs = attr_adder.transform(housing.values)

Agar lebih presisi, kita dapat menghindari hard-coding pada indeks seperti di atas, dengan cara sebagai berikut:

from sklearn.base import BaseEstimator, TransformerMixin

#Get the right column indices: safer than hard-coding indices 3, 4, 5, 6
rooms_ix, bedrooms_ix, population_ix, household_ix = [
    list(housing.columns).index(col)
    for col in ("total_rooms", "total_bedrooms", "population", "households")]

class CombinedAttributesAdder(BaseEstimator, TransformerMixin):
    def __init__(self, add_bedrooms_per_room = True): # no *args or **kwargs
        self.add_bedrooms_per_room = add_bedrooms_per_room
    def fit(self, X, y=None):
        return self  # nothing else to do
    def transform(self, X, y=None):
        rooms_per_household = X[:, rooms_ix] / X[:, household_ix]
        population_per_household = X[:, population_ix] / X[:, household_ix]
        if self.add_bedrooms_per_room:
            bedrooms_per_room = X[:, bedrooms_ix] / X[:, rooms_ix]
            return np.c_[X, rooms_per_household, population_per_household,
                         bedrooms_per_room]
        else:
            return np.c_[X, rooms_per_household, population_per_household]

#Tambahkan hyperparameter
attr_adder = CombinedAttributesAdder(add_bedrooms_per_room=False)
housing_extra_attribs = attr_adder.transform(housing.values)

Alternatif dari kode di atas, kita dapat menggunakan class FunctionTransformer pada scikit-learn:

from sklearn.preprocessing import FunctionTransformer

def add_extra_features(X, add_bedrooms_per_room=True):
    rooms_per_household = X[:, rooms_ix] / X[:, household_ix]
    population_per_household = X[:, population_ix] / X[:, household_ix]
    if add_bedrooms_per_room:
        bedrooms_per_room = X[:, bedrooms_ix] / X[:, rooms_ix]
        return np.c_[X, rooms_per_household, population_per_household,
                     bedrooms_per_room]
    else:
        return np.c_[X, rooms_per_household, population_per_household]

attr_adder = FunctionTransformer(add_extra_features, validate=False,
                                 kw_args={"add_bedrooms_per_room": False})
housing_extra_attribs = attr_adder.fit_transform(housing.values)

Kita perlu set validate=False karena data kita memiliki nilai non-float (pada scikit-learn 0.22, validate akan secara default di-set menjadi False).

Pada contoh-contoh di atas, transformer yang kita buat memiliki satu hyperparameter, yaitu add_bedrooms_per_room, yang di-set menjadi “True” by default (kita perlu untuk selalu menyediakan nilai default yang masuk akal/sensible, karena akan sangat membantu). Dengan hyperparameter ini memungkinkan kita untuk dengan mudah mencari tahu apakah dengan menambahkan atribut-atribut baru ini akan membantu algoritma Machine Learning kita atau tidak.

Secara umum, penambahan hyperparameter ini berguna untuk menjembatani langkah preparasi data yang kita belum yakin dengan keberhasilannya secara 100%. Semakin kita mampu melakukan otomatisasi pada tahap preparasi data, semakin memungkinkan untuk kita mencoba lebih banyak kombinasi preparasi data, dan semakin besar kemungkinan kita untuk menemukan kombinasi yang bagus, dan di sisi lain menghemat banyak waktu dalam melakukannya.

Berikut ini adalah hasil apabila parameter-parameter tambahan tersebut digabungkan dengan dataset kita:

housing_extra_attribs = pd.DataFrame(
    housing_extra_attribs,
    columns=list(housing.columns)+["rooms_per_household", "population_per_household"],
    index=housing.index)
housing_extra_attribs.head()

Hasil di console:

hyperparameter
Berhasil Menambahkan Dua Feature Baru. Sumber Gambar: Dokumentasi Pribadi.

 

2. Feature Scaling

Salah satu tahapan yang terpenting dari transformasi data yang wajib kita lakukan adalah melakukan feature scaling pada atribut-atribut numerik. Hal ini dikarenakan, mayoritas algoritma Machine Learning tidak akan berperforma baik apabila input yang berupa atribut numerik memiliki skala yang terlalu jauh berbeda.

Skala yang jauh berbeda ini kita temui pada kasus dataset housing kita: Misalnya, total jumlah kamar atau ruangan memiliki rentang dari 6 hingga 39,320, sedangkan median income hanya memiliki rentang dari 0 hingga 15.

Catatan: Scaling nilai pada variabel target tidak diperlukan.

Terdapat dua cara yang lazim dipakai untuk membuat semua atribut menjadi berskala sama, yaitu: Min-max scaling dan standardization.

Min-max scaling atau dikenal juga sebagai normalisasi (normalization), prinsipnya cukup sederhana: Yaitu sesederhana menggeser dan scaling data sehingga data menjadi berada pada rentang 0 hingga 1 (nilai minimum menjadi 0, dan nilai maksimum menjadi 1). Hasil tersebut diperoleh dengan mengurangi suatu nilai dengan nilai minimum, lalu membaginya dengan nilai maksimum dikurangi dengan nilai minimum.

Data Scientist, Data Science, Machine Learning, Statistics, Data Science Indonesia, Data Analytics, Data Analysis, Data Analyst, Data, Astronomy, Astronomer, Science, Python, iPython, Jupyter Notebook, R, RStudio, Excel, Coding, Koding, Cara Mengolah Data, Mengolah Data, Olah Data, Programming, Pemrograman, Sains, Teknologi, Ilmu Data, Teknologi Informasi, Tech in Asia, Teknologi, Technology, Sains, Bisnis, Business, Business Analyst, Business Analysis, Social Media Mining, Movie Review, Muhammad Azizul Hakim, Aziz

scikit-learn menyediakan sebuah transformer untuk kebutuhan ini, yang bernama MinMaxScaler. MinMaxScaler memiliki hyperparameter feature_range yang memungkinkan kita untuk mengubah range scalingnya, apabila kita tidak menginginkan rentang 0 hingga 1.

Baca juga: Berkenalan dengan scikit-learn (Part 4) – Scaling Data dengan MinMaxScaler

Namun standardisasi (standardization) agak berbeda: Standardisasi mengurangi suatu nilai dengan nilai rata-ratanya (sehingga, nilai yang terstandardisasi selalu memiliki nilai rata-rata nol), lalu dibagi dengan varians sehingga hasilnya akan memiliki distribusi dengan nilai varians sama dengan 1.

Data Scientist, Data Science, Machine Learning, Statistics, Data Science Indonesia, Data Analytics, Data Analysis, Data Analyst, Data, Astronomy, Astronomer, Science, Python, iPython, Jupyter Notebook, R, RStudio, Excel, Coding, Koding, Cara Mengolah Data, Mengolah Data, Olah Data, Programming, Pemrograman, Sains, Teknologi, Ilmu Data, Teknologi Informasi, Tech in Asia, Teknologi, Technology, Sains, Bisnis, Business, Business Analyst, Business Analysis, Social Media Mining, Movie Review, Muhammad Azizul Hakim, Aziz

Tidak seperti min-max scaling, standardisasi tidak membatasi nilai pada rentang tertentu secara spesifik, yang bisa menjadi masalah untuk beberapa algoritma (misalnya: Neural network yang meminta nilai input yang memiliki rentang nilai dari 0 hingga 1).

Standardisasi memiliki kelebihan lain, yaitu lebih “kebal” terhadap outliers atau data pencilan. Sebagai contoh, apabila pada suatu distrik memiliki median income sebesar 100 (akibat kesalahan penginputan data), dengan min-max scaling akan mengakibatkan rusaknya rentang data dari yang semula 0-15 menjadi 0-0.15, sedangkan dengan standardisasi, tidak akan banyak berpengaruh.

scikit-learn menyediakan transformer StandardScaler untuk operasi standardisasi.

Baca juga: Berkenalan dengan scikit-learn (Part 3) – Scaling Data Menjadi Standard Normal

Catatan: Scaling data hanya dilakukan pada dataset training, jangan pada keseluruhan dataset. Setelahnya, baru kita terapkan scaler kita yang telah terlatih pada data test (dan data baru di masa yang akan datang).

 

3. Transformation Pipelines

Sebagaimana yang sudah kita pelajari hingga saat ini, ternyatqa terdapat cukup banyak steps transformasi data yang harus kita lakukan dengan urutan yang tepat.

Untuk memenuhi kebutuhan tersebut, kita beruntung sekali karena para kontributor scikit-learn yang baik hati telah membuat class Pipeline untuk membantu kita dalam melakukan transformasi data dengan urutan yang tepat.

Berikut ini adalah pipeline untuk transformasi atribut-atribut numerik:

from sklearn.pipeline import Pipeline
from sklearn.preprocessing import StandardScaler

num_pipeline = Pipeline([
        ("imputer", Imputer(strategy="median")),
        ("attribs_adder", CombinedAttributesAdder()),
        ("std_scaler", StandardScaler()),
    ])

housing_num_tr = num_pipeline.fit_transform(housing_num)

Konstruktor Pipeline mengambil list pasangan nama/estimator yang mendefinisikan urutan langkah-langkah preprocessing data yang kita lakukan. Semua estimator di atas, kecuali baris terakhir merupakan transformer (baris terakhir berguna untuk menerapkan method fit_transform(). Untuk penamaan estimator, kita bisa menggunakan nama apapun yang kita suka, dengan catatan harus tetap representatif dan mudah diingat/dipahami.

Ketika kita memanggil method pipeline’s fit(), ia akan memangil fit_transform() secara sekuensial atau berurutan pada semua transformers, passing atau meneruskan output dari setiap tahap preprocessing ke tahap berikutnya, sampai ke estimator terakhir, yang mana hanya memanggil method fit().

Pipeline menunjukkan methods yang sama dengan estimator yang terakhir. Pada contoh di atas, estimator terakhir adalah StandardScaler; yang merupakan sebuah transformer, sehingga Pipeline memiliki method transform() yang menerapkan semua transformer pada data kita secara sekuensial atau berurutan (dengan method fit_transform() di atas, kita bisa menerapkan keduanya sekaligus, bukan dengan memanggil fit() lalu transform() satu-persatu).

Berikut ini adalah hasil dari Pipeline di atas:

housing_num_tr

Hasil di console:

pipeline numerical transform output
Output Pipeline Transformasi Numerik. Sumber Gambar: Dokumentasi Pribadi.

Warning: Tahapan-tahapan berikutnya, terdapat perubahan sistem yang saya gunakan, yaitu Python 3.6.3 dengan Anaconda3 5.0.1 saya update ke Python 3.7 dengan Anaconda 2019.10, scikit-learn versi 0.19 saya update ke scikit-learn versi 0.20, dan ini akan menyebabkan beberapa library yang berkaitan juga otomatis terupdate, serta terdapat beberapa perbedaan/perubahan functions, methods, atau class yang dipakai.

Kita telah berhasil membuat dan menjalankan Pipeline untuk mengolah variabel numerik. Tugas kita berikutnya adalah menerapkan LabelBinarizer pada variabel kategoris, dan menggabungkan kedua pekerjaan (Pipeline numerik dan Pipeline kategoris) tersebut menjadi satu Pipeline saja.

scikit-learn (versi 0.20) menyediakan ColumnTransformer untuk melaksanakan tugas tersebut (melakukan transformasi berbeda pada kolom yang berbeda, lalu menggabungkan keduanya kembali di akhir), atau FeatureUnion untuk scikit-learn versi < 0.20.

Berikut ini penerapan ColumnTransformer:

from sklearn.compose import ColumnTransformer

num_attribs = list(housing_num)
cat_attribs = ["ocean_proximity"]

full_pipeline = ColumnTransformer([
        ("num", num_pipeline, num_attribs),
        ("cat", OneHotEncoder(), cat_attribs),
    ])

housing_prepared = full_pipeline.fit_transform(housing)

Jalankan keseluruhan Pipelinenya:

housing_prepared

Hasil di console:

housing Prepared
Output dari Keseluruhan Pipeline. Sumber Gambar: Dokumentasi Pribadi.

Cek shape dari hasil Pipeline:

housing_prepared.shape

Output di console:

housing Prepared Shape
Shape dari Output. Sumber Gambar: Dokumentasi Pribadi.

 

4. Bonus

4.1. ColumnTransformer vs FeatureUnion

Untuk scikit-learn versi < 0.20, hasil yang sama dengan ColumnTransformer dapat diperoleh dengan menggunakan transformer DataFrameSelector (melakukan select pada kolom Pandas DataFrame), lalu menerapkan FeatureUnion:

from sklearn.base import BaseEstimator, TransformerMixin

# Create a class to select numerical or categorical columns 
class OldDataFrameSelector(BaseEstimator, TransformerMixin):
    def __init__(self, attribute_names):
        self.attribute_names = attribute_names
    def fit(self, X, y=None):
        return self
    def transform(self, X):
        return X[self.attribute_names].values
num_attribs = list(housing_num)
cat_attribs = ["ocean_proximity"]

old_num_pipeline = Pipeline([
        ('selector', OldDataFrameSelector(num_attribs)),
        ('imputer', SimpleImputer(strategy="median")),
        ('attribs_adder', FunctionTransformer(add_extra_features, validate=False)),
        ('std_scaler', StandardScaler()),
    ])

old_cat_pipeline = Pipeline([
        ('selector', OldDataFrameSelector(cat_attribs)),
        ('cat_encoder', OneHotEncoder(sparse=False)),
    ])
from sklearn.pipeline import FeatureUnion

old_full_pipeline = FeatureUnion(transformer_list=[
        ("num_pipeline", old_num_pipeline),
        ("cat_pipeline", old_cat_pipeline),
    ])
old_housing_prepared = old_full_pipeline.fit_transform(housing)
old_housing_prepared

Hasil di console:

output Feature Union
Output FeatureUnion. Sumber Gambar: Dokumentasi Pribadi.

Berikut ini adalah bukti bahwa kedua metode yang berbeda di atas (ColumnTransformer vs FeatureUnion), dapat memberikan hasil yang sama:

np.allclose(housing_prepared, old_housing_prepared)

Hasil di console:

output verification
True. Both of Them Gave the Same Answer. Sumber Gambar: Dokumentasi Pribadi.

 

4.2. Penampakan Penuh dari Hasil Analisis dengan Jupyter Notebook

 

References & Further Reading

Geron A. (2017), Hands-On Machine Learning with Scikit-Learn and Tensorflow, O’Reilly Media.

Hauck T. (2014): scikit-learn Cookbook, Packt Publishing.

 

Sumber Gambar

https://pixabay.com/en/money-home-coin-investment-2724235/ oleh nattanan23.

Tinggalkan Komentar

Alamat email Anda tidak akan dipublikasikan. Ruas yang wajib ditandai *

Social media & sharing icons powered by UltimatelySocial