Зеҳн
Омӯзиши мошинӣ ва амиқ

Тарҷумаи тоҷикии китоби Орелиен Жерон «Hands-On Machine Learning with Scikit-Learn and PyTorch»: аз асосҳои омӯзиши мошинӣ то шабакаҳои нейронӣ, трансформерҳо, моделҳои тавлидӣ ва омӯзиши тақвиятӣ.

Агенти Зеҳнӣ · 13 ноябри 2026 · 20 саҳифа · 1 писанд

БарномасозӣТехнологияИлм
Аз аввал хондан

Боби 3. Таснифбандӣ

Пештар гуфтам, ки вазифаҳои маъмултарини омӯзиши бо назорат регрессия — пешгӯии қиматҳо — ва таснифбандӣ — пешгӯии синфҳо — мебошанд. Дар боби 2 масъалаи регрессия, яъне пешгӯии арзиши манзилро бо алгоритмҳои гуногун, аз ҷумла регрессияи хаттӣ, дарахтҳои қарор ва ҷангалҳои тасодуфӣ таҳқиқ кардем. Кори ин алгоритмҳо дар бобҳои баъдӣ муфассалтар шарҳ дода мешавад. Акнун ба системаҳои таснифбандӣ мегузарем.

MNIST

Дар ин боб маҷмӯи додаҳои MNIST-ро истифода мебарем: 70 000 тасвири хурди рақамҳои дастнавис, ки хонандагони мактабҳои миёна ва кормандони Идораи барӯйхатгирии аҳолии ИМА навиштаанд. Ҳар тасвир бо рақами дар он тасвиршуда барчаспгузорӣ шудааст. Ин маҷмӯа чунон зиёд таҳқиқ шудааст, ки онро аксар вақт «hello world»-и омӯзиши мошинӣ меноманд: ҳар касе алгоритми нави таснифбандӣ пешниҳод мекунад, мехоҳад кори онро дар MNIST бинад. Ҳар омӯзандаи омӯзиши мошинӣ низ дер ё зуд бо он кор мекунад.

Scikit-Learn барои боргирии маҷмӯаҳои маъмул функсияҳои ёрирасони зиёд дорад. MNIST яке аз онҳост. Рамзи зерин онро аз OpenML.org мегирад:

from sklearn.datasets import fetch_openml

mnist = fetch_openml('mnist_784', as_frame=False, parser="liac-arff")

Эзоҳи тарҷумон. Дар манбаъ parser="liac-arff" нест. Бе он Scikit-Learn 1.9 бо pandas 3 ҳангоми боркунӣ хатои TypeError: expected string or bytes-like object, got 'int' медиҳад; бо ин параметр ҳамон додаҳо бор мешаванд. Дар бобҳои 7 ва 9 низ ҳамин тавр.

Бастаи sklearn.datasets асосан се навъи функсия дорад:

  • fetch_*, мисли fetch_openml(), барои боргирии маҷмӯаҳои воқеӣ;
  • load_* барои бор кардани маҷмӯаҳои хурди намунавие, ки ҳамроҳи Scikit-Learn меоянд ва аз интернет боргирӣ намехоҳанд;
  • make_* барои тавлиди маҷмӯаҳои сунъӣ, ки барои санҷишҳо муфиданд.

Маҷмӯаҳои тавлидшуда одатан ҳамчун кортежи (X, y) бармегарданд: додаҳои вуруд ва ҳадафҳо, ҳар ду дар шакли массивҳои NumPy. Маҷмӯаҳои дигар объектҳои sklearn.utils.Bunch мебошанд: луғатҳое, ки ба унсурҳояшон ҳамчун атрибут низ муроҷиат кардан мумкин аст. Онҳо одатан чунин майдонҳо доранд:

МайдонМаъно
"DESCR"Тавсифи маҷмӯи додаҳо
"data"Додаҳои вуруд, одатан массиви дученакаи NumPy
"target"Барчаспҳо, одатан массиви якченакаи NumPy

fetch_openml() каме фарқ мекунад: пешфарз вурудҳоро ҳамчун DataFrame ва барчаспҳоро ҳамчун Series-и Pandas медиҳад, магар маҷмӯа тунук бошад. Аммо MNIST тасвир дорад ва DataFrame барои он беҳтарин интихоб нест. Пас as_frame=False мегузорем, то массивҳои NumPy гирем. Онҳоро мебинем:

>>> X, y = mnist.data, mnist.target
>>> X
array([[0, 0, 0, ..., 0, 0, 0],
       [0, 0, 0, ..., 0, 0, 0],
       [0, 0, 0, ..., 0, 0, 0],
       ...,
       [0, 0, 0, ..., 0, 0, 0],
       [0, 0, 0, ..., 0, 0, 0],
       [0, 0, 0, ..., 0, 0, 0]])
>>> X.shape
(70000, 784)
>>> y
array(['5', '0', '4', ..., '4', '5', '6'], dtype=object)
>>> y.shape
(70000,)

70 000 тасвир ҳаст ва ҳар кадом 784 хусусият дорад, зеро андозаи тасвир 28 × 28 пиксел аст. Ҳар хусусият шиддати як пикселро аз 0 — сафед — то 255 — сиёҳ — нишон медиҳад. Як рақамро мебинем (расми 3.1): вектори хусусиятҳои намунаро гирифта, ба массиви 28 × 28 табдил медиҳем ва бо imshow()-и Matplotlib нишон медиҳем. cmap="binary" харитаи хокистарӣ медиҳад, ки дар он 0 сафед ва 255 сиёҳ аст:

import matplotlib.pyplot as plt

def plot_digit(image_data):
    image = image_data.reshape(28, 28)
    plt.imshow(image, cmap="binary")
    plt.axis("off")

some_digit = X[0]
plot_digit(some_digit)
plt.show()

Намунаи тасвири MNIST

Расми 3.1. Намунаи тасвири MNIST.

Ба 5 монанд аст ва барчасп ҳам ҳаминро мегӯяд:

>>> y[0]
'5'

Барои тасаввури мураккабии вазифа расми 3.2 чанд тасвири дигарро нишон медиҳад. Шакли рақамҳо хеле гуногун аст. Бо вуҷуди ин, тасвирҳо тозаанд, хуб дар марказ ҷойгиранд, зиёд тоб нахӯрдаанд ва андозаи рақамҳо тақрибан яксон аст. Ин маҷмӯа пешкоркарди зиёд намехоҳад; додаҳои воқеӣ одатан ин қадар қулай нестанд.

Аммо истед! Пеш аз таҳқиқи амиқ ҳамеша маҷмӯи санҷишӣ сохта, канор гузоред. MNIST-и аз fetch_openml() гирифташуда аллакай тақсим шудааст: 60 000 тасвири аввал омӯзишӣ ва 10 000 тасвири охир санҷишӣ мебошанд.

X_train, X_test, y_train, y_test = X[:60000], X[60000:], y[:60000], y[60000:]

Маҷмӯи омӯзишӣ аллакай омехта шудааст. Ин хуб аст, зеро қисмҳои санҷиши салибӣ монанд мешаванд: намехоҳем дар як қисм баъзе рақамҳо набошанд. Баъзе алгоритмҳо ба тартиби намунаҳо ҳассосанд ва ҳангоми пайдарпай гирифтани намунаҳои монанд бад кор мекунанд. Омехтакунӣ инро пешгирӣ мекунад.

Рақамҳо аз MNIST

Расми 3.2. Рақамҳо аз маҷмӯи MNIST.

Омӯзонидани таснифгари дуӣ

Ҳоло масъаларо сода карда, танҳо як рақам, масалан 5-ро муайян мекунем. Ин «ошкоркунандаи 5» намунаи таснифгари дуӣ (binary classifier) аст, ки танҳо ду синф — 5 ва ғайри-5-ро фарқ мекунад. Аввал векторҳои ҳадафро месозем:

