Tampilkan postingan dengan label Machine Learning. Tampilkan semua postingan
Tampilkan postingan dengan label Machine Learning. Tampilkan semua postingan

Kamis, 11 Februari 2021

Prediksi Diabetes Menggunakan Machine Learning

Prediksi Diabetes Menggunakan Machine Learning

Dari sini saya belajar bagaimana cara mendapatkan dataset, mempersiapkan dataset, memilih algoritma, melatih model, dan memvalidasi model yang dibuat dengan studi kasus prediksi diabetes.

Prediksi Diabetes Menggunakan Machine Learning


Prediksi Diabetes Menggunakan Machine Learning

Apa saja yang dibutuhkan:

Untuk mendapatkan dataset, teman-teman bisa mencarinya di Google dengan kata kunci "dataset [topik]", contohnya seperti "dataset diabetes", nanti Google akan menampilkan berbagai macam hasil, dan dari hasil tersebut kita bisa mendapatkan Pima Indians Diabetes Database dari situs Kaggle.

Untuk dataset yang saya gunakan, saya sudah modifikasi terlebih dahulu agar bisa melakukan beberapa proses dalam persiapan data. Dataset yang sudah dimodifikasi bisa didapatkan di repositori Github saya.

Selanjutnya kita buka Jupyter Notebook atau bisa juga online lewat Kaggle.

Persiapan Data


Pada proses ini kita akan memeriksa apakah ada data yang duplikat, kosong, melakukan pembersihan data sehingga data yang digunakan menjadi rapi. Data yang rapi akan memudahkan dalam memanipulasi data.

Impor library yang digunakan, lalu load dataset-nya.

# Import library
import pandas as pd # data frame library
import matplotlib.pyplot as plt # untuk plotting
import numpy as np 

# Load data csv
df = pd.read_csv('E:\PYTHON\Predict_diabetes\diabetes.csv')

Untuk melihat ukuran dataset, bisa dengan:

# lihat ukuran data
df.shape

Hasil:

(768, 10)

Dari hasil tersebut menunjukkan bahwa terdapat 768 baris/observasi dan 10 kolom/variabel/fitur.

Untuk melihat n data awal, bisa dengan:

# liat 5  data awal 
df.head(5)

Hasil:

Prediksi Diabetes Menggunakan Machine Learning

Kita juga bisa melihat n data akhir, dengan:

# liat 5 data bawah
df.tail(5)

Hasil:

Prediksi Diabetes Menggunakan Machine Learning

Mari kita cek apakah terdapat data yang kosong.

# apakah ada data yg kosong?
df.isnull().values.any()

Hasil:

False

Dari hasil tersebut, bisa diketahui bahwa dataset tidak memiliki data yang kosong.

Selanjutnya kita cek korelasi antar kolom. Cek korelasi ini bertujuan untuk melihat apakah ada data yang sama atau tidak. 

# cek kolerasi, data repetisi atau sama
def plot_corr(df, size=11):
    corr = df.corr() # data frame corelation
    fig, ax = plt.subplots(figsize=(size,size))
    ax.matshow(corr)
    plt.xticks(range(len(corr.columns)), corr.columns)
    plt.yticks(range(len(corr.columns)), corr.columns)

plot_corr(df)

Hasil:

Prediksi Diabetes Menggunakan Machine Learning

Dari gambar di atas, bisa dilihat bahwa kotak yang berwarna kuning menunjukkan adanya data yang sama atau duplikat. 

Contoh: baris Pregnancies dan kolom Pregnancies akan berwarna kuning karena memiliki data yang sama, begitu juga dengan kolom dan baris yang memiliki nama yang sama, sehingga kotak warna kuning membentuk pola diagonal. 

Namun jika dilihat kembali, terdapat data yang sama pada kolom SkinThickness dan baris Skin, juga sebaliknya.

Mari kita lihat nilai korelasinya.

# lihat korelasi
df.corr()

Hasil:

Prediksi Diabetes Menggunakan Machine Learning

Apabila kita bandingkan nilai korelasi antara SkinThickness dan Skin, maka terlihat bahwa kedua kolom tersebut memiliki nilai yang sama sehingga kita bisa menghapus salah satu kolom tersebut.

Misalnya kita hapus kolom Skin.

# krn korelasi SkinThickness dan Skin sama, hapus salah satunya, hapus Skin
del df['Skin']

Cek apakah kolom Skin sudah terhapus.

# cek apakah sudah terhapus?
df.head(5)

Hasil:

Prediksi Diabetes Menggunakan Machine Learning

Kolom Skin sudah terhapus, sekarang kita cek kembali korelasinya.

# cek kembali korelasi
plot_corr(df)

Hasil:

Prediksi Diabetes Menggunakan Machine Learning

Dari gambar di atas, bisa dilihat bahwa tidak ada lagi data yang sama atau duplikat.

Sekarang, cek tipe data pada dataset tersebut, pastikan semuanya dalam bentuk numeric.

Jika kita lihat kembali dari data di atas, kolom Outcome masih berisikan True atau False. Mari kita ubah ke numeric, True diganti 1 dan False diganti 0.

