Arkaplanında bir Makine Öğrenmesi Modeli bulunan bir web uygulaması oluşturmak

Verilerle çalışan biriyseniz, en az bir kez bir makine öğrenmesi modeli yaratmış olabilirsiniz. Hepimiz her gün çeşitli makine öğrenmesi modelleriyle karşılaşıyoruz. Her model farklıdır ve farklı amaçlar için kullanılır. İyi bir model oluşturmak için farklı algoritmalar kullanmamız gereken çeşitli problemlerimiz var. Ama modelimizi inşaa ettikten sonra ne yapmalıyız? Çoğu insan model oluşturur, ancak modeli gerçek hayatta uygulamadan pek bir faydası olmayacağı gerçeğini görmezden gelirler. Bu eğiticide, bir model oluşturma ve onu web’de dağıtma sürecinin tamamını gözden geçireceğiz.

Diyelim bir bankada çalışıyorsunuz ve patronunuz sizden kredi uygunluk sürecini otomatikleştiren bir web uygulaması oluşturmanızı istedi. Yapmanız gereken çok basit. Geçmiş verileri alarak bir model inşaa etmek, bu model kaydetmek ve ardından bir web uygulamasına dönüştürmek.

İlk olarak Masaüstü’nde (Desktop) LoanPredictionApp isimli bir boş klasör oluşturalım.

Sanal Ortam Oluşturmak

Bu eğiticiye başlamadan önce bilgisayarınızdaki düzeni bozmamak adına, aşağıda yapacağımız tüm işlemleri sanal bir ortam içerisinde gerçekleştirelim. Basitçe ifade etmek gerekirse, bir sanal ortam, diğer projeleri etkileme endişesi olmadan belirli bir proje üzerinde çalışmanıza izin veren, Python’un yalıtılmış bir çalışma kopyasıdır. Her proje için birden çok Python versiyonunun aynı makineye kurulumuna olanak tanır. Aslında Python’un ayrı kopyalarını kurmaz, ancak ortam değişkenlerini ve paketleri izole ettiği için farklı proje ortamlarını izole tutmanın akıllıca bir yolunu sağlar. Yanlış paket versiyonlarindan şikayet eden hata mesajlarının bir çaresidir

Python’da sanal ortam oluşturmak için kullanabileceğiniz bazı popüler kütüphaneler/araçlar şöyledir: virtualenv, virtualenvwrapper, pvenv ve venv. Burada virtualenv paketine odaklanacağız.

virtualenv‘in sisteminizde zaten kurulu olması muhtemeldir. Bununla birlikte, bu paketin global sisteminizde kurulu olup olmadığını, eğer kurulu ise, hangi sürümü kullandığınızı kontrol edin:

which virtualenv

veya

virtualenv --version

Eğer bu paket kurulu değilse, virtualenv paketini pip3 install virtualenv komutunu kullanarak yüklemeniz gerekmektedir.

Bunu işlemi gerçekleştirdikten sonra, önce bir Terminal penceresi açın ve oluşturduğunuz LoanPredictionApp isimli dizine gidin:

(base) Arat-MacBook-Pro-2:~ mustafamuratarat$ cd Desktop/LoanPredictionApp/

ve aşağıda verilenleri gerçekleştirin. Aşağıdaki kod, kurduğumuz virtualenv modülünü çağıracak ve mevcut klasörümüzün içinde myEnvironment adlı yeni bir klasör oluşturacak (yani myEnvironment isminde bir sanal ortam) ve myEnvironment‘te yeni bir Python kurulumu yükleyecek (tabii ki kurmak istediğiniz Python sürümünü değiştirebilirsiniz). Ben burada sistemimde kurulu olan aynı Python 3 sürümünü sanal ortamımda da kullanmak istediğim için which python3 komutunu kullandım:

(base) Arat-MacBook-Pro-2:LoanPredictionApp mustafamuratarat$ python3 -m venv myEnvironment

Yukarıdaki komut, projenizde tüm bağımlılıkların (dependencies) kurulu olduğu bir myEnvironment dizini oluşturur.

Kurulum tamamlandıktan sonra, bu sanal ortamı kullanmak istiyorsanız, izole edilmiş ortamınızı source komutu ile etkinleştirmeniz gerekir:

(base) Arat-MacBook-Pro-2:LoanPredictionApp mustafamuratarat$ source myEnvironment/bin/activate
(myEnvironment) (base) Arat-MacBook-Pro-2:LoanPredictionApp mustafamuratarat$

Burada dikkat etmeniz gereken nokta, komut satırının bilgisayarınızın adından, yeni oluşturduğunuz sanal ortamın ismine dönüşmesidir. Proje üzerinde her çalışmak istediğinizde bu sanal ortamı aktive etmelisiniz. Bu nedenle source myEnvironment/bin/activate kodunu çalıştırmayı unutmayın. Ancak, her seferinde bu kodu yazmak istemediğinizde ve bilgisayarınız başlar başlamaz, sanal ortamınızın aktive olmasını istiyorsanız şu sayfada bulunan adımları takip ederek bir betik yazabilirsiniz.

Artık bu ortamda istediğiniz paketleri ve bu paketlerin versiyonlarını global sisteminizi etkilemeden kurabilirsiniz.

Jupyter not defterine sanal ortama ait çekirdek (kernel) eklemek

Aşağıdaki eğitici JupyterLab üzerinden gerçekleştirilecektir. Ancak buradaki problem ise, kişisel bilgisayarınızdaki JupyterLab’ın global sisteminizde bulunan Python sürümü için çekirdeğinin (kernel) bulunmasıdır. Bu nedenle, sanal ortamımıza yüklediğimiz Python için çekirdek elle oluşturmamız için bazı işlemler daha yapmamız gerekmektedir. Bunun için ilk olarak Jupyter için IPython çekirdeğini sağlayan ipykernel‘i sanal ortamımızda kurmamız gerekmektedir:

(myEnvironment) (base) Arat-MacBook-Pro-2:LoanPredictionApp mustafamuratarat$ pip3 install ipykernel
Collecting ipykernel
  Using cached ipykernel-6.6.1-py3-none-any.whl (126 kB)
Collecting nest-asyncio
  Using cached nest_asyncio-1.5.4-py3-none-any.whl (5.1 kB)
Collecting matplotlib-inline<0.2.0,>=0.1.0
  Using cached matplotlib_inline-0.1.3-py3-none-any.whl (8.2 kB)
Collecting appnope
  Using cached appnope-0.1.2-py2.py3-none-any.whl (4.3 kB)
Collecting ipython>=7.23.1
  Using cached ipython-7.31.0-py3-none-any.whl (792 kB)
Collecting debugpy<2.0,>=1.0.0
  Using cached debugpy-1.5.1-cp38-cp38-macosx_10_15_x86_64.whl (1.7 MB)
Collecting jupyter-client<8.0
  Using cached jupyter_client-7.1.0-py3-none-any.whl (129 kB)
Collecting traitlets<6.0,>=5.1.0
  Using cached traitlets-5.1.1-py3-none-any.whl (102 kB)
Collecting tornado<7.0,>=4.2
  Using cached tornado-6.1-cp38-cp38-macosx_10_9_x86_64.whl (416 kB)
Collecting jedi>=0.16
  Using cached jedi-0.18.1-py2.py3-none-any.whl (1.6 MB)
Collecting prompt-toolkit!=3.0.0,!=3.0.1,<3.1.0,>=2.0.0
  Using cached prompt_toolkit-3.0.24-py3-none-any.whl (374 kB)
Collecting pickleshare
  Using cached pickleshare-0.7.5-py2.py3-none-any.whl (6.9 kB)
Collecting pygments
  Using cached Pygments-2.11.2-py3-none-any.whl (1.1 MB)
Requirement already satisfied: setuptools>=18.5 in ./myEnvironment/lib/python3.8/site-packages (from ipython>=7.23.1->ipykernel) (56.0.0)
Collecting backcall
  Using cached backcall-0.2.0-py2.py3-none-any.whl (11 kB)
Collecting decorator
  Using cached decorator-5.1.1-py3-none-any.whl (9.1 kB)
Collecting pexpect>4.3
  Using cached pexpect-4.8.0-py2.py3-none-any.whl (59 kB)
Collecting parso<0.9.0,>=0.8.0
  Using cached parso-0.8.3-py2.py3-none-any.whl (100 kB)
Collecting python-dateutil>=2.1
  Using cached python_dateutil-2.8.2-py2.py3-none-any.whl (247 kB)
Collecting entrypoints
  Using cached entrypoints-0.3-py2.py3-none-any.whl (11 kB)
Collecting pyzmq>=13
  Using cached pyzmq-22.3.0-cp38-cp38-macosx_10_9_x86_64.whl (1.3 MB)
Collecting jupyter-core>=4.6.0
  Using cached jupyter_core-4.9.1-py3-none-any.whl (86 kB)
Collecting ptyprocess>=0.5
  Using cached ptyprocess-0.7.0-py2.py3-none-any.whl (13 kB)
Collecting wcwidth
  Using cached wcwidth-0.2.5-py2.py3-none-any.whl (30 kB)
Collecting six>=1.5
  Using cached six-1.16.0-py2.py3-none-any.whl (11 kB)
Installing collected packages: wcwidth, traitlets, six, ptyprocess, parso, tornado, pyzmq, python-dateutil, pygments, prompt-toolkit, pickleshare, pexpect, nest-asyncio, matplotlib-inline, jupyter-core, jedi, entrypoints, decorator, backcall, appnope, jupyter-client, ipython, debugpy, ipykernel
Successfully installed appnope-0.1.2 backcall-0.2.0 debugpy-1.5.1 decorator-5.1.1 entrypoints-0.3 ipykernel-6.6.1 ipython-7.31.0 jedi-0.18.1 jupyter-client-7.1.0 jupyter-core-4.9.1 matplotlib-inline-0.1.3 nest-asyncio-1.5.4 parso-0.8.3 pexpect-4.8.0 pickleshare-0.7.5 prompt-toolkit-3.0.24 ptyprocess-0.7.0 pygments-2.11.2 python-dateutil-2.8.2 pyzmq-22.3.0 six-1.16.0 tornado-6.1 traitlets-5.1.1 wcwidth-0.2.5
WARNING: You are using pip version 21.1.1; however, version 21.3.1 is available.
You should consider upgrading via the '/Users/mustafamuratarat/Desktop/LoanPredictionApp/myEnvironment/bin/python3 -m pip install --upgrade pip' command.

Ardından, sanal ortamınız için oluşturduğunuz Python çekirdeğini aşağıdaki komutu kullanarak Jupyter’e ekleyebilirsiniz:

(myEnvironment) (base) Arat-MacBook-Pro-2:LoanPredictionApp mustafamuratarat$ python3 -m ipykernel install --user --name=myEnvironment
Installed kernelspec myEnvironment in /Users/mustafamuratarat/Library/Jupyter/kernels/myenvironment

/Users/mustafamuratarat/Library/Jupyter/kernels/myenvironment isimli klasörün içinde, her şeyi doğru yaptıysanız aşağıdaki şekilde görünmesi gereken bir kernel.json dosyası bulacaksınız:

(base) Arat-MacBook-Pro-2:~ mustafamuratarat$ cd /Users/mustafamuratarat/Library/Jupyter/kernels/myenvironment
(base) Arat-MacBook-Pro-2:myenvironment mustafamuratarat$ ls
kernel.json	logo-32x32.png	logo-64x64.png
(base) Arat-MacBook-Pro-2:myenvironment mustafamuratarat$ cat kernel.json
{
 "argv": [
  "/Users/mustafamuratarat/Desktop/LoanPredictionApp/myEnvironment/bin/python3",
  "-m",
  "ipykernel_launcher",
  "-f",
  "{connection_file}"
 ],
 "display_name": "myEnvironment",
 "language": "python",
 "metadata": {
  "debugger": true
 }

Hepsi bu kadar! Artık myenvironment isiml, sanal ortamını Jupyter’de çekirdek olarak seçebilirsiniz. JupyterLab’de bunun nasıl görüneceği aşağıda görülmektedir:

Bu butona tıklayarak, myenvironment çekirdeğine sahip Jupyter not defteri üzerinde model geliştirmeye başlayabilirsiniz.

Yeni bir Jupyter not defteri başlattığınızda, sağ üst köşedeki daire içerisinde çekirdeğinizin ismi yazmalıdır:

Web uygulaması için gerekli kütüphaneleri sanal ortama kurmak

Web uygulaması için kullanacağımız paketler numpy, pandas, sklearn ve streamlit‘tir. Bu kütüphaneleri sanal ortamımıza yüklememiz gerekmektedir. Bunun için pip Python paket yöneticisini kullanabilirsiniz (Anaconda kullanıyorsanız, gerekli komutları tercih ediniz.)

(myEnvironment) (base) Arat-MacBook-Pro-2:ExcelStreamlit_WebApp mustafamuratarat$ pip3 install numpy pandas sklearn streamlit

Bu komut satırı çalıştıktan sonra gerekli kütüphaneler sanal ortamınızda kullanılmaya hazır olacaktır.

Veri Kümesi

Model oluşturabilmek için bir veri setine ihtiyacımız var. Bu eğitici için kredi uygunluk sürecini otomatikleştirmek istediğimizden, aşağıdaki Kaggle bağlantısında bulunan Loan Prediction (Kredi Tahmin) veri kümesi kullanılacaktır. https://www.kaggle.com/ninzaami/loan-predication/home

Kaggle’a üye olup, train_u6lujuX_CVtuZ9i (1).csv isimli veri dosyasını indiriniz. Ardından, LoanPredictionApp klasörünün altında data isimli yeni bir klasör yaratalım. İndirdiğimiz csv dosyasını, data klasörünün içerisine taşıyınız. train_u6lujuX_CVtuZ9i (1).csv dosyasının ismini full_dataset.csv olarak değiştiriniz.

full_dataset.csv dosyasında 12 tane sütun vardır:

Değişken Tanımı Veri Tipi Aldığı değerler
Loan_ID Başvurulan kredi için banka tarafından verilen numara    
Gender Başvuru sahibinin cinsiyeti Kategorik Male (Erkek) / Female (Kadın)
Married Başvuru sahibinin evli olup olmadığı bilgisi Kategorik Yes (Evet) / No (Hayır)
Dependents Başvuru sahibinin bakmakla yükümlü olduğu kişi sayısı Kategorik 0 / 1 / 2 / 3+
Education Başvuru sahibinin eğitim seviyesi Kategorik (Graduate (Lisansüstü) / Under Graduate (Lisans)
Self_Employed Başvuru sahibinin kendi işini yapıp yapmadığının bilgisi Kategorik Yes (Evet) / No (Hayır)
ApplicantIncome Başvuru sahibinin geliri Nümerik  
CoapplicantIncome Başvuru sahibiyle birlikte başvuran kişinin geliri Nümerik  
LoanAmount Verilen kredinin tutarı (bin) Nümerik  
Loan_Amount_Term Ay cinsinden kredi vadesi Nümerik  
Credit_History Kredi geçmişi Nümerik 1.0 / 0.0
Property_Area Kredi istenilen mülk alanının yeri Kategorik Semiurban (Yarı Kentsel) / Urban (Kentsel) / Rural (Kırsal)
Loan_Status Kredinin onaylanıp onaylanmadığı bilgisi Kategorik Y (Evet) / N (Hayır)

Artık modelleme aşamasına geçmeye hazırız.

İlk olarak, full_dataset.csv veri kümesi $\%80 - \%20$ oranı kullanılarak iki kısıma ayrılacaktır. $\%80$’lik kısıma eğitim veri kümesi (training dataset) denilecek ve bu küme model eğitimi (model training) için, $\%20$’lik kısıma ise test veri kümesi (testing dataset) denilecek ve bu küme model değerlendirme (model evaluation) aşamasında kullanılacaktır. Test veri kümesine modelleme boyunca kesinlikle dokunulmayacaktır ve bu veri kümesi modelimizin görmediği verileri (unseen data) içermelidir. Model doğrulaması (model validation), hiperparametre seçimi (hyperparameter selection) ve çapraz doğrulama (cross validation) adımları train_ctrUa4K.csv veri kümesinden ayrılan $\%80$’lik kısım kullanılarak gerçekleştirilecektir.

Model Oluşturma

İlk olarak, model oluştururken kullanılacak tüm kütüphaneleri tek bir Jupyter not defteri hücresinde içe aktaralım:

import pandas as pd
import numpy as np
import pickle 

from sklearn.model_selection import train_test_split, GridSearchCV
from sklearn.pipeline import Pipeline
from sklearn.impute import SimpleImputer
from sklearn.preprocessing import MinMaxScaler, OneHotEncoder
from sklearn.compose import ColumnTransformer
from sklearn.ensemble import RandomForestClassifier
from sklearn.metrics import  accuracy_score, precision_score, recall_score, f1_score, roc_auc_score

Daha sonra, pandas kütüphanesini kullanarak verilerimizi inceleyelim. Burada, read_csv fonksiyonu, verileri Python ortamına okutmak için yeterlidir.

# Tam Veri Kümesi (Full Dataset)
full_set = pd.read_csv('./data/full_dataset.csv')
full_set.head(5)

Veri kümesindeki değişkenlerin veri tiplerine (data types) bakalım:

full_set.dtypes

# Gender                object
# Married               object
# Dependents            object
# Education             object
# Self_Employed         object
# ApplicantIncome        int64
# CoapplicantIncome    float64
# LoanAmount           float64
# Loan_Amount_Term     float64
# Credit_History       float64
# Property_Area         object
# Loan_Status           object
# dtype: object

Veri kümesi hem nicel (nümerik, sayısal) hem de kategorik (nitel) değişkenlerden oluşmaktadır.

Tam veri kümesinin (full_dataset.csv) şekline bakalım:

print(full_set.shape)
# (614, 13)

Etiketli (labelled) veri kümesinde 614 gözlem (observation) ve 13 sütun vardır. Burada Loan_ID sütunu modelimiz için herhangi bir bilgi taşımamaktadır, bu nedenle, bu sütunu elimine edebiliriz. Loan_Status değişkeni yapacağımız analiz için bağımlı değişkendir ve 2 sınıftan oluşmaktadır. Elimizdeki veri kümesi tarihi bir veridir (historical data). Geçmişte, müşterilerin kredi başvurusunun onaylanıp onaylanmadığı bilgisini taşımaktadır. Burada iki sınıflı sınıflandırma problemi ile karşı karşıyayız. Müşterilerin (yani gözlemlerin veya deneklerin) bilgisi ise Gender, Married, Dependents, Education, Self_Emplyed, ApplicantIncome, CoapplicantIncome, LoanAmount, Loan_Amount_Term, Credit_History ve Property_Area değişkenleri ile verilmektedir.

İlk olarak bu tam veri kümesinden, analizimizde kullanmayacağımız Loan_ID sütununu düşürelim:

full_set.drop(['Loan_ID'], axis=1, inplace=True)
full_set.head()

Bağımlı değişken (dependent variable) olan Loan_Status değişkeni Y (Yes) ve N (No) olmak üzere iki kategoriden oluşmaktadır. Bu değişkeni nümerikleştirelim. Y kategorisi için 1.0; N kategorisi için 0.0 eşleşmesi gerçekleştirelim:

full_set['Loan_Status'] = full_set['Loan_Status'].map({'N':0,'Y':1})
full_set.head()

Ayrıca, elimizdeki tam veri kümesinde kayıp gözlem (missing value) olup olmadığını kontrol edelim:

full_set.isnull().sum()

# Gender               13
# Married               3
# Dependents           15
# Education             0
# Self_Employed        32
# ApplicantIncome       0
# CoapplicantIncome     0
# LoanAmount           22
# Loan_Amount_Term     14
# Credit_History       50
# Property_Area         0
# Loan_Status           0
# dtype: int64

Görüldüğü üzere bir kaç değişkende kayıp gözlem vardır. Ancak, kayıp gözlem imputasyonunu (missing value imputation) tam veri kümesine uygulamak veri sızıntısına (data leakage) neden olacaktır. Bu nedenle, öncelikle tam veri kümesini, $\%80 - \%20$ oranı kullanılarak iki kısıma ayıralım. $\%80$’lik kısım eğitim veri kümesi (training dataset), $\%20$’lik kısıma test veri kümesi (testing dataset) olacaktır. İşte şimdi doğru bir şekilde eğitim veri kümesindeki parametreleri kullanarak test veri kümesi üzerinde veri imputasyonu gerçekleştirebiliriz.

Veri kümesine ayırma işlemi Scikit-Learn kütüphanesindeki train_test_split fonksiyonu kullanılarak yapılır. Ancak, bu fonksiyon, bağımlı değişken(ler) ile bağımsız değişken(ler)i ayrı ayrı kabul etmektedir. Bu nedenle, bağımlı değişken olan Loan_Status değişkenini başka bir değişkene atılalım:

X_full = full_set.drop(["Loan_Status"], axis=1)
y_full = full_set["Loan_Status"]

Burada, X_full bağımsız değişkenlerin (özniteliklerin - features) olduğu bir matristir.

Artık, tam veri kümemizi parçalayabiliriz.

X_train, X_test, y_train, y_test = train_test_split(X_full, y_full, test_size  = 0.2, random_state=42)

print(f"Eğitim Kümesinin şekli: {X_train.shape}, \nTest Kümesinin Şekli: {X_test.shape}")

# Eğitim Kümesinin şekli: (491, 11), 
# Test Kümesinin Şekli: (123, 11)

Kolaylıkla anlaşılabileceği üzere, eğitim veri kümesinde 491 müşteriye, test veri kümesinde ise 123 müşteriye ait bilgi bulunmaktadır. Bağımsız değişkenlerin sayısı ise 11’dir.

Burada eğitim veri kümesi çok büyük olmadığı için model doğrulama (model validation), çapraz doğrulama (cross validation) kullanılarak elde edilecektir. Çapraz doğrulama gerçekleştirilirken de, kullanacağımız makine öğrenmesi modelinin hiperparametrelerine Izgara Arama (Grid Search) kullanılarak ince ayar (fine tuning) verilecektir.

Ancak, veri kümesinde hem sayısal (nümerik) hem de kategorik (categorical) değişkenler vardır. Buna ek olarak, eğitim kümesinde kayıp gözlemler de mevcuttur. Bu farklı değişkenlere ayrı ayrı veri önişleme (data pre-processing) gerçekleştirilecektir. Örneğin, nümerik değişkenler ölçeklendirilecektir (scaling) ve kategorik değişkenler bire-bir-kodlama (one-hot-encoding) kullanılarak nümerikleştirilecektir.

Fakat, herhangi bir veri ön-işleme gerçekleştirirken, veri sızıntısını (data leakage) engellemek için , bu ön-işleme adımını, çapraz doğrulamanın her parçasında ayrı ayrı tekrarlamanız gerekmektedir. Bu adımlar gerek veri ölçekleme (data scaling) olabilir, gerekse sentetik veri oluşturma (Synthetic Data Generation - SMOTE, ADASYN, Oversampling, Undersampling) olabilir, veya kayıp değer tamamlama (missing value imputation) ya da kategorik değişken kodlama (categorical variable encoding) olabilir.

Verideki nümerik (sayısal) ve kategorik değişkenleri ayırdıktan sonra, bu değişkenlerin türüne göre farklı veri önişleme adımlarını Scikit-Learn kütüphanesindeki Pipeline fonksiyonunu kullanarak bir iletim hattı üzerine oturtabilir ve daha sonra gene Scikit-Learn kütüphanesinde bulunan ColumnTransformer fonksiyonu yardımı ile bu dönüşleri ayrı ayrı uygulayabilirsiniz.

Daha sonra başka bir Pipeline (İletim Hattı) oluşturarak, önce bir önişleme gerçekleştirir ve daha sonra #GridSearchCV fonksiyonu ile istediğiniz makine öğrenmesi modeli için çapraz doğrulama ile Izgara Arama (Grid Search) kullanarak hiperparametre araştırması gerçekleyebilirsiniz. İşte, arka planda k-parça çapraz doğrulama (k-fold cross validation) yapılırken, nümerik ve kategorik değişkenler için ayrı ayrı tanımladığınız tüm önişleme adımlarının parametreleri k-1 parçadaki verilerden elde edilecek ve daha sonra bu parametreler k-ıncı parçada uygulanacaktır ve bu, k defa aynı şekilde tekrarlanacaktır.

Öncelikle veri kümemizdeki nümerik ve kategorik değişkenlerin isimlerini otomatik olarak çekelim:

num_features = full_set.drop(['Loan_Status'], axis = 1).select_dtypes(include = 'number').columns
num_features
# Index(['ApplicantIncome', 'CoapplicantIncome', 'LoanAmount',
#        'Loan_Amount_Term', 'Credit_History'],
#       dtype='object')

ApplicantIncome, CoapplicantIncome, LoanAmount, Loan_Amount_Term, ve Credit_History değişkenleri nümerik veri yapısına sahiptir.

cat_features = full_set.select_dtypes(include = 'object').columns
cat_features
# Index(['Gender', 'Married', 'Dependents', 'Education', 'Self_Employed',
#        'Property_Area'],
#       dtype='object')

Benzer şekilde, Gender, Married, Dependents, Education, Self_Employed, ve Property_Area değişkenleri kategorik veri yapısına sahiptir.

Yukarıda da bahsedildiği gibi, nümerik ve kategorik değişkenlere ayrı ayrı veri ön-işleme adımları gerçekleştirilecektir. Nümerik değişkenler aynı birime sahip olmaları için Minimum-Maximum Ölçekleme (Mix-Max Scaling) yöntemi ile ölçeklenecektir ve varsa kayıp gözlemler ortalama (mean) ile impute edilecektir. Benzer şekilde, kategorik değişkenler, makine öğrenmesi modelinin anlayabileceği şekilde bire-bir-kodlama (one-hot-encoding) yöntemi kullanılarak nümerikleştirilecektir ve bu değişkenlerde kayıp gözlem varsa, bu kayıp gözlem sabit bir değer ile (örneğin, missing yani bilinmiyor bilgisi) ile impute edilecektir. Bu iki farklı değişken türü için gerçekleştirilecek işlemler, iki farklı iletim hattı (pipeline) ile gerçekleştirilecektir.

# Sayısal değişkenlere uygulanacak veri ön-işleme adımları için iletim hattı (pipeline)

num_transformer = Pipeline(steps = [('Imputer', SimpleImputer(missing_values=np.nan, strategy='mean')),
                                    ('MinMaxScaler', MinMaxScaler())])
# Pipeline(steps=[('Imputer', SimpleImputer()), ('MinMaxScaler', MinMaxScaler())])

# Kategorik değişkenlere uygulanacak veri ön-işleme adımları için iletim hattı (pipeline)

cat_transformer = Pipeline(steps = [('Imputer', SimpleImputer(strategy = 'constant', fill_value = 'missing')),
                                    ('OneHotEncoder', OneHotEncoder(categories = 'auto', drop=None, handle_unknown = 'ignore'))])

# Pipeline(steps=[('Imputer',
#                  SimpleImputer(fill_value='missing', strategy='constant')),
#                 ('OneHotEncoder',
#                  OneHotEncoder(drop='None', handle_unknown='ignore'))])

İletim hatlarını tanımladıktan sonra artık sütun dönüşümlerine (column transformers) geçebiliriz.

preprocessor = ColumnTransformer(transformers = [('num', num_transformer, num_features), 
                                                 ('cat', cat_transformer, cat_features)],
                                 remainder = 'drop',
                                 n_jobs = -1,
                                 verbose = False)

# ColumnTransformer(n_jobs=-1,
#                   transformers=[('num',
#                                  Pipeline(steps=[('Imputer', SimpleImputer()),
#                                                  ('MinMaxScaler',
#                                                   MinMaxScaler())]),
#                                  Index(['ApplicantIncome', 'CoapplicantIncome', 'LoanAmount',
#        'Loan_Amount_Term', 'Credit_History'],
#       dtype='object')),
#                                 ('cat',
#                                  Pipeline(steps=[('Imputer',
#                                                   SimpleImputer(fill_value='missing',
#                                                                 strategy='constant')),
#                                                  ('OneHotEncoder',
#                                                   OneHotEncoder(drop='None',
#                                                                 handle_unknown='ignore'))]),
#                                  Index(['Gender', 'Married', 'Dependents', 'Education', 'Self_Employed',
#        'Property_Area'],
#       dtype='object'))])

Artık veri ön-işleme hattımız hazır!

Şimdi, bu veri önişleme adımı ile, oluşturacağımız Rastgele Ormanlar (random forest) algoritmasını bir araya getirebilir, kolaylıkla çapraz doğrulama uygulayabiliriz. Böylelikle, yukarıda bahsedilen veri sızıntısından da kaçınırız.

pipe = Pipeline(steps = [('preprocess', preprocessor),
                         ('RF_model', RandomForestClassifier(class_weight = "balanced", n_jobs=-1))],
                verbose=False)         

Izgara arama gerçekleştirirken kullanacağımız parametreler için olası değerleri içeren bir sözlük hazırlayalım:

parameters_grid = [{'RF_model__n_estimators':[10, 20, 50],
                    'RF_model__max_features': ['sqrt', 'log2', 0.25, 0.5, 0.75],
                    'RF_model__max_depth' : [2, 4, 5, 6, 7, 8]}
                  ]

Gerekli tanımlamaları yaptıktan sonra artık çapraz doğrulamalı ızgara aramayı ayarlayabiliriz. Burada 10-katlı çapraz doğrulama (10-fold cross validation) kullanacağız ve performans ölçütü olarak doğruluk oranını (accuracy rate) tercih ediyoruz.

search = GridSearchCV(estimator = pipe, param_grid = parameters_grid, cv = 10, scoring = 'accuracy', return_train_score=False, verbose=1, n_jobs=-1)

Artık elimizdeki eğitim verisini modellerimize uydurmaya (fitting) başlayabiliriz:

best_model = search.fit(X_train, y_train)

Tam tamına 90 farklı moel denenecektir. Bu modellerden en iyisi max_depth=2, max_features=0.75, ve n_estimators=20 hiperparametrelerine sahip bir Rastgele Ağaç modelidir.

Bazı değerlendirme ölçütleri ile bu modelin eğitim kümesi ve test kümesi üzerinde performanslarını kontrol edebiliriz. Oluşturabileceğimiz fonksiyon şu şekilde olabilir:

def TrainTestScores(y_train, y_train_pred, y_test, y_test_pred):
    
    scores = {"train_set": {"Accuracy" : accuracy_score(y_train, y_train_pred),
                            "Precision" : precision_score(y_train, y_train_pred),
                            "Recall" : recall_score(y_train, y_train_pred),                          
                            "F1 Score" : f1_score(y_train, y_train_pred),
                           "AUC": roc_auc_score(y_train, y_train_pred)},
    
              "test_set": {"Accuracy" : accuracy_score(y_test, y_test_pred),
                           "Precision" : precision_score(y_test, y_test_pred),
                           "Recall" : recall_score(y_test, y_test_pred),                          
                           "F1 Score" : f1_score(y_test, y_test_pred),
                          "AUC:": roc_auc_score(y_test, y_test_pred)}}
    
    return scores

predict metodu kullanarak tahminler kolaylıkla elde edilebilir:

ytrain_pred = best_model.predict(X_train)
ytest_pred = best_model.predict(X_test)

Her iki küme için değerlendirme ölçütleri (evaluation metrics) şu şekildedir:

TrainTestScores(y_train, ytrain_pred , y_test, ytest_pred)
# {'train_set': {'Accuracy': 0.8167006109979633,
#   'Precision': 0.8,
#   'Recall': 0.9824561403508771,
#   'F1 Score': 0.8818897637795275,
#   'AUC': 0.709348875544566},
#  'test_set': {'Accuracy': 0.7886178861788617,
#   'Precision': 0.7596153846153846,
#   'Recall': 0.9875,
#   'F1 Score': 0.8586956521739131,
#   'AUC:': 0.7030523255813954}}

Burada amaç arkasında makine öğrenme modeli bulunan bir web uygulaması geliştirmek olduğu için bu modeli uygun kabul edelim ve pickle nesnesi olarak kaydedelim:

pickle_out = open("classifier.pkl", mode = "wb") 
pickle.dump(best_model, pickle_out) 
pickle_out.close()

Artık dizinimizde classifier.pkl modeli olacaktır.

Bu modeli istediğimiz zaman, aşağıdaki iki kod satırı kullanarak geri yükleyebiliriz:

pickle_in = open('classifier.pkl', 'rb') 
classifier = pickle.load(pickle_in)

Web uygulaması oluştururken de yapacağımız şey de budur!

Web Uygulaması

Modelimizi elde edip, pickle nesnesi olarak kaydettikten sonra, web uygulaması geliştirmeye geçebiliriz. Bu web uygulamasının kodunu Streamlit kütüphanesi kullanarak yazacağız.

Streamlit, makine öğrenmesi ve veri bilimi ekipleri tarafından model dağıtımı için kullanılan popüler bir açık kaynaklı yazılım çerçeves,dir. Ve en iyi yanı, ücretsiz ve tamamen Python tabanlı olmasıdır

Burada JupyterLab yerine Visual Studio Code kullanacağız. Siz istediğiniz Entegre Geliştirme Ortamını (Integrated Development Environment - IDE) tercih edebilirsiniz.

Visual Studio Code’u çalıştırdıktan sonra LoanPredictionApp klasörünü pencereye sürükleyiniz. Burada önemli olan Visual Studio Code’un oluşturduğunuz sanal ortamı tanımasıdır.

İlk olarak app isimli ve .py uzantılı boş bir Python yürütülebilir dosyası (Python executable file) yaratalım:

Tüm gerekli Python kütüphanelerini içe aktaralım:

import streamlit as st
import pandas as pd
import numpy as np
import sklearn
import pickle

İlk adım olarak, streamlit kütüphanesindeki set_page_config fonksiyonunu kullanarak web uygulamasının bazı yapılandırmasını ayarlayacağız. Oluşturacağınız web uygulamasının başlığını ve kullanacağınız favicon’u bu şekilde yerleştirebilirsiniz:

st.set_page_config(page_title="Kredi Uygunluğu", page_icon=":bank:", layout="wide")

Daha sonra, web uygulamasını tanıtan bir kaç metin ekleyelim:

st.markdown("<h1 style='text-align: center; font-size: 40px;'>Arat Banka Hoşgeldiniz!</h1>", unsafe_allow_html=True)
st.markdown("<h1 style='text-align: center; font-size: 20px;'>Aşağıda verilen gerekli bilgileri girerek müşterinin kredi uygunluğuna karar verebilirsiniz.</h1>", unsafe_allow_html=True)
st.markdown("##")

Ardından, pickle nesnesi olarak kaydettiğimiz modelimizi, pickle kütüphanesindeki load fonksiyonunu kullanarak yükleyelim. Burada önemli olan, app.py dosyası ile classifier.pkl dosyasının aynı dizin içinde yer alması.

# Eğitilmiş modeli yüklemek
pickle_in = open('classifier.pkl', 'rb') 
model = pickle.load(pickle_in)

Daha sonra, kullanıcıdan elimizdeki değişkenlere göre girdi talep ederiz. Nümerik değişkenler için bir kaydırıcı (slider), kategorik değişkenler için bir seçim kutusu (selectbox) kullanabiliriz:

# Kullanıcı girdisi
Gender_input = st.selectbox(label = 'Başvuru sahibinin cinsiyeti nedir?', options = ("Erkek", "Kadın"))
Married_input = st.selectbox(label = 'Başvuru sahibi medeni hali', options = ("Evli", "Bekar"))
Dependents_input = st.selectbox(label = 'Başvuru sahibinin bakmakla yükümlü olduğu kişi sayısı kaçtır?', options = ("0"," 1", "2", "3+"))
Education_input = st.selectbox(label = 'Başvuru sahibinin eğitim seviyesi nedir?', options = ("Lisansüstü", "Lisans"))
Self_Employed_input = st.selectbox(label = 'Başvuru sahibi kendi işinin sahibi midir?', options = ("Evet", "Hayır"))
ApplicantIncome_input = st.slider(label = 'Başvuru sahibinin geliri ne kadardır?', min_value = 0, max_value = 100000)
CoapplicantIncome_input = st.slider(label = 'Başvuru sahibiyle birlikte başvuran kişinin geliri nedir?', min_value = 0.0, max_value = 100000.0)
LoanAmount_input = st.slider(label = 'İstenilen kredinin tutarı ne kadardır?', min_value = 0.0, max_value = 100000.0)
Loan_Amount_Term_input = st.slider(label = 'İstenilen kredinin vadesi ay cinsinden ne kadardır?', min_value = 0.0, max_value = 480.0, step=1.0)
Credit_History_input = st.selectbox(label = 'Başvuru sahibinin kredi geçmişi var mı?', options = (1.0, 0.0))
Property_Area_input = st.selectbox(label = 'Kredi istenilen mülk alanı nerededir?', options = ("Yarı Kentsel", "Kentsel", "Kırsal"))

Kullanıcı girdilerini aldıktan sonra, bu girdileri bir sözlüğe (dictionary) taşıyalım ve bu sözlüğü web uygulamamız üzerinde bir DataFrame olarak yazdıralım. Bu DataFrame bize başvuru sahibi ile ilgili özet bilgileri göstersin:

st.markdown("<h1 style='text-align: center; font-size: 40px;'>Başvuru Sahibinin Özet Bilgileri:</h1>", unsafe_allow_html=True)
summary_dictionary = {'Cinsiyeti': Gender_input,  'Medeni Hali': Married_input, 'Bağımlı Sayısı': Dependents_input, 'Eğitim': Education_input,  'Kendi İşi': Self_Employed_input,
'Geliri': ApplicantIncome_input, 'Birlikte Başvuranın Geliri': CoapplicantIncome_input, 'Kredi Miktarı': LoanAmount_input, 'Kredi Vadesi': Loan_Amount_Term_input,
'Kredi Geçmişi': Credit_History_input, 'Mülk Alanı': Property_Area_input}

summary_df  = pd.DataFrame([summary_dictionary])
st.table(summary_df)

Kullanıcının girdiği verileri kullanarak tahmin yapacak bir fonksiyonu tanımlamamız gerekmektedir. Bu fonksiyona predict_ adını verelim. Burada, aynı zamanda, Scikit-Learn kütüphanesi kullanarak oluşturduğumuz, pickle nesnesi olarak kaydettiğimiz ve app.py dosyası içerisinde geri yüklediğimiz makine öğrenmesi modelimizin predict metodunu kullanmalıyız.

Ayrıca, kullanıcıdan bazı girdileri Türkçe almamızdan dolayı, bu Türkçe girdileri, elimizdeki verilere göre uyarlamamız gerekmektedir.

def predict_(model, Gender_input, Married_input, Dependents_input, Education_input, Self_Employed_input, ApplicantIncome_input, CoapplicantIncome_input, LoanAmount_input,
 Loan_Amount_Term_input, Credit_History_input, Property_Area_input):
    
    # kullanıcı girdisini ön işleme
    if Gender_input == "Erkek":
        Gender_var = "Male"
    else:
        Gender_var = "Female"
    
    if Married_input == "Evli":
        Married_var = "Yes"
    else:
        Married_var = "No"

    if Education_input == "Lisansüstü":
        Education_var = "Graduate"
    else:
        Education_var = "Not Graduate"

    if Self_Employed_input == "Evet":
        Self_Employed_var = "Yes"
    else:
        Self_Employed_var = "No"
    
    if Property_Area_input == "Yarı Kentsel":
        Property_Area_var = "Semiurban"
    elif Property_Area_input == "Kentsel":
        Property_Area_var = "Urban"
    else:
        Property_Area_var = "Rural"

    features = {'Gender': Gender_var,  'Married': Married_var, 'Dependents': Dependents_input, 'Education': Education_var,  'Self_Employed': Self_Employed_input, 
    'ApplicantIncome': ApplicantIncome_input, 'CoapplicantIncome': CoapplicantIncome_input, 'LoanAmount': LoanAmount_input, 'Loan_Amount_Term': Loan_Amount_Term_input,
    'Credit_History': Credit_History_input, 'Property_Area': Property_Area_var}

    features_df  = pd.DataFrame([features])
    
    prediction_ = model.predict(features_df)

    if prediction_ == 0:
        pred = 'red edildi.'
    else:
        pred = 'onaylandı.'
    
    return pred

Daha sonra bu predict_ fonksiyonunu kullanarak, kullanıcı girdileriyle birlikte canlı tahminleri (live predictions) elde ederiz ve gerekli şekilde ekrana yazdırırız:

# Tahmin butonuna tıklandığında, kaydedilmiş modelden tahmin elde et ve yazdır
st.markdown("---")

st.markdown("<h1 style='text-align: left; font-size: 20px;'>Girilen bilgilere göre başvuru sahibine kredi verilip verilmemesini öğrenmek için aşağıdaki butona tıklayınız:</h1>", unsafe_allow_html=True)

if st.button('Kredi verilsin mi?'):
    
    result_ = predict_(model, Gender_input, Married_input, Dependents_input, Education_input, Self_Employed_input, ApplicantIncome_input, CoapplicantIncome_input, LoanAmount_input, Loan_Amount_Term_input, Credit_History_input, Property_Area_input)

    if result_ == 'red edildi.':
        st.error('Krediniz {}'.format(result_))
    else:
        st.success('Krediniz {}'.format(result_))
        st.write(f'İstenilen kredi miktarı {LoanAmount_input * 1000: ,.2f} Türk lirasıdır.')

Uygulama kodunun yazılması böylelikle sona ermiştir. Terminal penceresi üzerinde streamlit run app.py komutu çalıştırıldığında, web uygulaması http://localhost:8501 lokal URL’inde web tarayıcınızda açılacaktır.

İstenilen değişkenlere girdi verdiğinizde arkaplanda çalışan makine öğrenme modeli hızlı bir şekilde yaptığı tahmini size sunacaktır. Değişkenlere değer verelim ve bir kaç sonuç görelim.

Kredi kabul edildiğinde (yani, model Loan_Status = 1 tahmini yaptığında), yeşil arkaplanıyla “Krediniz kabul edildi.” ibaresi belirecektir. Bunu st.success fonksiyonuyla gerçekleştirebiliriz. Bu bilginin ardından ne kadar kredi verileceği bilgisi yazdırılacaktır.

Kredi red edildiğinde (yani, model Loan_Status = 0 tahmini yaptığında) ise kırmızı arka planla “Krediniz red edildi.” ibaresi ekrana yazdırılacaktır.

Bu uygulamayı daha da detaylandırmak mümkün.

requirements.txt dosyasını oluşturma

Uygulama kodunun yazımı tamamlandığında tüm Python bağımlılıklarının (dependencies) olduğu requirements.txt dosyasını oluşturmak için pipreqs kütüphanesini kullanabiliriz. Bu kütüphane yüklü değilse, pip3 install pipreqs komutu ile kişisel bilgisayarınıza yükleyebilirsiniz.

Daha sonra bir Terminal penceresinden, app.py dosyamızın olduğu klasöre gidelim ve pipreqs ./ komutu ile kullanılan tüm paketleri requirements isimli metin dosyasına yazdıralım:

(base) Arat-MacBook-Pro-2:~ mustafamuratarat$ cd Desktop/LoanPredictionApp/
(base) Arat-MacBook-Pro-2:LoanPredictionApp/ mustafamuratarat$ pipreqs ./
INFO: Successfully saved requirements file in ./requirements.txt

requirements.txt metin dosyasını oluşturmamızın sebebi, uygulamayı canlı ortama dağıtırken kullanacağımız bulut sunucusunun gerekli gereksinimleri yüklemesi içindir.

Artık uygulamamıza sahip olduğumuza göre, uygulamayı dağıtmaya başlamaya hazırız.

Streamlit Sharing aracılığıyla dağıtım

Uygulamalarınızı Heroku, digital ocean, AWS veya Google Cloud gibi herhangi bir özel bulut sistemi dağıtıcısında barındırabilirsiniz. Bir önceki yazımda bir Streamlit uygulamasını nasıl Heroku’da dağıtabileceğinizi göstermiştim. Burada ise biraz farklılık yaparak, Streamlit Sharing ile nasıl dağıtabileceğinizi öğreteceğim!

Kaydedilmiş modelimizin pickle nesnesini (classifier.pkl), web uygulama kodumuzun olduğu app.py dosyasını ve requirements.txt dosyasını hazır hale getirdikten sonra, bunları Github hesabımızdaki herkese açık bir depoya (repository) push edebiliriz.

Ardından Streamlit Sharing web sitesini ziyaret edeceğiz. Bu web sayfasının sağ üst köşesinde bulunan Sign inbutonuna tıklayarak, tarafından kullanılmak üzere Streamlit Sharing’e erişim izni verilen Github hesabınızla oturum açın.

Giriş yaptıktan sonra, New App (Yeni uygulama) butonuna tıklayacağız.

Ve daha sonra gerekli alanların değerlerini giriniz:

ve “Deploy! (Dağıt!)” butonuna tıklayınız. Hatırlanması gereken önemli bir nokta, app.py dosyanızın, modelin .pkl ve requirements.txt dosyalarıyla birlikte Streamlit Sharing’de dağıtmak istediğiniz dal (branch) üzerinde olması gerektiğidir.

Bir kaç dakika sonra uygulamanız canlı ortama dağıtılmış olacaktır! İşte, arkaplanında bir makine öğrenmesi modeli bulunan web uygulamanıza https://share.streamlit.io/mmuratarat/loanpredictionapp/main/app.py bağlantısından ulaşabilirsiniz!

Oluşturduğumuz tüm dosyaları burada bulunan Github deposunda bulabilirsiniz. Github sayfasındaki dosyaları her güncellemenizde, canlı uygulama da yeniden inşaa edilip, güncellenecektir.