y_train_5 = (y_train == '5')  # True for all 5s, False for all other digits
y_test_5 = (y_test == '5')

Акнун таснифгареро интихоб карда, меомӯзонем. Оғози хуб таснифгари фаромадани градиентии стохастикӣ (SGD) бо синфи SGDClassifier-и Scikit-Learn аст. Он бо маҷмӯаҳои хеле калон самаранок кор мекунад. Як сабаб ин аст, ки SGD намунаҳоро мустақилона ва яктоӣ коркард менамояд. Аз ҳамин ҷиҳат барои омӯзиши онлайн низ мувофиқ аст, чунон ки баъдтар мебинед. Онро бо тамоми маҷмӯи омӯзишӣ меомӯзонем:

from sklearn.linear_model import SGDClassifier

sgd_clf = SGDClassifier(random_state=42)
sgd_clf.fit(X_train, y_train_5)

Акнун барои ошкор кардани тасвири 5 истифода мебарем:

>>> sgd_clf.predict([some_digit])
array([ True])

Таснифгар гумон мекунад, ки тасвир 5 аст — True. Дар ин ҳолат дуруст гуфт! Акнун самаранокиро арзёбӣ мекунем.

Меъёрҳои самаранокӣ

Арзёбии таснифгар аксар вақт аз арзёбии модели регрессия хеле душвортар аст, бинобар ин қисми зиёди бобро ба он мебахшем. Меъёрҳо зиёданд: як пиёла қаҳваи дигар гиред ва барои омӯхтани мафҳуму ихтисораҳои нав омода шавед!

Чен кардани дурустӣ бо санҷиши салибӣ

Мисли боби 2, санҷиши салибӣ роҳи хуби арзёбӣ аст. Бо cross_val_score() модели SGDClassifier-ро бо санҷиши салибии сеқисмӣ месанҷем. Ёдовар мешавем: дар санҷиши салибии k-қисмӣ (k-fold) маҷмӯи омӯзишӣ ба k қисм, дар ин ҷо се, ҷудо мешавад ва модел k бор омӯзонида мешавад. Ҳар бор қисми дигар барои арзёбӣ канор мемонад:

>>> from sklearn.model_selection import cross_val_score
>>> cross_val_score(sgd_clf, X_train, y_train_5, cv=3, scoring="accuracy")
array([0.95035, 0.96035, 0.9604 ])

Дар ҳамаи қисмҳо дурустӣ (accuracy), яъне ҳиссаи пешгӯиҳои дуруст, бештар аз 95 фоиз аст! Аҷоиб менамояд, ҳамин тавр не? Аммо пеш аз хурсандии зиёд таснифгари содаеро бинем, ки ҳар тасвирро ба синфи сершумортарин нисбат медиҳад. Дар ин ҷо он синфи манфӣ, яъне ғайри-5 аст:

from sklearn.dummy import DummyClassifier

dummy_clf = DummyClassifier()
dummy_clf.fit(X_train, y_train_5)
print(any(dummy_clf.predict(X_train)))  # prints False: no 5s detected

Дурустии ин моделро тахмин карда метавонед? Месанҷем:

>>> cross_val_score(dummy_clf, X_train, y_train_5, cv=3, scoring="accuracy")
array([0.90965, 0.90965, 0.90965])

Бале, бештар аз 90 фоиз! Танҳо тақрибан 10 фоизи тасвирҳо 5 мебошанд. Агар ҳамеша бигӯед, ки тасвир 5 нест, тақрибан 90 фоизи вақт дуруст мегӯед. Аз Нострадамус ҳам беҳтар!

Ин нишон медиҳад, ки чаро accuracy одатан меъёри бартаридодашудаи таснифгарҳо нест, махсусан барои маҷмӯаҳои номутавозин, ки баъзе синфҳо аз дигарон хеле зиёдтаранд. Роҳи хеле беҳтари арзёбӣ таҳлили матритсаи иштибоҳҳо (confusion matrix, CM) аст.

Татбиқи санҷиши салибӣ

Баъзан ба идоракунии бештар аз имкониятҳои тайёри Scikit-Learn ниёз доред. Он гоҳ санҷиши салибиро худатон татбиқ мекунед. Ин рамз тақрибан кори cross_val_score()-ро карда, ҳамон натиҷаро медиҳад:

from sklearn.model_selection import StratifiedKFold
from sklearn.base import clone

skfolds = StratifiedKFold(n_splits=3)  # add shuffle=True if the dataset is
                                       # not already shuffled
for train_index, test_index in skfolds.split(X_train, y_train_5):
    clone_clf = clone(sgd_clf)
    X_train_folds = X_train[train_index]
    y_train_folds = y_train_5[train_index]
    X_test_fold = X_train[test_index]
    y_test_fold = y_train_5[test_index]

    clone_clf.fit(X_train_folds, y_train_folds)
    y_pred = clone_clf.predict(X_test_fold)
    n_correct = sum(y_pred == y_test_fold)
    print(n_correct / len(y_pred))  # prints 0.95035, 0.96035, and 0.9604

StratifiedKFold намунагирии қишриро, ки дар боби 2 шарҳ додем, истифода мебарад, то дар ҳар қисм таносуби намояндаи синфҳо нигоҳ дошта шавад. Ҳар давр нусхаи таснифгар сохта мешавад, бо қисмҳои омӯзишӣ омӯзонида шуда, дар қисми санҷишӣ пешгӯӣ мекунад. Баъд шумораи пешгӯиҳои дуруст ҳисоб карда шуда, ҳиссаи онҳо бароварда мешавад.

Матритсаҳои иштибоҳҳо

Андешаи асосӣ ҳисоб кардани шумораи ҳолатҳоест, ки намунаҳои синфи A ҳамчун B тасниф шудаанд, барои ҳамаи ҷуфтҳои A/B. Масалан, барои фаҳмидани он ки чанд бор 8 бо 0 иштибоҳ шудааст, ба сатри 8 ва сутуни 0 нигоҳ мекунед.

Барои сохтани матритса аввал пешгӯиҳо лозиманд, то бо ҳадафҳои воқеӣ муқоиса шаванд. Метавонед маҷмӯи санҷиширо истифода баред, аммо беҳтар аст ҳоло даст нарасонед: он танҳо дар охири лоиҳа, ҳангоми доштани таснифгари омодаи истифода, лозим аст. Ба ҷойи он cross_val_predict()-ро истифода мебарем:

from sklearn.model_selection import cross_val_predict

y_train_pred = cross_val_predict(sgd_clf, X_train, y_train_5, cv=3)

Мисли cross_val_score() он санҷиши салибии k-қисмӣ мекунад, вале ба ҷойи баҳоҳо пешгӯиҳои ҳар қисми санҷиширо бармегардонад. Пас барои ҳар намунаи омӯзишӣ пешгӯии «тоза», яъне берун аз намуна (out-of-sample), мегиред: модел дар додаҳое пешгӯӣ мекунад, ки ҳангоми омӯзиш надидааст.

Акнун ба confusion_matrix() синфҳои воқеӣ (y_train_5) ва пешгӯишуда (y_train_pred)-ро медиҳем:

>>> from sklearn.metrics import confusion_matrix
>>> cm = confusion_matrix(y_train_5, y_train_pred)
>>> cm
array([[53892,   687],
       [ 1891,  3530]])

Ҳар сатр синфи воқеӣ ва ҳар сутун синфи пешгӯишударо ифода мекунад. Сатри аввал тасвирҳои ғайри-5, яъне синфи манфӣ, аст: 53 892-то дуруст ғайри-5 тасниф шудаанд — манфиҳои дуруст (true negatives, TN). 687-тои дигар нодуруст 5 гуфта шудаанд — мусбатҳои бардурӯғ (false positives, FP) ё хатоҳои навъи I.