# ganti outcome ke 1 atau 0
diabetes_map = {True: 1, False:0} # buat map
df['Outcome'] = df['Outcome'].map(diabetes_map) # ganti ke 1 atau 0

Sekarang cek apakah sudah diganti.

# cek apakah sudah terganti?
df.head(5)

Hasil:

Prediksi Diabetes Menggunakan Machine Learning

Nilai pada kolom Outcome sudah diganti menjadi 1 dan 0.

Kita cek apakah terdapat kesalahan ketika proses penggantian nilai tersebut. Misal, terdapat kata true atau false (huruf awal tidak kapital) sehingga nilai tidak menjadi 0 atau 1, melainkan menjadi NaN.

# cek apakah ada data yang kosong
df.isnull().values.any()

Hasil:

False

Sekarang cek jumlah yang terkena dan tidak terkena diabetes.

# cek distribusi Outcome
# nantinya akan displit untuk training dan testing
num_obs = len(df)
num_true = len(df.loc[df['Outcome'] == 1])
num_false = len(df.loc[df['Outcome'] == 0])
print("Jumlah terkena diabetes: {0} ({1:2.2f}%)".format(num_true, (num_true/num_obs)*100))
print("Jumlah tidak terkena diabetes: {0} ({1:2.2f}%)".format(num_false, (num_false/num_obs)*100))

Hasil:

Jumlah terkena diabetes: 268 (34.90%)
Jumlah tidak terkena diabetes: 500 (65.10%)

Memilih Algoritma


Ada beberapa faktor yang menentukan pemilihan algoritma, diantaranya:
  • Learning type (supervised, unsupervised, atau reinforcement): karena kita mendapatkan dataset yang sudah ada hasilnya, maka kita pilih supervised.
  • Result (regression atau classification): karena hasinya adalah terkena atau tidak terkena diabetes, maka pilih classification.
  • Complexity: karena masih algoritma awal, maka pilih algoritma yang sederhana.
  • Basic atau Enhance: karena masih awal pemilihan algoritma, maka pilih yang basic terlebih dahulu setelah itu mungkin bisa di-enhance.

Dari sini kita sudah menentukan bahwa kita akan memilih algoritma dengan tipe supervised, hasil yang berbentuk klasifikasi, jenis algoritma sederhana dan basic.

Selanjutnya, kita bisa mengumpulkan kandidat algoritma yang mungkin bisa digunakan, diantaranya:
  • Naive Bayes: Memprediksi dengan mempelajari dari data yang ada sebelumnya.
  • Logistic Regression: Berdasarkan weighting, setiap fitur akan diberikan bobot, bobot akan diproses Logistic Regression sehingga mendapatkan nilai yang lebih dekat ke 0 atau 1. 
  • Decision Tree: Mirip seperti if else, setiap node memiliki decision masing-masing.

Dari ketiga kandidat algoritma tersebut, kita akan pilih Naive Bayes terlebih dahulu, karena:
  • Berbasis probabilitas: memprediksi berdasarkan pengalaman sebelumnya, apakah orang tersebut terkena atau tidak terkena diabetes berdasarkan data-data sebelumnya.
  • Membutuhkan sedikit data.
  • Sederhana, mudah untuk dipahami, performa yang cepat dan efisien.

Melatih Model


Sebelum digunakan untuk klasifikasi, model akan dilatih terlebih dahulu sehingga menghasilkan model spesifik yang kita inginkan.

Pelatihan model ini bisa dilakukan berulang kali, misalnya jika ada data tambahan atau perbaikan data, atau jika nanti hasilnya kurang puas, maka bisa mengganti algoritma yang digunakan dan melakukan pelatihan kembali sehingga hasil klasifikasi menjadi lebih baik.

Pada proses ini kita akan menggunakan library Scikit-learn.

Membagi Data


Dataset akan dibagi menjadi 2 bagian, yaitu 70% untuk pelatihan, dan 30% untuk pengujian.

# import library
from sklearn.model_selection import train_test_split

# dapatkan kolom nama fitur
feature_col_names = list(df.columns[0:8])

# dapatkan kolom nama klas/outcome
predicted_class_name = list(df.columns)[8]

# predictor feature column, shape 8*m
X = df[feature_col_names].values

# predicted class, shape 1*m
y = df[predicted_class_name].values

# split data, 30% test, 70% train
split_test_size = .3
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=split_test_size, random_state=42)

Cek hasil split-nya.

