Machine learning-ə giriş: scikit-learn, validation və feature-lər
Machine learning-ə giriş: scikit-learn, validation və feature-lər
İndiyə qədər data-nı yüklədik, formalaşdırdıq və ona baxdıq. Bu fəsildə növbəti addıma keçirik: data-dan model qurmaq. Data science workflow-unda machine learning pattern-i əl ilə yazmaq əvəzinə data-dan öyrənən funksiyalar qurmaq deməkdir, və pandas ilə hazırladığınız cədvəl məhz bu funksiyaların girişidir. Burada alqoritmlərin riyaziyyatına dərin girməyəcəyik — bunun üçün ayrıca Machine Learning kitabı var. Məqsəd scikit-learn-ün iş üsulunu öyrənməkdir: data-nı necə təqdim etməli, hər model-lə eyni beş addımı necə keçməli, nəticəyə niyə inanmaq olmaz və feature-ləri model-ə necə vermək olar. Bu üsulu bir dəfə mənimsəyəndə yüzlərlə model eyni görünməyə başlayır.
Machine learning növləri
İki böyük ailə var. Supervised learning-də hər nümunənin düzgün cavabı (label-i) məlumdur və model giriş ilə cavab arasındakı əlaqəni öyrənir. Cavab diskret kateqoriya olanda bu classification-dır (email spam-dır ya yox, şəkildə hansı rəqəm var), davamlı kəmiyyət olanda regression-dır (evin qiyməti, sabahkı tələb). Hər iki halda intuisiya eynidir: nümunələr feature fəzasında nöqtələrdir və model bu nöqtələri ayıran sərhəd (classification) və ya onlardan keçən səth (regression) tapır.
Unsupervised learning-də label yoxdur, model data-nın öz strukturunu tapmalıdır. Clustering bir-birinə yaxın nöqtələri qruplara ayırır, dimensionality reduction isə çoxölçülü data-nın əsas məlumatını saxlayan az ölçülü təqdimat tapır. Birincisi "müştərilər neçə tipdir" sualına, ikincisi "50 ölçülü data-nı iki ölçüdə necə görək" sualına cavab verir.
Data-nın təqdimatı
Scikit-learn-ün bütün alətləri eyni iki obyekt gözləyir. Features matrix X iki ölçülü massivdir, shape-i [n_samples, n_features]: hər sətir bir müşahidə (nümunə), hər sütun bir ölçülən xassə (feature). Target y isə uzunluğu n_samples olan bir ölçülü massivdir, hər nümunənin label-i və ya proqnozlaşdırılacaq dəyəri. Pandas DataFrame-i X, Series-i y kimi birbaşa vermək olar:
X = iris.drop(columns="species") # shape (150, 4)
y = iris["species"] # shape (150,)
Bu konvensiyanın gücü sadəliyindədir: şəkil, mətn, səs — hər şey əvvəlcə bu formaya gətirilir, sonra istənilən model-ə verilir. Feature engineering, aşağıda görəcəyimiz kimi, əslində "xam data-nı X-ə çevirmək" işidir.
Estimator API
Scikit-learn-də hər model, hər transformer estimator-dur və eyni interface-i paylaşır. İş axını beş addımdır:
- Model class-ını import edin.
- Hyperparameter-ləri verərək instance yaradın.
- Data-nı
Xvəyformasına gətirin. fit(X, y)ilə model-i data-ya uyğunlaşdırın.- Yeni data-ya
predict(X_new)(supervised) və yatransform(X_new)/predict(X_new)(unsupervised) tətbiq edin.
from sklearn.linear_model import LinearRegression
model = LinearRegression(fit_intercept=True) # 1–2
model.fit(X, y) # 4
model.coef_, model.intercept_ # öyrənilmiş parametrlər
y_new = model.predict(X_new) # 5
İki konvensiyaya diqqət edin. Hyperparameter-lər constructor-a gedir və fit-dən əvvəl seçilir; data-dan öyrənilən parametrlər isə fit-dən sonra sonunda alt xətt olan atributlarda yaşayır (coef_, intercept_, components_). Bu ayrılıq model-in nəyi sizdən, nəyi data-dan aldığını həmişə aydın edir.
Eyni beş addım bir-birinə bənzəməyən məsələlərdə dəyişmir:
from sklearn.naive_bayes import GaussianNB
from sklearn.model_selection import train_test_split
Xtr, Xte, ytr, yte = train_test_split(X, y, random_state=1)
clf = GaussianNB().fit(Xtr, ytr)
accuracy_score(yte, clf.predict(Xte)) # ≈ 0.97 Iris üçün
from sklearn.decomposition import PCA
from sklearn.mixture import GaussianMixture
X2 = PCA(n_components=2).fit_transform(X) # 4 ölçü → 2 ölçü
labels = GaussianMixture(n_components=3).fit(X).predict(X)
Gaussian naive Bayes Iris-də dörd ölçü ilə növləri təxminən 97% dəqiqliklə ayırır; PCA eyni data-nı iki ölçüyə sıxıb scatter ilə baxmağa imkan verir, və orada növlərin kifayət qədər yaxşı ayrıldığı görünür; Gaussian mixture isə label-ə heç baxmadan üç qrup tapır. Digits dataset-i (8×8 piksel əl yazısı rəqəmlər, 64 feature) eyni axını bir az daha mürəkkəb data-da göstərir: load_digits() → train_test_split → GaussianNB və ya Isomap ilə 2D xəritə. Beş addım dəyişmədi, yalnız class adı dəyişdi — scikit-learn-ün əsas dəyəri budur.
Hyperparameter-lər və model validation
Model-i fit etdiniz, training data-da dəqiqlik 100%-dir. Bu rəqəm heç nə demir. Ən yaxın qonşunun label-ini qaytaran model training nümunələrində həmişə özünü tapır və həmişə düz çıxır; eyni model yeni data-da pis işləyə bilər. Model validation model-in görmədiyi data üzərində necə işlədiyini ölçməkdir, və yeganə düzgün metrik budur.
Ən sadə yol holdout set-dir: data-nı train_test_split ilə iki hissəyə bölürsünüz, birində fit, digərində score. Qiyməti var: məlumatın bir hissəsi öyrənmədən kənarda qalır və tək bölgüdən alınan rəqəm bölgünün təsadüfünə həssasdır. Cross-validation hər iki problemi həll edir. k-fold data-nı k hissəyə bölür, hər dəfə birini holdout, qalanını training edir və k score-un ortasını götürür. Həddi leave-one-out-dur, k = n_samples; kiçik data-da dəqiq, böyük data-da çox bahadır.
from sklearn.model_selection import cross_val_score
scores = cross_val_score(model, X, y, cv=5)
scores.mean(), scores.std()
Standart sapma score-un özü qədər vacibdir: 0.90 ± 0.01 və 0.90 ± 0.08 eyni model deyil.
Bias-variance trade-off
Validation score-u niyə aşağıdır? İki fərqli səbəb var və onların dərmanı əksdir. Underfitting (yüksək bias) model data-nın formasını tutmaq üçün çox sadə olanda baş verir: düz xətt ilə parabolanı approximate edirsiniz. Həm training, həm validation score aşağıdır. Overfitting (yüksək variance) model o qədər çevikdir ki, hər training nöqtəsinin küyünü də öyrənir: training score əla, validation score pisdir. Model mürəkkəbliyi artdıqca bias düşür, variance qalxır; optimum ikisinin arasındadır.
Bunu görmək üçün iki əyri var. Validation curve bir hyperparameter-i (məsələn, polynomial dərəcəsi) dəyişib training və validation score-u çəkir:
from sklearn.model_selection import validation_curve
degrees = np.arange(1, 15)
tr, va = validation_curve(PolynomialRegression(), X, y,
param_name="polynomialfeatures__degree",
param_range=degrees, cv=7)
Sol tərəfdə hər iki əyri aşağıdır (underfitting), sağ tərəfdə training yüksək, validation düşür (overfitting), validation əyrisinin zirvəsi optimal mürəkkəblikdir. Learning curve isə mürəkkəbliyi sabit saxlayıb training ölçüsünü dəyişir və fərqli sual verir: daha çox data kömək edəcəkmi? İki əyri konvergensiya edib bir-birinə yaxınlaşıbsa, əlavə data faydasız olacaq, daha mürəkkəb model lazımdır. Aralarında böyük boşluq varsa, daha çox data overfitting-i azaldacaq. Pul və vaxt xərcləməzdən əvvəl bu əyriyə baxın.
Bir neçə hyperparameter eyni anda olanda əl ilə axtarış əvəzinə GridSearchCV bütün kombinasiyaları cross-validation ilə yoxlayır:
from sklearn.model_selection import GridSearchCV
grid = GridSearchCV(model, {"polynomialfeatures__degree": [1, 3, 5],
"linearregression__fit_intercept": [True, False]}, cv=7)
grid.fit(X, y)
grid.best_params_, grid.best_estimator_
Grid kombinasiya sayı ilə partlayır; çox parametrdə RandomizedSearchCV eyni interface ilə təsadüfi nümunə götürür. Validation və metriklərin daha dərin təhlili — precision, recall, ROC, calibration — Machine Learning kitabındadır.
Feature engineering scikit-learn üslubunda
Real data [n_samples, n_features] ədəd matrisi kimi gəlmir: kateqoriya, mətn, şəkil, boşluqlar. Feature engineering bu xam formaları model-in başa düşdüyü ədədlərə çevirməkdir və scikit-learn bunu da estimator kimi edir: fit çevirmənin lüğətini öyrənir, transform tətbiq edir.
Kateqoriyalar. {"city": "Baku"} dəyərini sadəcə 1, 2, 3 kimi kodlamaq səhvdir, çünki model Baku < Ganja < Sumqayit kimi sıra olduğunu güman edəcək. One-hot encoding hər kateqoriya üçün 0/1 sütun yaradır. DictVectorizer dict siyahısını, OneHotEncoder isə massiv sütununu bu şəkildə çevirir; yüksək cardinality-də sparse_output=True yaddaşı xilas edir.
from sklearn.feature_extraction import DictVectorizer
vec = DictVectorizer(sparse=False)
vec.fit_transform([{"city": "Baku", "rooms": 2}, {"city": "Ganja", "rooms": 3}])
# [[1, 0, 2], [0, 1, 3]] sütunlar: city=Baku, city=Ganja, rooms
Mətn. Ən sadə yol sözləri saymaqdır: CountVectorizer hər sənədi lüğətdəki hər sözün sayı ilə təqdim edir. Problem budur ki, tez-tez rast gəlinən sözlər ("və", "bir") ağırlıq alır. TfidfVectorizer hər sayı sözün bütün sənədlərdə nə qədər nadir olduğu ilə vurur (term frequency – inverse document frequency); nadir söz daha məlumatlıdır. Nəticə sparse matrisdir, və scikit-learn-ün əksər model-ləri sparse girişlə işləyir.
Şəkillər. Ən sadə feature pikselin özüdür — digits nümunəsində 64 piksel 64 feature idi. Daha mənalı feature-lər (HOG, kənar detektorları) scikit-image kitabxanasından gəlir; müasir praktikada isə bu işi deep learning model-ləri görür.
Törəmə feature-lər. Linear model parabolanı tuta bilmir, amma siz ona x² sütununu versəniz tutar. PolynomialFeatures(degree=3) x-dən x, x², x³ sütunlarını yaradır; model hələ də linear-dır, amma feature fəzası qeyri-xəttidir. Bu, basis function regression adlanır və sadə model-lərin gücünü kəskin artırır.
Missing data. Scikit-learn model-ləri NaN qəbul etmir. SimpleImputer(strategy="mean") boşluqları sütunun ortası, medianı və ya ən tez-tez dəyəri ilə doldurur; fit statistikaları training data-dan öyrənir, transform onları tətbiq edir. Daha ağıllı imputation və "boş idi" flag-i Machine Learning kitabının feature engineering fəslindədir.
Pipeline: leakage-in qarşısı
Hər çevirmə ayrı addımdırsa, iki təhlükə yaranır. Birincisi sadəcə səliqəsizlikdir: beş transformer-i düzgün ardıcıllıqla iki dəfə (training və test üçün) çağırmaq lazımdır. İkincisi daha ciddidir: imputer-i və ya scaler-i bütün data-da fit etsəniz, test nümunələrinin statistikası training-ə sızır. Buna data leakage deyilir və validation score-unu süni şəkildə yüksəldir — model-iniz production-da gözləniləndən pis işləyəcək, siz isə bunu ancaq orada görəcəksiniz.
make_pipeline çevirmələri və model-i bir estimator-a yığır:
from sklearn.pipeline import make_pipeline
from sklearn.preprocessing import PolynomialFeatures
from sklearn.impute import SimpleImputer
model = make_pipeline(SimpleImputer(strategy="mean"),
PolynomialFeatures(degree=2),
LinearRegression())
cross_val_score(model, X, y, cv=5)
Pipeline-ın fit-i hər addımı ardıcıl fit edib növbətiyə ötürür, predict-i isə yalnız transform çağırır. Cross-validation-a pipeline verəndə hər fold-da imputer və polynomial yalnız həmin fold-un training hissəsində fit olunur — leakage struktur olaraq mümkünsüz olur. Hyperparameter adları addım__parametr formasını alır (yuxarıdakı polynomialfeatures__degree), beləliklə GridSearchCV bütün pipeline-ı bir obyekt kimi tune edir. Qayda belədir: data-ya toxunan hər çevirmə pipeline-ın içində olmalıdır. Pipeline-dan kənarda qalan tək bir StandardScaler bütün validation-ı şübhə altına qoyur.
Tapşırıqlar
- Iris dataset-ini
train_test_splitilə bölün,GaussianNB,KNeighborsClassifiervəLogisticRegressionmodel-lərinə eyni beş addımı tətbiq edin. Hər biri üçün training accuracy-ni və 5-fold cross-validation ortalamasını yan-yana cədvəldə göstərin; fərq ən böyük olan model haqqında nə deyə bilərsiniz? y = sin(x) + küysintetik data-sı yaradın.PolynomialFeatures+LinearRegressionpipeline-ı üçün dərəcə 1-dən 20-yə qədər validation curve çəkin, sonra optimal dərəcə ilə 20-dən 500 nümunəyə qədər learning curve çəkin. Hər iki qrafikdə underfitting və overfitting zonalarını işarələyin.- Bir sütunun 30%-ni
NaNetdiyiniz regression data-sındaSimpleImputer-i əvvəlcə bütün data-da fit edib sonra cross-validation edin, sonra eyni imputer-i pipeline-ın içində cross-validation edin. Score-ları müqayisə edin və fərqin hansı istiqamətdə olduğunu leakage anlayışı ilə izah edin.