Сатри дуюм тасвирҳои 5, яъне синфи мусбат, аст: 1 891-то нодуруст ғайри-5 тасниф шудаанд — манфиҳои бардурӯғ (false negatives, FN) ё хатоҳои навъи II. 3 530-тои боқимонда дуруст 5 шинохта шудаанд — мусбатҳои дуруст (true positives, TP).

Таснифгари комил танҳо TP ва TN дорад; қиматҳои ғайрисифрӣ танҳо дар диагонали асосӣ, аз болои чап то поёни рост, ҷой мегиранд:

>>> y_train_perfect_predictions = y_train_5  # pretend we reached perfection
>>> confusion_matrix(y_train_5, y_train_perfect_predictions)
array([[54579,     0],
       [    0,  5421]])

Матритса маълумоти зиёд медиҳад, аммо баъзан меъёри кӯтоҳтар мехоҳед. Яке аз онҳо дурустии пешгӯиҳои мусбат аст, ки дақиқӣ (precision) ном дорад.

Муодилаи 3.1. Дақиқӣ.

precision = TP / (TP + FP).

TP шумораи мусбатҳои дуруст ва FP мусбатҳои бардурӯғ аст.

Акнун моделеро тасаввур кунед, ки танҳо ҳангоми боварии хеле баланд ҷавоби мусбат медиҳад. Ҳолати ифротӣ: ҳамеша ҷавоби манфӣ медиҳад, ғайр аз як намунае, ки ба мусбат буданаш аз ҳама бештар бовар дорад. Агар ҳамон як ҷавоб дуруст бошад, precision = 1/1 = 100% мешавад. Аммо чунин таснифгар чандон муфид нест, зеро ҳамаи намунаҳои мусбатро ғайр аз якто нодида мегирад.

Аз ин рӯ, precision одатан ҳамроҳи фарогирӣ (recall) истифода мешавад, ки онро ҳассосият (sensitivity) ё ҳиссаи мусбатҳои дуруст (true positive rate, TPR) низ меноманд. Он ҳиссаи намунаҳои мусбатест, ки таснифгар дуруст ошкор мекунад.

Муодилаи 3.2. Фарогирӣ.

recall = TP / (TP + FN).

FN шумораи манфиҳои бардурӯғ аст.

Агар матритса бароятон норавшан бошад, расми 3.3 метавонад ёрӣ диҳад.

Матритсаи тасвирии иштибоҳҳо

Расми 3.3. Матритсаи тасвирии иштибоҳҳо: манфиҳои дуруст дар болои чап, мусбатҳои бардурӯғ дар болои рост, манфиҳои бардурӯғ дар поёни чап ва мусбатҳои дуруст дар поёни рост.

Дақиқӣ ва фарогирӣ

Scikit-Learn барои ҳисоб кардани меъёрҳои таснифгар, аз ҷумла precision ва recall, функсияҳо дорад:

>>> from sklearn.metrics import precision_score, recall_score
>>> precision_score(y_train_5, y_train_pred)  # == 3530 / (687 + 3530)
np.float64(0.8370879772350012)
>>> recall_score(y_train_5, y_train_pred)  # == 3530 / (1891 + 3530)
np.float64(0.6511713705958311)

Акнун ошкоркунандаи 5 мисли вақти дидани accuracy он қадар дурахшон нест. Вақте мегӯяд тасвир 5 аст, танҳо дар 83,7 фоизи ҳолатҳо дуруст мебошад. Ғайр аз ин, танҳо 65,1 фоизи ҳамаи 5-ҳоро меёбад.

Аксар вақт якҷо кардани precision ва recall ба баҳои F₁ қулай аст, махсусан барои муқоисаи ду таснифгар бо як адад. F₁ миёнаи гармоникӣ-и онҳост. Миёнаи одӣ ба ҳамаи қиматҳо баробар муносибат мекунад, аммо миёнаи гармоникӣ ба қиматҳои паст вазни бештар медиҳад. Пас F₁ танҳо вақте баланд мешавад, ки ҳам precision ва ҳам recall баланд бошанд.

Муодилаи 3.3. Баҳои F₁.

F₁ = 2 / (1 / precision + 1 / recall) = 2 · (precision · recall) / (precision + recall) = 2TP / (2TP + FP + FN).

Барои ҳисоб f1_score()-ро даъват кунед:

>>> from sklearn.metrics import f1_score
>>> f1_score(y_train_5, y_train_pred)
0.7325171197343846

F₁ таснифгарҳоеро бартар медонад, ки precision ва recall-и наздик доранд. Ин на ҳамеша ҳадафи шумост: баъзан precision ва баъзан recall муҳимтар аст. Масалан, агар таснифгар видеоҳои барои кӯдакон бехатарро интихоб кунад, эҳтимол мехоҳед видеоҳои хуби зиёдеро рад намояд — recall-и паст — аммо танҳо бехатарҳоро гузаронад — precision-и баланд. Таснифгаре, ки recall-и баландтар дорад, вале чанд видеои бисёр бадро роҳ медиҳад, муносибтар нест. Ҳатто метавонед санҷиши интихоби видеоҳоро ба одамон ҳам супоред.

Аз тарафи дигар, барои ёфтани дуздони мағоза дар тасвирҳои назоратӣ шояд precision-и 30-фоиза ҳам қобили қабул бошад, агар recall ба 99 фоиз расад. Посбонон чанд огоҳии бардурӯғ мегиранд, аммо қариб ҳамаи дуздон ошкор мешаванд. Ба ҳамин монанд, дар ташхиси тиббӣ одатан recall-и баланд лозим аст, то ягон чизи муҳим аз назар наафтад. Мусбатҳои бардурӯғро бо санҷишҳои минбаъдаи тиббӣ рад кардан мумкин аст.

Мутаассифона, ҳар дуро ҳамзамон ба осонӣ баланд карда наметавонед: зиёд кардани precision одатан recall-ро паст мекунад ва баръакс. Ин созиши дақиқӣ ва фарогирӣ (precision/recall trade-off) ном дорад.

Созиши дақиқӣ ва фарогирӣ

Барои фаҳмидани ин созиш мебинем, ки SGDClassifier чӣ гуна қарор мегирад. Барои ҳар намуна бо функсияи қарор (decision function) баҳо ҳисоб мекунад. Агар он аз ҳадди қарор (threshold) баланд бошад, намуна мусбат ва дар акси ҳол манфӣ мешавад. Дар расми 3.4 чанд рақам аз баҳои пасттарин дар чап то баландтарин дар рост ҷойгир шудааст.

Фарз мекунем ҳад дар тирчаи миёна, байни ду 5, аст. Дар рост чор мусбати дуруст — 5-ҳои воқеӣ — ва як мусбати бардурӯғ — дар асл 6 — ҳаст. Precision ба 80 фоиз, яъне 4 аз 5, баробар аст. Аммо аз шаш 5-и воқеӣ танҳо чорто ёфт шудааст, пас recall тақрибан 67 фоиз, яъне 4 аз 6, мебошад. Агар ҳадро ба тирчаи рост баланд кунед, 6 аз мусбати бардурӯғ ба манфии дуруст табдил меёбад ва precision то 100 фоиз мерасад. Аммо як мусбати дуруст манфии бардурӯғ мешавад ва recall то 50 фоиз паст мегардад. Баръакс, паст кардани ҳад recall-ро зиёд ва precision-ро кам мекунад.

Созиши дақиқӣ ва фарогирӣ

Расми 3.4. Созиши precision/recall: тасвирҳо аз рӯйи баҳои таснифгар мураттабанд ва онҳое, ки аз ҳадди интихобшуда болотаранд, мусбат ҳисоб мешаванд. Ҳар қадар ҳад баландтар бошад, recall пасттар ва одатан precision баландтар мешавад.