# cek hasil split
print("{0:0.2f}% ada pada training set".format((len(X_train)/len(df.index)) * 100))
print("{0:0.2f}% ada pada test set".format((len(X_test)/len(df.index)) * 100))
print("")
print("Jumlah original terkena diabetes: {0} ({1:2.2f}%)".format(len(df.loc[df['Outcome'] == 1]), (len(df.loc[df['Outcome'] == 1])/len(df.index))* 100))
print("Jumlah original tidak terkena diabetes: {0} ({1:2.2f}%)".format(len(df.loc[df['Outcome'] == 0]), (len(df.loc[df['Outcome'] == 0])/len(df.index))* 100))
print("")
print("Training True: {0} ({1:2.2f}%)".format(len(y_train[y_train[:] == 1]), (len(y_train[y_train[:] == 1]) / len(y_train) * 100)))
print("Training False: {0} ({1:2.2f}%)".format(len(y_train[y_train[:] == 0]), (len(y_train[y_train[:] == 0]) / len(y_train) * 100)))
print("")
print("Testing True: {0} ({1:2.2f}%)".format(len(y_test[y_test[:] == 1]), (len(y_test[y_test[:] == 1]) / len(y_test) * 100)))
print("Testing False: {0} ({1:2.2f}%)".format(len(y_test[y_test[:] == 0]), (len(y_test[y_test[:] == 0]) / len(y_test) * 100)))

Hasil:

69.92% ada pada training set
30.08% ada pada test set

Jumlah original terkena diabetes: 268 (34.90%)
Jumlah original tidak terkena diabetes: 500 (65.10%)

Training True: 188 (35.01%)
Training False: 349 (64.99%)

Testing True: 80 (34.63%)
Testing False: 151 (65.37%)

Pra-pengolahan


Sekarang kita lakukan pra-pengolahan pada data tersebut. Di sini kita akan melihat banyaknya data yang bernilai 0 dan menentukan harus diapakan data tersebut.

Mari kita lihat seberapa banyak data yang berisikan nilai 0.

#lihat data yg 0
print("# rows in dataframe {0}".format(len(df)))
for n in feature_col_names:
    print("# rows missing {0}: {1}".format(n, len(df.loc[df[n] == 0])))

Hasil:

# rows in dataframe 768
# rows missing Pregnancies: 111
# rows missing Glucose: 5
# rows missing BloodPressure: 35
# rows missing SkinThickness: 227
# rows missing Insulin: 374
# rows missing BMI: 11
# rows missing DiabetesPedigreeFunction: 0
# rows missing Age: 0

Untuk menangani nilai 0, kita bisa melakukan:
  • Hapus kolom/fitur tersebut.
  • Ganti nilainya.
  • Didiamkan saja.

Dari hasil di atas, kita bisa lihat bahwa banyak sekali data 0 pada setiap fitur. Contohnya pada Insulin, hampir 50% datanya bernilai 0, dengan jumlah yang segini banyaknya kita tidak bisa menghapus atau membiarkan data tersebut.

Maka kita bisa menggantinya dengan nilai mean.

# mean inputing
from sklearn.impute import SimpleImputer
fill_0 = SimpleImputer(missing_values=0, strategy="mean")
X_train = fill_0.fit_transform(X_train)
X_test = fill_0.fit_transform(X_test)

Sekarang cek apakah masih ada nilai 0.

dff = pd.DataFrame(X_train)
# cek nilai 0
for n in dff.columns:
    print("kolom ke-{0}, jml data 0: {1}".format(n ,len(dff.loc[dff[n] == 0])))

Hasil:

kolom ke-0, jml data 0: 0
kolom ke-1, jml data 0: 0
kolom ke-2, jml data 0: 0
kolom ke-3, jml data 0: 0
kolom ke-4, jml data 0: 0
kolom ke-5, jml data 0: 0
kolom ke-6, jml data 0: 0
kolom ke-7, jml data 0: 0

Sekarang kita bisa lihat bahwa sudah tidak ada lagi data yang bernilai 0.

Melatih Model


Untuk melatih model, bisa dengan.

# import naive bayes
from sklearn.naive_bayes import GaussianNB

# buat model naive bayes
nb_model = GaussianNB()

# train model naive bayes
nb_model.fit(X_train, y_train.ravel())

Evaluasi Model


Sekarang kita evaluasi model yang sudah dilatih dengan data pengujian. 

# prediksi test
nb_predict_test = nb_model.predict(X_test)

Untuk melihat hasilnya, kita bisa menggunakan confusion matrix.

# confusion matrix
print("Confusion Matrix")
print("{0}".format(metrics.confusion_matrix(y_test, nb_predict_test)))
print("")
print("Classification Report")
print(metrics.classification_report(y_test, nb_predict_test))

Hasil:

Prediksi Diabetes Menggunakan Machine Learning

Confusion matrix yang dihasilkan Scikit-learn, bentuknya seperti ini:

Prediksi Diabetes Menggunakan Machine Learning

Kita bisa membacanya:
  • True, berarti hasil klasifikasi benar.
  • False, berarti hasil klasifikasi palsu.
  • Positive, berarti terkena diabetes.
  • Negative, berarti tidak terkena diabetes.

Maka:
  • True Positive (TP), benar terkena diabetes, orang tersebut memang terkena diabetes. TP = 52.
  • True Negative (TN), benar tidak terkena diabetes, orang tersebut memang tidak terkena diabetes. TN = 118.
  • False Positive (FP), palsu terkena diabetes, orang tersebut tidak terkena diabetes namun hasil klasifikasi menunjukkan bahwa dia terkena diabetes. FP = 33.
  • False Negative (FN), palsu tidak terkena diabetes, orang tersebut sebenarnya terkena diabetes namun hasil klasifikasi menunjukna bahwa dia tidak terkena. FN = 28.