Ба ҷойи predict() метавонед decision_function()-ро даъват кунед, то барои ҳар намуна баҳо гиред. Баъд аз рӯйи он ҳар ҳадди дилхоҳро истифода баред:

>>> y_scores = sgd_clf.decision_function([some_digit])
>>> y_scores
array([2164.22030239])
>>> threshold = 0
>>> y_some_digit_pred = (y_scores > threshold)
>>> y_some_digit_pred
array([ True])

SGDClassifier ҳадди 0 дорад, пас натиҷа бо predict() яксон аст: True. Ҳадро баланд мекунем:

>>> threshold = 3000
>>> y_some_digit_pred = (y_scores > threshold)
>>> y_some_digit_pred
array([False])

Ин коҳиши recall-ро тасдиқ мекунад: тасвир воқеан 5 аст ва ҳангоми ҳадди 0 ёфт мешавад, аммо дар 3 000 аз назар меафтад.

Ҳадро чӣ гуна интихоб мекунед? Як роҳ гирифтани баҳои ҳамаи намунаҳои омӯзишӣ бо cross_val_predict() аст. Ин бор мегӯем ба ҷойи пешгӯӣ баҳои қарорро баргардонад:

y_scores = cross_val_predict(sgd_clf, X_train, y_train_5, cv=3,
                             method="decision_function")

Бо precision_recall_curve() precision ва recall-ро барои ҳамаи ҳадҳои имконпазир ҳисоб мекунем. Функсия дар охир precision-и 1 ва recall-и 0 илова мекунад, ки ба ҳадди беохир мувофиқанд:

from sklearn.metrics import precision_recall_curve

precisions, recalls, thresholds = precision_recall_curve(y_train_5, y_scores)

Баъд бо Matplotlib онҳоро ҳамчун функсияи ҳад мекашем (расми 3.5). Ҳадди интихобкардаи 3 000-ро ҳам нишон медиҳем:

plt.plot(thresholds, precisions[:-1], "b--", label="Precision", linewidth=2)
plt.plot(thresholds, recalls[:-1], "g-", label="Recall", linewidth=2)
plt.vlines(threshold, 0, 1.0, "k", "dotted", label="threshold")
[...]  # beautify the figure: add grid, legend, axis, labels, and circles
plt.show()

Дақиқӣ ва фарогирӣ нисбат ба ҳад

Расми 3.5. Дақиқӣ ва фарогирӣ нисбат ба ҳадди қарор.

Эзоҳ. Шояд пурсед, ки чаро каҷи precision дар расми 3.5 аз recall ноҳамвортар аст. Сабаб: бо баланд кардани ҳад precision баъзан паст ҳам мешавад, ҳарчанд одатан меафзояд. Дар расми 3.4 ҳадро аз миёна танҳо як рақам ба рост баред: precision аз 4/5, яъне 80 фоиз, ба 3/4, яъне 75 фоиз, паст мешавад. Аммо recall ҳангоми баланд кардани ҳад танҳо метавонад кам шавад; бинобар ин каҷаш ҳамвортар аст.

Дар ин ҳад precision наздики 90 фоиз ва recall тақрибан 50 фоиз аст. Роҳи дигари интихоби созиши муносиб кашидани precision бевосита нисбат ба recall мебошад (расми 3.6). Ҳамон ҳад нишон дода шудааст:

plt.plot(recalls, precisions, linewidth=2, label="Precision/Recall curve")
[...]  # beautify the figure: add labels, grid, legend, arrow, and text
plt.show()

Дақиқӣ нисбат ба фарогирӣ

Расми 3.6. Дақиқӣ нисбат ба фарогирӣ.

Мебинед, ки дар recall-и тақрибан 80-фоиза precision босуръат паст шудан мегирад. Эҳтимол нуқтаи пеш аз ин пастравӣ, масалан recall-и 60-фоизаро интихоб мекунед. Аммо интихоб ба лоиҳа вобаста аст.

Фарз мекунем ҳадаф precision-и 90-фоиза аст. Аз графики аввал ҳадро ёфтан мумкин аст, аммо чандон дақиқ нест. Метавон хурдтарин ҳаддеро ҷуст, ки ҳадди ақал 90 фоиз precision медиҳад. argmax()-и NumPy индекси аввали қимати бузургтаринро медиҳад: дар ин ҳолат аввалин True-ро:

>>> idx_for_90_precision = (precisions >= 0.90).argmax()
>>> threshold_for_90_precision = thresholds[idx_for_90_precision]
>>> threshold_for_90_precision
np.float64(3370.0194991439557)

Барои пешгӯӣ, ҳоло дар маҷмӯи омӯзишӣ, ба ҷойи predict() чунин мекунем:

y_train_pred_90 = (y_scores >= threshold_for_90_precision)

Precision ва recall-ро месанҷем:

>>> precision_score(y_train_5, y_train_pred_90)
0.9000345901072293
>>> recall_at_90_precision = recall_score(y_train_5, y_train_pred_90)
>>> recall_at_90_precision
0.4799852425751706

Аъло, таснифгари дорои precision-и 90-фоиза доред! Сохтани таснифгар бо қариб ҳар precision-и дилхоҳ осон менамояд: ҳадро ба қадри кофӣ баланд гузоред. Аммо шитоб накунед: precision-и баланд бо recall-и хеле паст чандон муфид нест. Барои бисёр барномаҳо recall-и 48-фоиза хуб нест.

Маслиҳат. Агар касе гӯяд: «Ба precision-и 99-фоиза расем», пурсед: «Бо кадом recall?»

Аз версияи 1.5-и Scikit-Learn ду синфи нав барои осонтар танзим кардани ҳад ҳаст:

  • FixedThresholdClassifier таснифгари дуиро дар бар гирифта, имкон медиҳад ҳадро дастӣ муайян кунед. Агар модели асосӣ predict_proba() дошта бошад, ҳад байни 0 ва 1 аст, бо пешфарзи 0,5. Дар акси ҳол ҳад баҳои қарорест, ки бо баромади decision_function() муқоиса мешавад, бо пешфарзи 0.
  • TunedThresholdClassifierCV бо санҷиши салибии k-қисмӣ беҳтарин ҳадро барои меъёри додашуда худкор меёбад. Пешфарз дурустии мутавозин (balanced accuracy) — миёнаи recall-и ҳар синфро бештар мекунад. Метавон меъёри дигар интихоб кард; рӯйхати пурра дар ҳуҷҷатгузорӣ аст.

Каҷи ROC

Каҷи хусусияти амалии қабулкунанда (receiver operating characteristic, ROC) воситаи дигари маъмули таснифгарҳои дуист. Он ба каҷи precision/recall монанд аст, аммо ҳиссаи мусбатҳои дуруст (TPR), яъне recall-ро, нисбат ба ҳиссаи мусбатҳои бардурӯғ (FPR) мекашад.

FPR, ки fall-out ҳам ном дорад, ҳиссаи намунаҳои манфиест, ки нодуруст мусбат шудаанд. Он ба 1 − TNR баробар аст. TNR ҳиссаи манфиҳои дуруст, яъне намунаҳои манфиест, ки дуруст манфӣ шинохта шудаанд. TNR-ро махсусият (specificity) низ меноманд. Пас ROC ҳассосият ё recall-ро нисбат ба 1 − махсусият нишон медиҳад.

Барои кашидани ROC аввал бо roc_curve() TPR ва FPR-ро дар ҳадҳои гуногун ҳисоб кунед:

from sklearn.metrics import roc_curve

fpr, tpr, thresholds = roc_curve(y_train_5, y_scores)

Баъд бо Matplotlib TPR-ро нисбат ба FPR мекашем. Рамз расми 3.7-ро месозад. Барои нуқтаи precision-и 90-фоиза индекси ҳадди дилхоҳро меёбем. Азбаски ҳадҳо ин ҷо камшавандаанд, дар сатри аввал =:

idx_for_threshold_at_90 = (thresholds <= threshold_for_90_precision).argmax()
tpr_90, fpr_90 = tpr[idx_for_threshold_at_90], fpr[idx_for_threshold_at_90]

plt.plot(fpr, tpr, linewidth=2, label="ROC curve")
plt.plot([0, 1], [0, 1], 'k:', label="Random classifier's ROC curve")
plt.plot([fpr_90], [tpr_90], "ko", label="Threshold for 90% precision")
[...]  # beautify the figure: add labels, grid, legend, arrow, and text
plt.show()

Боз созиш вуҷуд дорад: recall ё TPR ҳар қадар баланд бошад, FPR низ бештар мешавад. Хати нуқтадор ROC-и таснифгари комилан тасодуфист. Таснифгари хуб аз он ҳарчи дуртар, ба сӯйи кунҷи болои чап, ҷой мегирад.

Яке аз роҳҳои муқоиса чен кардани масоҳати зери каҷ (AUC) аст. ROC AUC-и таснифгари комил 1 ва тасодуфӣ 0,5 мебошад. Scikit-Learn барои тахмини он функсия дорад:

>>> from sklearn.metrics import roc_auc_score
>>> roc_auc_score(y_train_5, y_scores)
np.float64(0.9604938554008616)

Каҷи ROC

Расми 3.7. Каҷи ROC барои ҳамаи ҳадҳои имконпазир: FPR дар муқобили TPR. Доираи сиёҳ нуқтаи интихобшударо бо precision-и 90-фоиза ва recall-и 48-фоиза нишон медиҳад.

Маслиҳат. Азбаски ROC ва precision/recall ё PR монанданд, шояд интихоб душвор намояд. Қоидаи умумӣ: вақте синфи мусбат камёб аст ё мусбатҳои бардурӯғ аз манфиҳои бардурӯғ бештар бароятон муҳиманд, PR-ро афзал донед; вагарна ROC-ро истифода баред. Масалан, ROC-и боло ва ROC AUC таснифгарро хеле хуб нишон медиҳанд, аммо ин асосан аз кам будани 5-ҳо нисбат ба ғайри-5-ҳост. PR равшан мекунад, ки ҷойи беҳбудӣ ҳаст: каҷ метавонист ба кунҷи болои рост наздиктар бошад. Боз расми 3.6-ро бинед.

Акнун RandomForestClassifier месозем, то PR ва F₁-и онро бо SGDClassifier муқоиса кунем:

from sklearn.ensemble import RandomForestClassifier

forest_clf = RandomForestClassifier(random_state=42)

precision_recall_curve() барои ҳар намуна барчасп ва баҳо мехоҳад. Пас ҷангали тасодуфиро омӯзонида, баҳо мегирем. Аммо RandomForestClassifier бинобар тарзи кораш decision_function() надорад; инро дар боби 6 мебинем. Хушбахтона, predict_proba() дорад, ки эҳтимоли тахминии синфҳоро медиҳад. Эҳтимоли синфи мусбатро ҳамчун баҳо истифода мебарем ва precision_recall_curve() кор мекунад. Бо cross_val_predict() омӯзиш ва пешгӯии эҳтимолҳои ҳар тасвирро иҷро мекунем:

y_probas_forest = cross_val_predict(forest_clf, X_train, y_train_5, cv=3,
                                    method="predict_proba")

Эҳтимолҳои ду тасвири аввали омӯзишӣ:

>>> y_probas_forest[:2]
array([[0.11, 0.89],
       [0.99, 0.01]])

Модел аввалиро бо эҳтимоли 89 фоиз мусбат ва дуюмиро бо эҳтимоли 99 фоиз манфӣ мешуморад. Азбаски ҳар тасвир мусбат ё манфӣ аст, эҳтимолҳои ҳар сатр ҷамъ шуда 100 фоиз мешаванд.

Огоҳӣ. Инҳо эҳтимолҳои тахминӣ мебошанд, на эҳтимолҳои воқеӣ. Масалан, аз тасвирҳое, ки модел эҳтимоли мусбат буданашонро 50–60 фоиз тахмин кардааст, тақрибан 94 фоиз воқеан мусбатанд. Пас дар ин ҳолат модел эҳтимолро хеле паст тахмин кардааст, аммо моделҳо метавонанд аз ҳад боварнок ҳам бошанд. CalibratedClassifierCV аз sklearn.calibration бо санҷиши салибӣ эҳтимолҳоро калибровка карда, ба воқеият наздиктар мекунад. Мисоли рамз дар дафтар аст. Ин барои ташхиси тиббӣ, арзёбии хавфи молиявӣ ё ошкорсозии қаллобӣ муҳим мебошад.

Сутуни дуюм эҳтимоли синфи мусбатро дорад; онро ба precision_recall_curve() медиҳем:

y_scores_forest = y_probas_forest[:, 1]
precisions_forest, recalls_forest, thresholds_forest = precision_recall_curve(
    y_train_5, y_scores_forest)

Акнун PR-ро мекашем. Барои муқоиса каҷи аввалро ҳам нишон медиҳем (расми 3.8):

plt.plot(recalls_forest, precisions_forest, "b-", linewidth=2,
         label="Random Forest")
plt.plot(recalls, precisions, "--", linewidth=2, label="SGD")
[...]  # beautify the figure: add labels, grid, and legend
plt.show()

Каҷҳои PR-и ҷангали тасодуфӣ ва SGD

Расми 3.8. Муқоисаи каҷҳои PR: ҷангали тасодуфӣ аз таснифгари SGD беҳтар аст, зеро каҷаш ба кунҷи болои рост наздиктар ва масоҳати зери он калонтар мебошад.

PR-и RandomForestClassifier воқеан хеле беҳтар аст. Баҳои F₁ ва ROC AUC низ хеле баландтаранд:

>>> y_train_pred_forest = y_probas_forest[:, 1] >= 0.5  # positive proba ≥ 50%
>>> f1_score(y_train_5, y_train_pred_forest)
0.9274509803921569
>>> roc_auc_score(y_train_5, y_scores_forest)
0.9983436731328145

Precision ва recall-ро худатон чен кунед: бояд тақрибан 99,0 фоиз precision ва 87,3 фоиз recall гиред. Бад нест!

Акнун медонед, ки таснифгари дуиро чӣ гуна омӯзонед, меъёри мувофиқ интихоб кунед, бо санҷиши салибӣ арзёбӣ намоед, созиши precision/recall-ро ба ниёз мутобиқ созед ва моделҳоро бо чанд меъёру каҷ муқоиса кунед. Барои ошкор кардани рақамҳои дигар ҳам омодаед.

Таснифбандии чандсинфӣ

Таснифгарҳои дуӣ ду синфро фарқ мекунанд, аммо таснифгарҳои чандсинфӣ (multiclass classifiers), ки multinomial ҳам ном доранд, бештар аз ду синфро фарқ карда метавонанд.

Баъзе таснифгарҳои Scikit-Learn, мисли LogisticRegression, RandomForestClassifier ва GaussianNB, чанд синфро мустақиман дастгирӣ мекунанд. Дигарон, мисли SGDClassifier ва SVC, дар асос таснифгарҳои дуӣ мебошанд. Аммо бо якчанд таснифгари дуӣ стратегияҳои гуногуни чандсинфӣ сохтан мумкин аст.

Як роҳ барои таснифи рақамҳои 0–9 омӯзонидани 10 таснифгари дуӣ аст: ошкоркунандаи 0, 1, 2 ва ғайра. Ҳангоми таснифи тасвир аз ҳар кадом баҳои қарор гирифта, синфи дорои баҳои баландтаринро интихоб мекунед. Ин «як бар зидди боқимондаҳо» (one-versus-the-rest, OvR) ё баъзан «як бар зидди ҳама» (one-versus-all, OvA) ном дорад.