Untuk menghitung performa, kita bisa menggunakan:

Akurasi, adalah perbandingan prediksi benar dengan keseluruhan prediksi. Berapa persen prediksi orang yang benar terkena diabetes dan benar tidak terkena diabetes dari keseluruhan orang?

Prediksi Diabetes Menggunakan Machine Learning

Akurasi = ((52+118)/(52+118+33+28))*100 % = 73.59 % 

Presisi, adalah perbandingan prediksi benar positif dengan keseluruhan hasil yang diprediksi positif. Berapa persen prediksi orang yang benar terkena diabetes dengan keseluruhan orang yang diprediksi diabetes?

Prediksi Diabetes Menggunakan Machine Learning

Presisi = (52/(52+33))*100 % = 61.18 %

Recall (Sensitifitas), adalah perbandingan prediksi benar positif dengan keseluruhan data yang benar positif. Berapa persen prediksi orang yang benar terkena diabetes dengan keseluruhan orang memang terkena diabetes?

Prediksi Diabetes Menggunakan Machine Learning

Recall = (52/(52+28))*100 % = 65 %

Specificity, adalah perbandingan prediksi benar negatif dengan keseluruhan data negatif. Dari semua orang yang sehat, berapa orang yang terprediksi sehat?

Prediksi Diabetes Menggunakan Machine Learning

Specificity = (118/(118+33))*100 % = 78.15 %

F1 Score, perbandingan rata-rata presisi dan recall yang dibobotkan.

Prediksi Diabetes Menggunakan Machine Learning

F1 Score = (2*(65%*61%)/(65%+61%))*100 % = 62.94 %

Memilih Acuan Performa Algoritma


Idealnya, apapun algoritma yang digunakan, jika ingin hasilnya bagus, maka FP dan FN harus bernilai 0.

Apabila kita ingin mengevaluasi atau menguji, membandingkan algoritma satu dengan algoritma yang lain, apa yang bisa kita pilih sebagai acuan?

Pilih akurasi apabila FN dan FP memiliki nilai yang simetris atau nilainya hampir sama. Jika nilai FN dan FP berbeda, maka lebih baik pilih F1 Score.

Pilih recall apabila kondisi FP lebih baik dibandingkan kondisi FN. Misalnya pada studi kasus diabetes, lebih baik diprediksi orang yang terkena diabetes tetapi aslinya sehat, daripada diprediksi sehat tetapi orang tersebut menderita diabetes. 

Pilih precision apabila ingin hasilnya benar-benar mementingkan TP. Misalnya, lebih baik ada spam masuk ke inbox dibandingkan email reguler yang dimasukan ke kotak spam.

Pilih specificity apabila ingin hasilnya benar-benar mementingkan TN. Misalnya test narkoba, jangan sampai orang yang tidak memakai malah diprediksi memakai narkoba, sehingga bisa saja dimasukkan ke penjara.  

Dari pertimbangan di atas, acuan mana yang bisa kita pilih? 

Kita bisa memilih acuan recall karena lebih baik orang sehat yang terprediksi diabetes dibandingkan sebaliknya. Untuk nilai acuannya, katakanlah algoritma akan dikatakan bagus apabila recall memiliki nilai lebih dari 70%.

Jika kita lihat kembali hasil evaluasi di atas, nilai recall-nya adalah 65 %. Hasil ini lebih rendah dari nilai acuan, yaitu 70%. Namun, hasil ini wajar karena kita belum melakukan peningkatan performa, atau mencoba algoritma lain yang mungkin bisa lebih baik hasilnya. 

Meningkatkan Performa Prediksi


Mari kita coba menggunakan algoritma lain, yaitu Random Forest Classifier.

# import Random Forest Classifier
from sklearn.ensemble import RandomForestClassifier

# buat model rf
rf_model = RandomForestClassifier(random_state=42, n_estimators=10)

# train model rf
rf_model.fit(X_train, y_train.ravel())

Sekarang kita coba pakai untuk memprediksi

# prediksi test
rf_predict_test = rf_model.predict(X_test)

Tampilkan confusion matrix-nya.

# confusion matrix
print("Confusion Matrix")
print("{0}".format(metrics.confusion_matrix(y_test, rf_predict_test)))
print("")
print("Classification Report")
print(metrics.classification_report(y_test, rf_predict_test))

Hasil:

Prediksi Diabetes Menggunakan Machine Learning

Ternyata nilai recall-nya adalah 54% masih di bawah 70%, dan lebih rendah dibandingkan hasil dari algoritma Naive Bayes.

Sekarang kita coba memakai Algoritma Logistic Regression.

# import logistic regression
from sklearn.linear_model import LogisticRegression

# buat model lr
lr_model = LogisticRegression(C=0.7, random_state=42, solver="liblinear", max_iter=10000)

# train model lr
lr_model.fit(X_train, y_train.ravel())

# prediksi test
lr_predict_test = lr_model.predict(X_test)