Стратегияи дигар барои ҳар ҷуфти рақам таснифгар меомӯзонад: 0 бо 1, 0 бо 2, 1 бо 2 ва ғайра. Ин «як бар зидди як» (one-versus-one, OvO) аст. Барои N синф N × (N − 1) / 2 таснифгар лозим мешавад. Барои MNIST ин 45 таснифгари дуист! Ҳангоми тасниф тасвир аз ҳамаи 45 таснифгар гузашта, синфе интихоб мешавад, ки бештар дар муқоисаҳои ҷуфтӣ пирӯз шудааст. Бартарии асосӣ ин аст, ки ҳар таснифгар танҳо бо намунаҳои ду синфи дахлдор омӯзонида мешавад.

Баъзе алгоритмҳо, мисли таснифгарҳои мошини векторҳои такягоҳӣ, бо калон шудани маҷмӯа хуб васеъ намешаванд. Барои онҳо OvO афзал аст, зеро омӯзонидани моделҳои зиёди хурд аз чанд модели калон тезтар аст. Аммо барои аксари алгоритмҳои дуӣ OvR беҳтар аст.

Scikit-Learn истифодаи алгоритми дуиро барои масъалаи чандсинфӣ мешиносад ва вобаста ба алгоритм OvR ё OvO-ро худкор иҷро мекунад. Бо sklearn.svm.SVC месанҷем. Боби онлайнии SVM дар homl.info аст. Барои зудтар шудан танҳо бо 2 000 тасвири аввал меомӯзонем:

from sklearn.svm import SVC

svm_clf = SVC(random_state=42)
svm_clf.fit(X_train[:2000], y_train[:2000])  # y_train, not y_train_5

Осон буд! Ба ҷойи ҳадафҳои «5 ё боқимондаҳо» — y_train_5 — синфҳои аслии 0–9, яъне y_train-ро додем. Азбаски 10 синф дорем, Scikit-Learn OvO-ро истифода бурда, 45 таснифгари дуӣ омӯзонд. Пешгӯӣ мекунем:

>>> svm_clf.predict([some_digit])
array(['5'], dtype=object)

Дуруст! Дар асл 45 пешгӯӣ, барои ҳар ҷуфти синф якто, иҷро шуда, синфи дорои пирӯзиҳои бештар интихоб шуд. decision_function() барои ҳар намуна 10 баҳо, якто барои ҳар синф, медиҳад. Баҳои ҳар синф шумораи муқоисаҳои бурдааш аст, бо ислоҳи хурди мусбат ё манфӣ, ҳадди аксар ±0,33, аз рӯйи баҳои таснифгарҳо барои ҳал кардани баробарӣ:

>>> some_digit_scores = svm_clf.decision_function([some_digit])
>>> some_digit_scores.round(2)
array([[ 3.79,  0.73,  6.06,  8.3 , -0.29,  9.3 ,  1.75,  2.77,  7.21,
         4.82]])

Баландтарин баҳо 9,3 аст ва воқеан ба синфи 5 мувофиқ меояд:

>>> class_id = some_digit_scores.argmax()
>>> class_id
np.int64(5)

Таснифгари омӯзонидашуда рӯйхати синфҳои ҳадафро дар classes_ бо тартиби қимат нигоҳ медорад. Дар MNIST индекси ҳар синф хушбахтона ба худи он мувофиқ аст: масалан, дар индекси 5 синфи '5' ҷойгир мебошад. Аммо умуман чунин нест ва бояд барчаспро чунин ёбед:

>>> svm_clf.classes_
array(['0', '1', '2', '3', '4', '5', '6', '7', '8', '9'], dtype=object)
>>> svm_clf.classes_[class_id]
'5'

Агар хоҳед Scikit-Learn ҳатман OvO ё OvR истифода барад, синфҳои OneVsOneClassifier ё OneVsRestClassifier-ро гиред. Объект сохта, ба созанда таснифгар диҳед; ҳатто шарт нест, ки он дуӣ бошад. Масалан, ин рамз дар асоси SVC таснифгари чандсинфии OvR месозад:

from sklearn.multiclass import OneVsRestClassifier

ovr_clf = OneVsRestClassifier(SVC(random_state=42))
ovr_clf.fit(X_train[:2000], y_train[:2000])

Пешгӯӣ карда, шумораи таснифгарҳои омӯзонидашударо месанҷем:

>>> ovr_clf.predict([some_digit])
array(['5'], dtype='<U1')
>>> len(ovr_clf.estimators_)
10

Омӯзонидани SGDClassifier бо маҷмӯи чандсинфӣ ва истифодаи он низ осон аст:

>>> sgd_clf = SGDClassifier(random_state=42)
>>> sgd_clf.fit(X_train, y_train)
>>> sgd_clf.predict([some_digit])
array(['3'], dtype='<U1')

Ин нодуруст аст. Хатои пешгӯӣ рӯй медиҳад! Ин бор Scikit-Learn дар дохил OvR истифода бурда, барои 10 синф 10 таснифгари дуӣ омӯзонд. decision_function() акнун барои ҳар синф як қимат медиҳад. Баҳоҳоро мебинем:

>>> sgd_clf.decision_function([some_digit]).round()
array([[-31893., -34420.,  -9531.,   1824., -22320.,  -1386., -26189.,
        -16148.,  -4604., -12051.]])

Мебинед, ки модел ба пешгӯӣ чандон бовар надорад: қариб ҳамаи баҳоҳо сахт манфӣ ҳастанд, аммо синфи 3 баҳои +1 824 ва синфи 5 баҳои начандон дури −1 386 дорад. Албатта, моделро бояд дар бештар аз як тасвир арзёбӣ кунед. Азбаски шумораи тасвирҳои синфҳо тақрибан баробар аст, accuracy мувофиқ мебошад. Мисли ҳамеша cross_val_score()-ро истифода мебарем:

>>> cross_val_score(sgd_clf, X_train, y_train, cv=3, scoring="accuracy")
array([0.87365, 0.85835, 0.8689 ])

Дар ҳамаи қисмҳо бештар аз 85,8 фоиз мегирад. Таснифгари тасодуфӣ тақрибан 10 фоиз медод, пас бад нест, аммо хеле беҳтар кардан мумкин аст. Танҳо миқёсбандии вурудҳо, ки дар боби 2 дидем, дурустиро аз 89,1 фоиз болотар мебарад:

>>> from sklearn.preprocessing import StandardScaler
>>> scaler = StandardScaler()
>>> X_train_scaled = scaler.fit_transform(X_train.astype("float64"))
>>> cross_val_score(sgd_clf, X_train_scaled, y_train, cv=3, scoring="accuracy")
array([0.8983, 0.891 , 0.9018])

Таҳлили хатоҳо

Агар ин лоиҳаи воқеӣ мебуд, акнун рӯйхати санҷиши лоиҳа-ро пайгирӣ мекардед: роҳҳои омодасозии додаҳоро меомӯхтед, моделҳои гуногунро санҷида, беҳтаринҳоро интихоб ва гиперпараметрҳояшонро бо GridSearchCV танзим мекардед ва то ҳадди имкон корро худкор месохтед. Дар ин ҷо фарз мекунем модели умедбахш ёфтаед ва роҳи беҳтар кардани онро меҷӯед. Яке аз роҳҳо таҳлили навъи хатоҳои он аст.

Аввал матритсаи иштибоҳҳоро бинед. Бо cross_val_predict() пешгӯӣ карда, мисли пеш ба confusion_matrix() барчаспҳо ва пешгӯиҳоро медиҳед. Аммо акнун ба ҷойи 2 синф 10 синф ҳаст, бинобар ин рақамҳо зиёд ва хондан душвор мешавад.

Диаграммаи ранга хеле фаҳмотар аст. Онро бо ConfusionMatrixDisplay.from_predictions() мекашем:

from sklearn.metrics import ConfusionMatrixDisplay

y_train_pred = cross_val_predict(sgd_clf, X_train_scaled, y_train, cv=3)
ConfusionMatrixDisplay.from_predictions(y_train, y_train_pred)
plt.show()

Диаграммаи чапи расми 3.9 ҳосил мешавад. Матритса хуб менамояд: аксари тасвирҳо дар диагонали асосӣ ҳастанд, яъне дуруст тасниф шудаанд. Ҳуҷайраи диагоналии сатр ва сутуни 5 аз дигарон каме ториктар аст. Шояд модел дар 5 бештар хато кардааст ё дар маҷмӯа 5-ҳо камтаранд.

Аз ин рӯ, матритсаро нормализатсия кардан муҳим аст: ҳар қиматро ба шумораи ҳамаи тасвирҳои синфи воқеии дахлдор, яъне ҷамъи сатр, тақсим мекунем. Барои ин normalize="true" кофист. Бо values_format=".0%" фоизҳоро бе рақамҳои даҳӣ нишон медиҳем. Ин рамз диаграммаи рости расми 3.9-ро месозад:

ConfusionMatrixDisplay.from_predictions(y_train, y_train_pred,
                                        normalize="true", values_format=".0%")
plt.show()

Матритсаи иштибоҳҳо

Расми 3.9. Матритсаи иштибоҳҳо дар чап ва ҳамон матритсаи аз рӯйи сатр нормализатсияшуда дар рост.

Акнун равшан аст, ки танҳо 82 фоизи тасвирҳои 5 дуруст тасниф шудаанд. Хатои маъмултарин 5-ро 8 шуморидан аст: 10 фоизи ҳамаи 5-ҳо чунин шудаанд. Аммо танҳо 2 фоизи 8-ҳо 5 гуфта шудаанд. Матритсаи иштибоҳҳо одатан симметрӣ нест!

Агар бодиққат бинед, рақамҳои зиёд нодуруст 8 шудаанд, вале дар диаграмма ин фавран равшан нест. Барои барҷастатар кардани хатоҳо ба пешгӯиҳои дуруст вазни сифр медиҳем. Рамзи зерин диаграммаи чапи расми 3.10-ро месозад:

sample_weight = (y_train_pred != y_train)
ConfusionMatrixDisplay.from_predictions(y_train, y_train_pred,
                                        sample_weight=sample_weight,
                                        normalize="true", values_format=".0%")
plt.show()

Матритсаи хатоҳо

Расми 3.10. Матритсаи танҳо хатоҳо: нормализатсия аз рӯйи сатр дар чап ва аз рӯйи сутун дар рост.

Акнун навъи хатоҳо равшантар аст. Сутуни 8 хеле равшан шудааст: тасвирҳои зиёд нодуруст 8 гуфта шудаанд. Дар асл, барои қариб ҳамаи синфҳо ин хатои маъмултарин аст. Аммо фоизҳоро бо эҳтиёт фаҳмед: пешгӯиҳои дуруст хориҷ шудаанд. Масалан, 36 фоизи сатри 7 ва сутуни 9 дар диаграммаи чап маънои онро надорад, ки 36 фоизи ҳамаи 7-ҳо 9 шудаанд. Он мегӯяд, ки 36 фоизи хатоҳои модел дар 7-ҳо ба нодуруст 9 шуморидани онҳо рост меояд. Дар асл танҳо 3 фоизи ҳамаи 7-ҳо 9 шудаанд; ин дар диаграммаи рости расми 3.9 дида мешавад.

Матритсаро аз рӯйи сутун ҳам нормализатсия кардан мумкин аст: бо normalize="pred" диаграммаи рости расми 3.10 ҳосил мешавад. Масалан, аз тасвирҳое, ки нодуруст 7 пешгӯӣ шудаанд, 56 фоиз дар асл 9 мебошанд.

Таҳлили матритса роҳҳои беҳтарсозиро нишон медиҳад. Аз ин графикҳо бармеояд, ки бояд 8-ҳои бардурӯғро кам кунед. Метавонед барои рақамҳои ба 8 монанд, вале ғайри-8, додаҳои бештар гиред, то модел фарқ карданро омӯзад. Ё хусусиятҳои нав созед: масалан, алгоритми ҳисобкунии ҳалқаҳои баста, зеро 8 ду ҳалқа, 6 як ва 5 ягон ҳалқа надорад. Ё бо Scikit-Image, Pillow ё OpenCV тасвирҳоро пешкоркард кунед, то қолабҳое мисли ҳалқаҳои баста равшантар шаванд.

Таҳлили хатоҳои алоҳида низ нишон медиҳад, ки таснифгар чӣ мекунад ва чаро ноком мешавад. Масалан, намунаҳои 3 ва 5-ро мисли матритсаи иштибоҳҳо мекашем (расми 3.11):

cl_a, cl_b = '3', '5'
X_aa = X_train[(y_train == cl_a) & (y_train_pred == cl_a)]
X_ab = X_train[(y_train == cl_a) & (y_train_pred == cl_b)]
X_ba = X_train[(y_train == cl_b) & (y_train_pred == cl_a)]
X_bb = X_train[(y_train == cl_b) & (y_train_pred == cl_b)]
[...]  # plot all images in X_aa, X_ab, X_ba, X_bb in a confusion matrix style

Тасвирҳои 3 ва 5

Расми 3.11. Чанд тасвири 3 ва 5 бо тартиби матритсаи иштибоҳҳо.

Баъзе рақамҳои нодуруст таснифшуда дар блокҳои поёни чап ва болои рост чунон бад навишта шудаанд, ки ҳатто одам душвор мешиносад. Аммо аксари хатоҳо барои мо ошкоранд. Шояд фаҳмидани сабаб душвор бошад, вале мағзи инсон системаи аълои шинохти қонуниятҳост ва системаи биноии мо пеш аз расидани маълумот ба шуури мо пешкоркарди мураккаб мекунад. Сода эҳсос шудани вазифа маънои сода будани онро надорад.

Мо SGDClassifier-и сода — модели хаттӣ — истифода кардем. Он барои ҳар синф ба ҳар пиксел вазн медиҳад ва ҳангоми дидани тасвири нав ҷамъи вазндори шиддати пикселҳоро ҳисоб карда, баҳои синфҳоро мегирад. Азбаски 3 ва 5 танҳо бо чанд пиксел фарқ мекунанд, модел онҳоро осон омехта мекунад.

Фарқи асосӣ ҷойи хати хурдест, ки хати болоиро ба камони поён мепайвандад. Агар дар 3 ин пайвандро каме ба чап кашед, таснифгар шояд 5 гӯяд ва баръакс. Яъне модел ба ҷойивазкунӣ ва тобдиҳии тасвир ҳассос аст. Барои кам кардани иштибоҳи 3/5 метавон пешкоркард карда, рақамҳоро хуб марказонид ва тобхӯриро кам намуд. Аммо ин осон нест, зеро кунҷи дурусти ҳар тасвирро пешгӯӣ кардан лозим аст.

Роҳи хеле осонтар илова кардани нусхаҳои каме ҷойивазшуда ва тобдодашудаи тасвирҳои омӯзишӣ мебошад. Ин моделро маҷбур мекунад ба чунин тағйирот таҳаммулпазиртар шавад. Он афзоишдиҳии додаҳо (data augmentation) ном дорад. Онро дар боби 12 мебинем.

Таснифбандии чандбарчаспӣ

То ин ҷо ҳар намуна танҳо ба як синф мансуб мешуд. Аммо баъзан мехоҳед барои ҳар намуна якчанд синф барояд. Таснифгари шинохти чеҳраро тасаввур кунед: агар дар як акс чанд нафарро шиносад, чӣ мекунад? Барои ҳар шахси шинохташуда як барчасп медиҳад. Масалан, модел Алиса, Боб ва Чарлиро мешиносад. Барои акси Алиса ва Чарли бояд [True, False, True] диҳад: «Алиса ҳаст, Боб нест, Чарли ҳаст». Системаи дорои чанд барчаспи дуӣ системаи таснифбандии чандбарчаспӣ (multilabel classification) ном дорад.