# confusion matrix
print("Confusion matrix")
print(metrics.confusion_matrix(y_test, lr_predict_test))
print("")
print("Classification Report")
print(metrics.classification_report(y_test, lr_predict_test))

Hasilnya:

Prediksi Diabetes Menggunakan Machine Learning

Nilai recall-nya adalah 55% masih dibawah 70%. Mungkin kita bisa coba untuk mengatur dataset lagi.

Kalau kita lihat kembali ke atas, jumlah orang terkena dan tidak terkena diabetes jumlahnya tidak sama. Jumlah orang yang terkena diabetes adalah 268 orang, sedangkan jumlah orang yang tidak terkena adalah 500 orang, maka kita bisa atur agar seimbang.

Kita bisa mengatur parameter agar claass weight-nya balanced

Dan kita bisa coba untuk mencari nilai parameter C teroptimal untuk mendapatkan nilai recall yang terbaik. Kita bisa melakukannya dengan perulangan.

# mencari nilai C berdasarkan recall terbaik

# nilai C awal
C_start = 0.01

# nilai C akhir
C_end = 5

# nilai increment C
C_inc = 0.01

# untuk menyimpan nilai C dan recall
C_values, recall_scores = [], []

# untuk menyimpan nilai C
C_val = C_start

# untuk menyimpan nilai recall terbaik
best_recall_score = 0

while (C_val < C_end):
    # masukan nilai C saat ini ke C_values
    C_values.append(C_val)
    
    # buat model lr dengan nilai C saat ini
    lr_model_loop = LogisticRegression(C=C_val, class_weight="balanced", random_state=42, solver="liblinear", max_iter=10000)
    
    # latih model
    lr_model_loop.fit(X_train, y_train.ravel())

    # prediksi test
    lr_predict_loop_test = lr_model_loop.predict(X_test)

    # memperoleh nilai recall
    recall_score = metrics.recall_score(y_test, lr_predict_loop_test)

    # simpan nilai recall saat ini 
    recall_scores.append(recall_score)

    # ambil nilai recall terbaik
    if (recall_score > best_recall_score):
        best_recall_score = recall_score
        best_lr_predict_test = lr_predict_loop_test

    #increment nilai C
    C_val = C_val + C_inc

# ambil nilai C teroptimal berdasarkan nilai recall terbaik
best_score_C_val = C_values[recall_scores.index(best_recall_score)]

# tampilkan recall terbaik dan C terbaik
print("best recall {0:.3f} occured at C={1:.3f}".format(best_recall_score, best_score_C_val))

# plot recal dan c
plt.plot(C_values, recall_scores, "-")
plt.xlabel("C values")
plt.ylabel("recall score")

# buat model lr dengan nilai C terbaik
lr_model = LogisticRegression(class_weight="balanced", C=best_score_C_val, random_state=42, solver="liblinear")

# latih model
lr_model.fit(X_train, y_train.ravel())

# prediksi test
lr_predict_test = lr_model.predict(X_test)

print("confusion matrix")
print(metrics.confusion_matrix(y_test, lr_predict_test))
print("")
print("Classification Report")
print(metrics.classification_report(y_test, lr_predict_test))

Hasilnya:

Prediksi Diabetes Menggunakan Machine Learning

Prediksi Diabetes Menggunakan Machine Learning

Kita bisa mendapatkan nilai recall terbaik 75% dengan nilai parameter C = 0.32. Hasil ini jauh lebih baik dibandingkan hasil-hasil sebelumnya yang nilai recall-nya hanya 55%.

Cross Validation


Pada proses sebelumnya, evaluasi yang telah kita kerjakan adalah menggunakan split validation, dataset dibagi 70% untuk training dan 30% untuk testing, pemilihan datanya secara acak.

Sekarang kita coba memakai K-fold cross validation, metode validasi ini cocok digunakan ketika kita tidak memiliki banyak data untuk melakukan pembagian data (training dan testing) tanpa kehilangan performa model yang signifikan.

Data akan dibagi menjadi K bagian, nilai K yang akan digunakan adalah 10, sehingga nantinya akan terdapat 10 bagian, 9 digunakan untuk training, dan 1 digunakan untuk testing. Setiap iterasi, data testing akan berpindah ke data selanjutnya. Illustrasinya seperti ini:

Prediksi Diabetes Menggunakan Machine Learning

Sekarang kita coba terapkan.

# import logistic regression cv
from sklearn.linear_model import LogisticRegressionCV

# buat model lr cv
lr_cv_model = LogisticRegressionCV(n_jobs=-1, random_state=42, Cs=3, cv=10, refit=False, class_weight="balanced", max_iter=10000)

# latih model lr cv
lr_cv_model.fit(X_train, y_train.ravel())

# predict test
lr_cv_predict_test = lr_cv_model.predict(X_test)

print("Confusion Matrix")
print(metrics.confusion_matrix(y_test, lr_cv_predict_test))
print("")
print("Classification Report")
print(metrics.classification_report(y_test, lr_cv_predict_test))

Hasil:

Prediksi Diabetes Menggunakan Machine Learning

Dari hasil tersebut bisa dilihat bahwa nilai recall adalah 66% lebih rendah dibandingkan ketika memakai split validation. Namun hasil ini kemungkinan masih bisa ditingkatkan lagi dengan mengatur parameter-parameternya.

Mari kita rangkum hasil dari beberapa algoritma yang sudah digunakan:

Algoritma Recall
Naive Bayes 65%
Random Forest 54%
Logistic Regression 55%
Logistic Regression (balanced, optimal C value) 75%
Logistic Regression CV 66%

Mungkin sekian dari postingan ini, dan terimakasih yang sudah membaca.

Rabu, 03 Februari 2021

Deteksi dan Membaca Plat Nomor Kendaraan Secara Otomatis menggunakan Python

Deteksi dan Membaca Plat Nomor Kendaraan Secara Otomatis menggunakan Python

Sudah lama sekali rasanya sejak terakhir kali saya menulis di blog ini, terakhir kali adalah di tahun 2019. Sekarang rasaya saya pengen nulis lagi, rutin lagi seperti dulu seperti saat sebelum diberikan kerjaan freelance dan tugas kuliah yg banyak, jadi agak terbengkalai ini blog mana domainnya masih panjang kan mubadzir hehe.. Jadi mari mulai nulis lagi mumpung dah lulus kerjaan freelance juga jadi masih bisa atur waktu yg fleksibel..

Pada postingan ini saya akan membahas tugas akhir ketika saya kuliah yaitu deteksi dan membaca plat nomor kendaraan secara otomatis.


Deteksi dan Membaca Plat Nomor Kendaraan Secara Otomatis menggunakan Python

Pada awalnya proyek ini ditulis menggunakan Matlab, karena dulu ketika belajar mata kuliah computer vision dosen saya menggunakan Matlab. Matlab bagus sekali untuk digunakan penelitian, praktis, namun sayangnya kebutuhan di industri lebih memilih menggunakan Python, semua lowongan pekerjaan yg saya lihat tentang image processing atau computer vision semuanya mencantumkan Python dan saya belum lihat ada yang mencantumkan Matlab. Jadi karena itu lah saya mencoba belajar Python dan sebagai latihannya saya mencoba menulis ulang kode program tugas akhir ini ke dalam bahasa tersebut.

Tidak semua saya tulis ulang sih, beberapa langkah ada yg saya lewati atau saya ubah, yang terpenting goalnya sama, bisa deteksi dan membaca plat nomor juga belajar hal baru.


Apa saja yang diperlukan?


Ini adalah beberapa perangkat dan bahan yang saya gunakan untuk membuat proyek ini, diantaranya:
  • Laptop Dell G3, dengan spesifikasi:
    • Intel core i5 8300H
    • Ram 16 GB
    • VGA Nvidia GTX 1050 4GB
    • SSD 256 GB
  • Smartphone Zenfone 3 Max, dengan spesifikasi kamera:
    • Resolusi 13 MP
    • Aperture f/2.2, AF
  • Citra test kendaraan yang diambil sendiri di parkiran kampus, dengan ketentuan:
    • Diambil ketika pagi - sore
    • Jarak pengambilan gambar  sekitar 1 meter, plat hampir lurus.
    • Resolusi 2560 x 1920 pixels
    • Jumlah 60 gambar.
  • Citra karakter dari 0-9 A-Z untuk training, saya lupa sumbernya dari mana karena sudah ada di laptop saya dari dulu, kalau saya ingat akan saya cantumkan sumbernya di sini.
    • Setiap karakter berisi 10 citra, berukuran 28 x 28 piksel
  • Untuk kode program saya membutuhkan:
    • Python 3.8.6
    • OpenCV 4.5.1
    • NumPy 1.19.4
    • Tensorflow 2.4.0
    • Matplotlib

Metode yang digunakan


Secara garis besar, bagaimana cara saya mendapatkan hasil deteksi dan pembacaan plat nomor adalah sebagai berikut:

Prapengolahan, foto RGB kendaraan dimasukkan ke sistem (OpenCV akan membacanya BGR), di-resize, diubah menjadi citra grayscale, karena cahaya saat pengambilan gambar tidak selalu sama, lakukan normalisasi kondisi cahaya, lalu konversi menjadi citra BW (hitam-putih) dengan menggunakan pengambanagan Otsu.

Deteksi plat, di tugas akhir, saya menggunakan profile projection untuk melakukan deteksi plat, namun karena pengen nyoba sesatu hal yang baru di sini saya menggunakan contours, dari sini kita bisa mendapatkan area berdasarkan nilai piksel yang sama yang saling berhubungan, termasuk area plat nomornya. Untuk mendapatkan area plat nomor, saya memfilter area tersebut dengan membandingkan lebar dan aspect ratio. 

Segmentasi karakter, karakter yang saya ambil di proyek ini adalah baris pertama pada bagian plat nomor, yaitu bagian yang memuat nomor unik setiap kendaraan. Untuk mendapatkan setiap karakternya, saya menggunakan cara yang sama seperti deteksi plat, yaitu dengan contours, hanya saja filter yang digunakan adalah dari tinggi dan lebar area.