Ҳоло ба шинохти чеҳра намегузарем ва танҳо мисоли содатар мебинем:

import numpy as np
from sklearn.neighbors import KNeighborsClassifier

y_train_large = (y_train >= '7')
y_train_odd = (y_train.astype('int8') % 2 == 1)
y_multilabel = np.c_[y_train_large, y_train_odd]

knn_clf = KNeighborsClassifier()
knn_clf.fit(X_train, y_multilabel)

Рамз массиви y_multilabel месозад, ки барои ҳар тасвири рақам ду барчаспи ҳадаф дорад: аввалӣ мегӯяд, ки рақам калон — 7, 8 ё 9 — аст ё не; дуюмӣ тоқ буданро нишон медиҳад. Баъд KNeighborsClassifier месозад, ки чандбарчаспиро дастгирӣ мекунад; на ҳамаи таснифгарҳо чунинанд. Моделро бо массиви чандҳадафа меомӯзонад. Пешгӯӣ ду барчасп медиҳад:

>>> knn_clf.predict([some_digit])
array([[False,  True]])

Дуруст! 5 калон нест — False — ва тоқ аст — True.

Роҳҳои арзёбии таснифгари чандбарчаспӣ зиёданд ва меъёри дуруст ба лоиҳа вобаста аст. Як роҳ ҳисоб кардани F₁ ё меъёри дигари дуӣ барои ҳар барчасп ва баъд миёнагирӣ мебошад. Ин рамз миёнаи F₁-и ҳамаи барчаспҳоро ҳисоб мекунад:

>>> y_train_knn_pred = cross_val_predict(knn_clf, X_train, y_multilabel, cv=3)
>>> f1_score(y_multilabel, y_train_knn_pred, average="macro")
0.976410265560605

Ин равиш фарз мекунад, ки ҳамаи барчаспҳо аҳаммияти баробар доранд, аммо шояд чунин набошад. Масалан, агар аксҳои Алиса аз Боб ё Чарли хеле зиёдтар бошанд, шояд ба натиҷаи Алиса вазни бештар додан хоҳед. Як роҳи сода вазни ҳар барчаспро ба дастгирии он (support) — шумораи намунаҳои дорои он барчасп — баробар кардан аст. Барои ин ҳангоми f1_score() қимати average="weighted" гузоред.

Агар таснифгар, мисли SVC, чандбарчаспиро мустақиман дастгирӣ накунад, барои ҳар барчасп як модел омӯзонидан мумкин аст. Аммо ин стратегия вобастагии барчаспҳоро душвор мефаҳмад. Масалан, рақами калон — 7, 8 ё 9 — нисбат ба ҷуфт будан ду маротиба бештар эҳтимоли тоқ будан дорад. Аммо модели «тоқ» намедонад модели «калон» чӣ пешгӯӣ кард. Барои ҳал моделҳоро ба занҷир мегузоранд: ҳар модел аз хусусиятҳои вурудӣ ва ҳамаи пешгӯиҳои моделҳои пешинаи занҷир истифода мебарад.

Хушбахтона, Scikit-Learn синфи ClassifierChain дорад, ки ҳамин корро мекунад! Пешфарз ҳангоми омӯзиш барчаспҳои воқеиро вобаста ба мавқеи ҳар модел дар занҷир ба он медиҳад. Аммо бо гузоштани cv санҷиши салибиро истифода бурда, барои ҳар намуна аз ҳар модели омӯзонидашуда пешгӯиҳои «тоза», яъне берун аз намуна, мегирад. Баъд ин пешгӯиҳо барои омӯзонидани моделҳои минбаъдаи занҷир истифода мешаванд. Тартиби моделҳо ба самаранокии ниҳоӣ таъсир карда метавонад.

Ин мисол занҷирро бо санҷиши салибӣ месозад ва меомӯзонад. Мисли пеш барои тезтар шудан танҳо 2 000 тасвири аввалро мегирем:

from sklearn.multioutput import ClassifierChain

chain_clf = ClassifierChain(SVC(), cv=3, random_state=42)
chain_clf.fit(X_train[:2000], y_multilabel[:2000])

Акнун пешгӯӣ мекунем:

>>> chain_clf.predict([some_digit])
array([[0., 1.]])

Таснифбандии чандбаромада

Навъи охирини ин боб таснифбандии чандбаромадаи чандсинфӣ (multioutput–multiclass classification) ё танҳо таснифбандии чандбаромада (multioutput classification) аст. Ин умумисозии чандбарчаспист: ҳар барчасп метавонад чандсинфӣ бошад, яъне бештар аз ду қимат гирад.

Барои мисол системаи тозакунии ғавғои тасвир месозем. Он тасвири ғавғодори рақамро гирифта, умедворем тасвири тоза медиҳад: массиви шиддати пикселҳо мисли MNIST. Баромад чандбарчаспист — барои ҳар пиксел як барчасп — ва ҳар барчасп чандқиматӣ мебошад, зеро шиддат аз 0 то 255 аст. Пас ин системаи чандбаромада аст.

Эзоҳ. Марзи таснифбандӣ ва регрессия баъзан норавшан аст, мисли ҳамин мисол. Метавон гуфт, ки пешгӯии шиддати пиксел ба регрессия наздиктар аст. Ҳамчунин системаҳои чандбаромада танҳо ба таснифбандӣ маҳдуд нестанд: метавон барои ҳар намуна якчанд баромад, ҳам барчаспҳои синфӣ ва ҳам қиматҳои ададӣ, гирифт.

Аввал бо методи integers()-и генератори тасодуфӣ ба шиддати пикселҳои MNIST ғавғо илова карда, маҷмӯаҳои омӯзишӣ ва санҷишӣ месозем. Тасвирҳои ҳадаф нусхаҳои аслиянд:

rng = np.random.default_rng(seed=42)
noise_train = rng.integers(0, 100, (len(X_train), 784))
X_train_mod = X_train + noise_train
noise_test = rng.integers(0, 100, (len(X_test), 784))
X_test_mod = X_test + noise_test
y_train_mod = X_train
y_test_mod = X_test

Ба тасвири аввали маҷмӯи санҷишӣ нигоҳ мекунем (расми 3.12). Бале, ба додаҳои санҷишӣ чашм андохтем: акнун бояд бо норозигӣ абрӯ чин кунед!

Тасвири ғавғодор ва тоза

Расми 3.12. Тасвири ғавғодор дар чап ва тасвири тозаи ҳадаф дар рост.

Дар чап вуруди ғавғодор ва дар рост ҳадафи тоза аст. Акнун таснифгарро омӯзонида, тасвирро тоза мекунем (расми 3.13):

knn_clf = KNeighborsClassifier()
knn_clf.fit(X_train_mod, y_train_mod)
clean_digit = knn_clf.predict([X_test_mod[0]])
plot_digit(clean_digit)
plt.show()

Тасвири тозакардашуда

Расми 3.13. Тасвири тозакардашуда.

Ба ҳадаф ба қадри кофӣ монанд аст! Бо ҳамин шиносоии мо бо таснифбандӣ анҷом меёбад. Акнун медонед, ки барои вазифаҳои таснифбандӣ меъёрҳои хуб ва созиши муносиби precision/recall-ро чӣ гуна интихоб кунед, таснифгарҳоро муқоиса намоед ва умуман барои вазифаҳои гуногун системаҳои хуб созед. Дар бобҳои оянда мефаҳмед, ки ҳамаи моделҳои то ин ҷо истифодакардаатон воқеан чӣ гуна кор мекунанд.

пешина
аз 20
навбатӣ