Klasifikasi karakter, di tugas akhir, saya sudah mencoba menggunakan cosine similarity namun jujur saja hasilnya jelek, banyak yg misklasifikasi apalagi untuk karakter yang bentuknya mirip, seperti B dan angka 8. Oleh karena itu untuk sekarang saya pengen coba teknik yang lain, saya coba pakai model yang ada di tutorial Tensorflow ini

Proyek ini bisa dipisah menjadi dua bagian, yaitu bagian pelatihan/training, dan bagian testing. Bagian pelatihan digunakan untuk melatih model agar bisa digunakan untuk mengklasifikasi karakter. Dan bagian testing digunakan untuk deteksi dan membaca plat nomor menggunakan model yang sudah terlatih.

Nanti akan saya bandingkan juga dengan metode yg sudah saya pakai pada tugas akhir saya.

Hasil Per Tahap


Prapengolahan


Kita ketahui bahwa cuaca atau intensitas cahaya setiap harinya sulit untuk ditebak, citra hasil pengambilan data tidak selalu memiliki level cahaya yang sama, ada yang lebih terang ada yang lebih gelap, bahkan dalam satu citra kendaraan pun terkadang terdapat bagian yang terhalang oleh bayangan sehingga apabila tidak dilakukan prapengolahan mungkin bisa mengacaukan proses selanjutnya. Proses ini digunakan untuk mempersiapkan citra agar mudah dalam mendeteksi plat. 

Citra RGB diload ke sistem (OpenCV akan membacanya sebagai BGR), citra tersebut diresize karena rasanya sulit untuk mengamati hasilnya apabila ukurannya terlalu besar saat ditampilkan di layar (hampir full menutupi satu layar). Saya resize dengan mengalikan lebar dan tingginya dengan nilai 0.4 sehingga yang asalnya berukuran 1920 x 2560 sekarang menjadi 768 x 1024, lebih kecil, tidak menutupi hampir seluruh layar ketika menampilkannya.

Sekarang citra akan dilakukan normalisasi cahaya, fungsinya agar intensitas cahayanya sama, lebih mudah untuk dilanjutkan ke proses berikutnya. Normalisasi cahaya ini prosesnya dimulai dari mengubah citra RGB ke grayscale, lakukan operasi opening ke citra grayscale, lalu kurangkan citra grayscale dengan citra hasil opening, hasil pengurangan tersebut selanjutnya bisa dikonversi ke citra BW (hitam putih) dengan pengambangan Otsu. 

Bisa dilihat pada gambar di bawah perbedaan citra BW hasil normalisasi dan tanpa normalisasi, bagian plat dan karakter yang dihasilkan pun lebih jelas apabila dilakukan normalisasi.

Deteksi dan Membaca Plat Nomor Kendaraan Secara Otomatis menggunakan Python
Proses Prapengolahan

Deteksi Plat


Dari hasil pengamatan saya pada hasil prapengolahan, saya melihat bahwa bagian plat nomor membentuk garis kotak dan memiliki intensitas yang sama. 

Pada skripsi saya menggunakan cara profile projection. Jadi saya bikin grafik untuk menghitung setiap piksel putih yang ada pada setiap baris, lalu lakukan pemotongan berdasarkan titik yang tertinggi. Lalu lakukan hal yang sama untuk kolom.

Kira-kira prosesnya seperti ini:

Deteksi dan Membaca Plat Nomor Kendaraan Secara Otomatis menggunakan Python
Profile Projection Plat

Untuk sekarang karena saya ingin mencoba cara baru, saya akan memakai fungsi contours, karena jika saya lihat fungsi ini lumayan cocok juga untuk melakukan deteksi plat dari hasil citra prapengolahan yang mana kontur atau area plat bisa terlihat dengan lumayan jelas, saling berhubungan membuat suatu area.

Dari semua area kontur pada citra, untuk mendapatkan area plat nomor, saya filter berdasarkan aspect ratio dan lebarnya, jika lebarnya lebih dari atau sama dengan 200 piksel dan aspect rationya kurang dari atau sama dengan 4.


Deteksi dan Membaca Plat Nomor Kendaraan Secara Otomatis menggunakan Python
Deteksi plat nomor

Hasilnya? Dari 60 citra 2 diantaranya tidak memuaskan.

Deteksi dan Membaca Plat Nomor Kendaraan Secara Otomatis menggunakan Python
Hasil deteksi plat nomor

Apabila kita bandingkan hasil dari profile projection dan contours, maka profile projection akan menghasilkan pemotongan yang lebih rapih dan pas, contohnya untuk plat di bawah ini: 

Deteksi dan Membaca Plat Nomor Kendaraan Secara Otomatis menggunakan Python

Hal ini dikarenakan pada profile projection akan tepat memotong pada garis plat berdasarkan dari puncak grafik proyeksinya, sedangkan hasil pada contours sangat dipengaruhi oleh bentuk area konturnya, jika kontur bagian plat nyambung dengan bagian body maka hasil deteksi plat tidak akan pas atau bahkan bisa mengacaukannya.

Segmentasi Karakter


Contours juga saya gunakan untuk segmentasi karakter. Citra hasil crop diubah ke citra BW, lalu karena hasilnya terdapat area piksel yang tidak diinginkan maka lakukan operasi opening, operasi ini akan menghilangkan area kecil dekat karakter.

Deteksi dan Membaca Plat Nomor Kendaraan Secara Otomatis menggunakan Python

Untuk mendapatkan kandidat karakternya lakukan filter terhadap kontur dengan berdasarkan lebar dan tingginya, yaitu apabila tingginya dalam rentang 40-60 piksel dan lebarnya lebih dari 10 piksel.

Deteksi dan Membaca Plat Nomor Kendaraan Secara Otomatis menggunakan Python

Apabila dilihat dari hasil di atas, kandidat karakter memiliki bagian yang bukan karakter. Untuk mendapatkan bagian yang benar sebuah karakter, kita bisa mengecek apakah bagian tersebut sebaris, berderet secara horizontal, atau memiliki letak sumbu y yang tidak jauh berbeda.

Kita bisa menghitung selisih letak sumbu y antara kandidat satu dengan yang lainnya, setiap kandidat yang selisihnya kurang dari 11 piksel maka mendapatkan 1 score/poin. Kandidat yang memiliki score yang sama dan tertinggi maka itu adalah karakter yang sesungguhnya.

Deteksi dan Membaca Plat Nomor Kendaraan Secara Otomatis menggunakan Python
Score karakter yang sama dan tertinggi

Dari sini kita sudah memiliki bagian yang benar-benar sebuah karakter. Namun sayangnya karena kita menggunakan contours, karakter tersebut tidak berurutan dari kiri ke kanan melainkan dari atas ke bawah, oleh karena itu mari kita urutkan. Pengurutan ini penting sekali karena menentukan urutan karakter yang nantinya akan diklasifikasi oleh model, kalo urutannya ngaco meskipun klasifikasi karakternya benar ya tetap saja ngaco.

Deteksi dan Membaca Plat Nomor Kendaraan Secara Otomatis menggunakan Python
Hasil pengurutan karakter

Bagaimana dengan tugas akhir saya? Di tugas akhir saya menggunakan profile projection, prosesnya sama seperti pada deteksi plat, hanya saja disini kita perlu memfilter lagi untuk mendapatkan bagian yang benar-benar karakternya.

Deteksi dan Membaca Plat Nomor Kendaraan Secara Otomatis menggunakan Python
Profile Projection Karakter

Klasifikasi karakter


Untuk klasifikasi karakter, di sini saya menggunakan model dari tutorial Tensorflow ini, jadi kita bikin modelnya terlebih dahulu, lalu melatihnya dengan dataset karakter. Hasil dari model terlatih bisa digunakan untuk klasifikasi karakter

Deteksi dan Membaca Plat Nomor Kendaraan Secara Otomatis menggunakan Python
Pelatihan Model

Dan hasil klasifikasinya seperti ini:

Deteksi dan Membaca Plat Nomor Kendaraan Secara Otomatis menggunakan Python

Bagaimana hasilnya? 

Deteksi dan Membaca Plat Nomor Kendaraan Secara Otomatis menggunakan Python

Jadi memang ada banyak hal yang mempengaruhi hasilnya, kalau dari data diatas, dataset yang lebih banyak memang sangat disarankan agar klasifikasi lebih baik. Jika memakai dataset yang sedikit maka performanya tidak jauh beda dengan memakai cosine similarity yang hanya memakai 36 dataset saja.

Kebanyakan misklasifikasi adalah karakter yang mirip seperti 1 dengan I atau B dengan 8, solusinya bisa memisahkan karakter ke dalam 3 bagian, yaitu huruf awal, angka tengah, dan huruf akhir. Selain itu juga karena hasil prapengolahan atau segmentasi yang kurang bagus.  

Saya pernah melakukan percobaan pemisahan karakter menjadi 3 bagian dan memperbaiki prapengolahannya, dan memang terbukti bagus hasilnya, untuk klasifikasinya menggunakan CNN di Matlab dengan dataset berjumlah 1840, sayangnya saya hanya memiliki hasil datanya saja, kodenya sudah tidak bisa saya jalankan lagi, matlabnya berbayar euy 😢 mana lupa metodenya gimana aja dan kodenya pun tidak bersih (acak-acakan), jadi pengingat kalau eksperimen mending langsung ditulis dan sekalian nulis kode yg bersih.

Deteksi dan Membaca Plat Nomor Kendaraan Secara Otomatis menggunakan Python
Hasil Data CNN

Kode Sumber


Proyek ini memang jauh dari kata sempurna, bahkan saya hanya menggunakan metode yang sederhana saja, yang mungkin bisa saja sudah usang, tapi dari sini saya bisa belajar OpenCV, Python, dan sedikit tentang Tensorflow.

Bagi temen-temen yang menginginkan kode sumber beserta penjelasannya, temen-temen bisa mendownloadnya di halaman Github saya.


Terimakasih banyak yang sudah membaca.