note_3
Заметка 3. Классификация на примере распознавания рукописных цифр.
курса Введение в машинное обучение.
Шокуров Антон В.
shokurov.anton.v@yandex.ru
http://машинноезрение.рф
Версия 0.13

Аннотация

Вводятся понятие классификации на базе библиотек scikit-learn. Рассмотрены как минимум следующие классификаторы: svm (Support Vector Machine, метод опорных векторов) включая ядровый трюк, MLP (Multi-layer Perceptron, Многослойный перцептрон, нейронные сети), SGD (Stochastic Gradient Descent, Стохастический градиентный спуск) и RidgeClassifier (ridge classification, гребневый классификатор). В процессе изложения показано насколько важную роль играют параметры того или иного метода.

Это предварительная версия! Любые замечания приветсвуются.

Задача классификации является частным случаем общей формулировке задачи машинного обучения показаной в самой первой заметке про регрессии. Так, в задаче лкассификации считается, что регрессия строится для конечного, при этом существенно небольшого колличества чисел. Фактически значения образуют дискретное множество или просто конечное множество объектов. Интерпретация такая. По признакам строится прогноз нечисловой, а элементу из множества. Например, по признакам (где обитает, сколько весит в взослом состоянии, сколько ног и так дале) определить животное. Или как в примере из прошлой заметке определить название цветка ириса.

In [1]:
import numpy as np
import scipy as sp
import matplotlib.pyplot as plt
%matplotlib inline

Линейные модели

Линейные модели как следует из названия предполагают, что данные линейной разделимы -- речь про бинарную классификацию. Так, существует гипер-плоскость такая что по одну сторону будут элементы одного класса, а по другую другого.

In [2]:
from sklearn import linear_model 
In [3]:
X = [[0, 0], [1, 1]] # Две точки на двумерной плоскости.
y = [0, 1] # Им соответсвуют данные классы.

Берем формулу из второй заметки, которую мы минимизируем, и считаем, что сама функция принимает два значения6 -1 и 1. 1 для одного класса, а -1 для другого. Ествественно значения не будут идеальными. Например, значению может быть равно 1.1 или 0.8 для класса 1.

Как и ранее ищится минимум данной фкнции. Но теперь вадно обратить внимание на функцию потерь. В зависимости от того какая будет выбрана будет зависеть и качественый результат по сути. А точнее некотрым из фукнций потерь соответвуют те или иные традиционные методы классификации. Так, логорифмическая ункция потерь даст логистическую регрессию.

In [4]:
# Общий метод градиентного скуска.
# Характеристики зависят от функции потерь.
clf = linear_model.SGDClassifier()
clf.fit( X, y)
Out[4]:
SGDClassifier(alpha=0.0001, average=False, class_weight=None,
              early_stopping=False, epsilon=0.1, eta0=0.0, fit_intercept=True,
              l1_ratio=0.15, learning_rate='optimal', loss='hinge',
              max_iter=1000, n_iter_no_change=5, n_jobs=None, penalty='l2',
              power_t=0.5, random_state=None, shuffle=True, tol=0.001,
              validation_fraction=0.1, verbose=0, warm_start=False)
In [5]:
# Теперь можно предсказывать:
p = [-4,-5] # Проверим класс для данной точки.
clf.predict( [p] ) # В общем случае передает список. Поэтому в квадратных скобках.
Out[5]:
array([0])
In [6]:
# По умолчанию loss равен hinge
clf = linear_model.SGDClassifier(loss='log') # Теперь будет логисчическая регрессия.
clf.fit( X, y)
Out[6]:
SGDClassifier(alpha=0.0001, average=False, class_weight=None,
              early_stopping=False, epsilon=0.1, eta0=0.0, fit_intercept=True,
              l1_ratio=0.15, learning_rate='optimal', loss='log', max_iter=1000,
              n_iter_no_change=5, n_jobs=None, penalty='l2', power_t=0.5,
              random_state=None, shuffle=True, tol=0.001,
              validation_fraction=0.1, verbose=0, warm_start=False)
In [7]:
clf.predict( [[3, 2]] )
Out[7]:
array([1])

Для конкртеных методов есть более оптимальный алгоритм из вычисления.

In [8]:
clf = linear_model.RidgeClassifier()
clf.fit( X, y)
Out[8]:
RidgeClassifier(alpha=1.0, class_weight=None, copy_X=True, fit_intercept=True,
                max_iter=None, normalize=False, random_state=None,
                solver='auto', tol=0.001)

Есть более мощные методы, которые по умолчанию тоже работают как линейные модели, но имеют расширеные верси для работы с кривыми поверхзностями. К таким методам относится, например, метод опроных векторов.

In [9]:
from sklearn import svm # Загружаем базовый модуль.
# Под модули:
# svm -- suport vector machines, метод опорных векторов.
# Содержит разные вариации.
# Отличающиеся в ом числе скоротью работы.
# LinearSVC -- линейный (без ядрового трюка)
In [10]:
clf = svm.LinearSVC() # #SVC() SVC( kernel = 'poly' )
In [11]:
clf.fit(X, y) # Главный метод все моделей это fit.
# т.е. подсроится под данные.
Out[11]:
LinearSVC(C=1.0, class_weight=None, dual=True, fit_intercept=True,
          intercept_scaling=1, loss='squared_hinge', max_iter=1000,
          multi_class='ovr', penalty='l2', random_state=None, tol=0.0001,
          verbose=0)
In [12]:
clf.predict( [p] ) # Более првильное значение "регресии", далее определили класс по знаку.
Out[12]:
array([0])

Разделяющая граница

Для классификаторов при их анализе важно увидеть картинку в целом, а не значения для каждой точки по отдельности.

In [13]:
# Значение исходной функции (без отнесение к классу)
clf.decision_function([p])
Out[13]:
array([-5.76465951])
In [14]:
# строим сетку точек, для которых будет построен прогноз.
# В прошлой заметки это делали более детально.
x = np.linspace(-1, 2, 50)
y = np.linspace(-1, 2, 50)

Xx, Xy = np.meshgrid(x, y)
#Xy.shape, Xx[0].shape
In [15]:
x
Out[15]:
array([-1.        , -0.93877551, -0.87755102, -0.81632653, -0.75510204,
       -0.69387755, -0.63265306, -0.57142857, -0.51020408, -0.44897959,
       -0.3877551 , -0.32653061, -0.26530612, -0.20408163, -0.14285714,
       -0.08163265, -0.02040816,  0.04081633,  0.10204082,  0.16326531,
        0.2244898 ,  0.28571429,  0.34693878,  0.40816327,  0.46938776,
        0.53061224,  0.59183673,  0.65306122,  0.71428571,  0.7755102 ,
        0.83673469,  0.89795918,  0.95918367,  1.02040816,  1.08163265,
        1.14285714,  1.20408163,  1.26530612,  1.32653061,  1.3877551 ,
        1.44897959,  1.51020408,  1.57142857,  1.63265306,  1.69387755,
        1.75510204,  1.81632653,  1.87755102,  1.93877551,  2.        ])
In [16]:
Xx.shape # Сетка 50 на 50 точек.
Out[16]:
(50, 50)
In [17]:
Xx[10, 24] # Х-овая кордината точки с индексом 10, 24.
Out[17]:
0.46938775510204067
In [18]:
Xy[10, 24] # Y-овая кордината точки с индексом 10, 24.
Out[18]:
-0.3877551020408163
In [19]:
p = [1.5, 1.3 ]
ans = clf.decision_function( [p] )
ans[0] # Берем нуденвой элемент из возвращенного списка.
Out[19]:
1.1764538217298743
In [20]:
# Создаем массив/матрицу нужного размера из нулей.
# Нужного это под нашу сетку.
Z = np.zeros( (x.shape[0], y.shape[0]) )
for i in range( x.shape[0] ):
    for j in range( y.shape[0] ):
        #print( [Xx[j][i], Xy[j][i]] ) # Отладочная печать, если надо.
        # Заполняем массив:
        Z[i,j] = clf.decision_function( [[Xx[j][i], Xy[j][i]]] )
Z
Out[20]:
array([[-1.64704991, -1.61103583, -1.57502175, ...,  0.04561177,
         0.08162585,  0.11763992],
       [-1.61103583, -1.57502175, -1.53900767, ...,  0.08162585,
         0.11763992,  0.153654  ],
       [-1.57502175, -1.53900767, -1.50299359, ...,  0.11763992,
         0.153654  ,  0.18966808],
       ...,
       [ 0.04561177,  0.08162585,  0.11763992, ...,  1.73827344,
         1.77428752,  1.8103016 ],
       [ 0.08162585,  0.11763992,  0.153654  , ...,  1.77428752,
         1.8103016 ,  1.84631568],
       [ 0.11763992,  0.153654  ,  0.18966808, ...,  1.8103016 ,
         1.84631568,  1.88232975]])
In [21]:
Z.shape # Проверяем что массив имеет нужный размер.
Out[21]:
(50, 50)
In [22]:
# Хотим построить гистограмму.
# Чтобы например показать, что знчения не сконцентрированы в одном или двух какх то местах...
# И просто увидеть характер их распределения.
plt.hist(Z.flatten()) # flatten напомню превращает двумерный массив/матрицу в одномерный массив/вектор.
Out[22]:
(array([ 55., 155., 255., 355., 455., 405., 355., 255., 155.,  55.]),
 array([-1.64704991, -1.29411194, -0.94117397, -0.58823601, -0.23529804,
         0.11763992,  0.47057789,  0.82351586,  1.17645382,  1.52939179,
         1.88232975]),
 <a list of 10 Patch objects>)
In [23]:
# Теперь можно отобразить значение функционала.
plt.scatter( Xx.flatten(), Xy.flatten(), c=Z.flatten() )
Out[23]:
<matplotlib.collections.PathCollection at 0x7f4a5e693550>

Хотим получить тоже самое но по питоновски...без цикла

По сути зочется построить список точек (их координат) сетки.

In [24]:
# Показано что в даном случае методы ravel и flatten делают одно и тоже.
np.mean(Xx.ravel()==Xx.flatten())
Out[24]:
1.0
In [25]:
# Показано, что метод c_ может взять массивы и создать из них матрицу по-столбцам.
np.c_[[1,2],[-2,-4]]
Out[25]:
array([[ 1, -2],
       [ 2, -4]])
In [26]:
# Показано, что метод r_ может взять массивы и создать из них матрицу по-строкам.
# Получится объединение строк.
np.r_[[1,2],[-2,-4]]
Out[26]:
array([ 1,  2, -2, -4])
In [27]:
Xx.ravel()
Out[27]:
array([-1.        , -0.93877551, -0.87755102, ...,  1.87755102,
        1.93877551,  2.        ])
In [28]:
# Теперь строим список точек. Цикл на не понадобился.
points = np.c_[Xx.ravel(), Xy.ravel()]
points
Out[28]:
array([[-1.        , -1.        ],
       [-0.93877551, -1.        ],
       [-0.87755102, -1.        ],
       ...,
       [ 1.87755102,  2.        ],
       [ 1.93877551,  2.        ],
       [ 2.        ,  2.        ]])
In [29]:
# Массив имеет нужный размер.
points.shape # Нужное колличество точек из двемерной сетки.
Out[29]:
(2500, 2)
In [30]:
# Напомним как вычислять значение в точке.
clf.decision_function( [[0.25, 0.3 ]] )[0]
Out[30]:
-0.14706355025700346
In [31]:
clf.predict( [[0.25, 0.3 ]] )[0]
Out[31]:
0
In [32]:
# Теперь создаем массим значений без цикла.
Z = clf.decision_function( np.c_[Xx.ravel(), Xy.ravel()] )
# Обратно формируем матрицу по одномерному массиву значений.
Z = Z.reshape( Xx.shape )
In [33]:
# Покажем что получили такой же ответ.
plt.scatter( Xx.flatten(), Xy.flatten(), c=Z.flatten() )
Out[33]:
<matplotlib.collections.PathCollection at 0x7f4a5e608ef0>

Линии уровня

Бывает важным не цветную картинку рисовать, а строить линии уровня, т.е. показать где данная двумерная фунция принимает конкретные значения.

In [34]:
#plt.plot( Z, c='r')
# Автоматическое построение.
plt.contour(x, y, Z) # На каждом контуре фкнция принимает конкретное значение.
Out[34]:
<matplotlib.contour.QuadContourSet at 0x7f4a5cd6c2b0>
In [35]:
Z = clf.decision_function( np.c_[Xx.ravel(), Xy.ravel()] ) #predict
# Put the result into a color plot
Z = Z.reshape( Xx.shape )
plt.contour(x, y, Z)
Out[35]:
<matplotlib.contour.QuadContourSet at 0x7f4a5cd56400>

Классификация на примере изображений рукописных цифр.

Изоражение рукописных цифр имеет фисированный рамер. изображение черное белое (скорее даже градации серого). Изображение как известно харакетезуется его размером: некое колличество пикселов в ширину и в высоту. Фактически изображение является матрицей чисел. Признаками являются сами пикселы, т.е. отдельные элементы из которых изображение состоит. Их общее количество будет ширина умноденная на высоту.

Рассмотрим одну из классическиз задачь... определение цифры по изображению её рукописной заиси. Иначе говоря на вход будет подаватся изображение с рукописной цифрой (от 0 до 9). С точки зрения постановки задачи важно, что ничего друго не может быть там написано быть. т.е. гарантируется что одна из фифр (и только одна) изображена. Так вот по изображению нужно определить цифру.

In [36]:
from sklearn.datasets import load_digits # Данные для цифр
In [37]:
# Загружаем датасет из изображений рукописных чисел.
digits = load_digits()

Смотрим что находится в данных. На основании того что там есть решить отдкуда брать изображения и метки (правильные ответы).

In [38]:
# Смотрим ключи "словаря"
digits.keys()
Out[38]:
dict_keys(['data', 'target', 'target_names', 'images', 'DESCR'])
In [39]:
# Это очевидно метки.  
digits['target'] # target стандартное название для них.
Out[39]:
array([0, 1, 2, ..., 8, 9, 8])
In [40]:
# Проверим нашу интуицию. 
np.unique(digits['target']) # Тут должны быть только цифры от 0 до 9.
# И ничего больше! (конечно, только для данного датасета)
Out[40]:
array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9])
In [41]:
digits['target'].shape
Out[41]:
(1797,)

Это вроде сами изображения. проверим что количество элементов в массиве images совпдает с количеством ответов.

In [42]:
digits['images'].shape
# Видно даже, что размер каждого изображения 8 на 8.
Out[42]:
(1797, 8, 8)
In [43]:
# Отобразим матрицу изображения.
digits['images'][74] # матрица 8 на 8.
Out[43]:
array([[ 0.,  0., 13., 13.,  8.,  2.,  0.,  0.],
       [ 0.,  5., 16., 16., 16., 12.,  0.,  0.],
       [ 0.,  1., 15., 12.,  0.,  0.,  0.,  0.],
       [ 0.,  0., 12., 13.,  7.,  1.,  0.,  0.],
       [ 0.,  0.,  8., 16., 16., 12.,  0.,  0.],
       [ 0.,  0.,  0.,  4.,  9., 16.,  3.,  0.],
       [ 0.,  0.,  1.,  5., 14., 15.,  1.,  0.],
       [ 0.,  0., 10., 16., 16.,  6.,  0.,  0.]])

Вывдеим матрицы изображения виде картинки для лучшего понимания.

In [44]:
dig = digits['images'][74]
plt.imshow( dig ) # Картинка цветная и вводит в заблуждение.
Out[44]:
<matplotlib.image.AxesImage at 0x7f4a5cc4d630>

Посмотрим какой должен быть правильным ответ.

In [45]:
digits['target'][74]
Out[45]:
5

Сделаем все-таки изображения в градациях серого.

In [46]:
img = np.zeros((dig.shape[0], dig.shape[1], 3))
img[:,:,0] = dig
img[:,:,1] = dig
img[:,:,2] = dig
np.max(img), img.shape # Вычислил максимум значения изображения и тип.
# Видими, что изображение имеет всего 16 градаций.
Out[46]:
(16.0, (8, 8, 3))
In [47]:
img.dtype
Out[47]:
dtype('float64')
In [48]:
img /= np.max(img) # Отнормируем изображение (так чтобы максимум был равен 1).
plt.imshow( img ) # Теперь выглядит как надо.
Out[48]:
<matplotlib.image.AxesImage at 0x7f4a5cb17a90>

В данном массиве по суте хранятся названия для людей. В более сложном датасете они были бы более осмысленными. В даном они совпадабт с самими метками.

In [49]:
digits['target_names']
Out[49]:
array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9])

Для начала попробуем линейную модель

In [50]:
def train( clf, data, labels ):
    n = data.shape[0]
    perm = np.random.permutation( n )
    tr_n = int(0.85 * n)
    train = perm[ :tr_n ]
    valid = perm[ tr_n: ]

    train_dig = data[ train ]
    train_lab = labels[ train ]

    valid_dig = data[ valid ]
    valid_lab = labels[ valid ]

    clf.fit( train_dig, train_lab )

    val_cnt = sum( clf.predict( valid_dig ) == valid_lab ) 
    return clf.predict( valid_dig ), valid_lab #val_cnt / valid_lab.shape[0]
In [51]:
clf = svm.LinearSVC() # Вариант для линейных моделей.
pred, valid = train( clf, digits['data'], digits['target'] )
qq = pred == valid
sum(qq)/qq.shape[0]
/data/conda/anaconda3/envs/data_analysis/lib/python3.6/site-packages/sklearn/svm/base.py:929: ConvergenceWarning: Liblinear failed to converge, increase the number of iterations.
  "the number of iterations.", ConvergenceWarning)
Out[51]:
0.9666666666666667

Уже прекрасный результат.

In [52]:
clf = linear_model.SGDClassifier()
pred, valid = train( clf, digits['data'], digits['target'] )
qq = pred == valid
sum(qq)/qq.shape[0]
Out[52]:
0.9592592592592593
In [53]:
qq = pred == valid
#q3q
In [54]:
sum(qq)/qq.shape[0]
Out[54]:
0.9592592592592593
In [55]:
clf = linear_model.RidgeClassifier()
pred, valid = train( clf, digits['data'], digits['target'] )
qq = pred == valid
sum(qq)/qq.shape[0]
Out[55]:
0.9481481481481482

Ядровый метод опорных векторов

Как ранее было уже указано метод опорных веторов пдддерживает ядровый трюк, т.е. кривые разделяющие поверхности.

In [56]:
clf = svm.SVC( kernel = 'poly', gamma='auto' ) #svm.LinearSVC() # #SVC() SVC( kernel = 'poly' )
In [57]:
clf.fit( digits['data'][:1400], digits['target'][:1400] )
Out[57]:
SVC(C=1.0, cache_size=200, class_weight=None, coef0=0.0,
    decision_function_shape='ovr', degree=3, gamma='auto', kernel='poly',
    max_iter=-1, probability=False, random_state=None, shrinking=True,
    tol=0.001, verbose=False)
In [58]:
pred = clf.predict( digits['data'][1400:] )
In [59]:
pred[232], digits['target'][1400:][232]
Out[59]:
(3, 3)
In [60]:
sum( digits['target'][1400:] == pred )
Out[60]:
378
In [61]:
pred.shape
Out[61]:
(397,)
In [62]:
378/397
Out[62]:
0.9521410579345088
In [63]:
np.mean( digits['target'][1400:] == pred )
Out[63]:
0.9521410579345088

Оценим наше везение

In [64]:
ll = []
# На практике наверное не нужно делать такое большое колличество испытаний.
for k in range( 2000 ):
    clf = svm.SVC( kernel = 'poly', gamma='auto' )
    tr, vl = train( clf, digits['data'], digits['target'] )
    eq = tr == vl
    ll.append( np.mean(eq) )
In [65]:
# Скорее делается для того чтобы данных было много.
# Покажем часть.
ll[:10]
Out[65]:
[0.9888888888888889,
 0.9925925925925926,
 0.9851851851851852,
 0.9925925925925926,
 0.9814814814814815,
 1.0,
 0.9851851851851852,
 0.9888888888888889,
 0.9814814814814815,
 0.9888888888888889]
In [66]:
# и соответственно, статистики были более точные.
np.mean(ll), np.std(ll)
Out[66]:
(0.9884462962962964, 0.0059745915483902425)
In [67]:
params = sp.stats.norm.fit( ll )
params
Out[67]:
(0.9884462962962964, 0.0059745915483902425)
In [68]:
dat=sp.stats.norm.pdf( np.arange(0.965, 1, 0.001 ), loc = params[0], scale = params[1] )
In [69]:
np.arange(0.965, 1, 0.01)
Out[69]:
array([0.965, 0.975, 0.985, 0.995])
In [70]:
plt.hist( ll, 30, density = True );
plt.plot( np.arange(0.965, 1, 0.001), dat*3 )
Out[70]:
[<matplotlib.lines.Line2D at 0x7f4a5ca8ae80>]
In [71]:
clf = svm.SVC( kernel = 'poly', gamma='auto' )
pred, valid = train( clf, digits['data'], digits['target'] )

Отображение результатов

Зафиксируем какой либо класс. Например конкретную цифру. Два главных понятия:

  • precision -- точность: какой процент из предсказанныз цифр является правильным ответом.
  • recall -- полнота: какой процент из правильных ответов был верно отвечен.

Разберем синтезированые примеры

Возьмем два вектра чисел. Правильные ответы: [1,1,1,1,2,1], и то что быо предсказано [1,2,1,1,2,2]. Правдивость предсказаний: [Да, Нет, Да, Да, Да,Нет].

Выберем класс, например, 1. Тогда, все места где было предсказано 1, действительно соответсвуют действительности. Значит precision равен 1. Все ли 1 были предсказаны. Нет, не все. Вторая и последняя 1 не были предсказаны (мы сказали что там 2). Значит мы верно предсказали 3 из 5 единиц. 3/5=0.6. Значит recall 0.6.

In [72]:
from sklearn import metrics
In [73]:
print( metrics.classification_report( [1,1,1,1,2,1], [1,2,1,1,2,2] ) )
              precision    recall  f1-score   support

           1       1.00      0.60      0.75         5
           2       0.33      1.00      0.50         1

    accuracy                           0.67         6
   macro avg       0.67      0.80      0.62         6
weighted avg       0.89      0.67      0.71         6

Упр. По аналогии проверьте класс 2.

Разумеется можно запустить и для большего колличества классов.

In [74]:
print( metrics.classification_report( [1,1,0,1,2,1], [0,2,1,1,2,2] ) )
              precision    recall  f1-score   support

           0       0.00      0.00      0.00         1
           1       0.50      0.25      0.33         4
           2       0.33      1.00      0.50         1

    accuracy                           0.33         6
   macro avg       0.28      0.42      0.28         6
weighted avg       0.39      0.33      0.31         6

In [75]:
print( metrics.classification_report( [0,2,2], [1,2,1] ) )
              precision    recall  f1-score   support

           0       0.00      0.00      0.00         1
           1       0.00      0.00      0.00         0
           2       1.00      0.50      0.67         2

    accuracy                           0.33         3
   macro avg       0.33      0.17      0.22         3
weighted avg       0.67      0.33      0.44         3

/data/conda/anaconda3/envs/data_analysis/lib/python3.6/site-packages/sklearn/metrics/classification.py:1437: UndefinedMetricWarning: Precision and F-score are ill-defined and being set to 0.0 in labels with no predicted samples.
  'precision', 'predicted', average, warn_for)
/data/conda/anaconda3/envs/data_analysis/lib/python3.6/site-packages/sklearn/metrics/classification.py:1439: UndefinedMetricWarning: Recall and F-score are ill-defined and being set to 0.0 in labels with no true samples.
  'recall', 'true', average, warn_for)

Отчет для обученной модели

In [76]:
print( metrics.classification_report( pred, valid ) )
              precision    recall  f1-score   support

           0       0.96      1.00      0.98        25
           1       1.00      1.00      1.00        26
           2       1.00      1.00      1.00        24
           3       0.97      1.00      0.98        28
           4       1.00      0.97      0.98        31
           5       1.00      0.96      0.98        26
           6       1.00      1.00      1.00        33
           7       1.00      0.92      0.96        24
           8       0.97      1.00      0.98        29
           9       0.96      1.00      0.98        24

    accuracy                           0.99       270
   macro avg       0.99      0.98      0.98       270
weighted avg       0.99      0.99      0.99       270

Другие классификаторы

In [77]:
#clf.s support_vectors_.shape
In [78]:
clf = svm.SVC( gamma = 'auto' )
pred, valid = train( clf, digits['data'], digits['target'] )
qq = pred == valid
sum(qq)/qq.shape[0]
Out[78]:
0.4074074074074074
In [79]:
clf = svm.SVC( kernel = 'poly', gamma='auto' )
pred, valid = train( clf, digits['data'], digits['target'] )
qq = pred == valid
sum(qq)/qq.shape[0]
Out[79]:
0.9814814814814815
In [80]:
clf = svm.SVC( gamma = 0.001 )
pred, valid = train( clf, digits['data'], digits['target'] )
qq = pred == valid
sum(qq)/qq.shape[0]
Out[80]:
0.9962962962962963

Полносвязанные нейронные сети

In [81]:
from sklearn import neural_network
In [82]:
clf = neural_network.MLPClassifier( )
pred, valid = train( clf, digits['data'], digits['target'] )
qq = pred == valid
sum(qq)/qq.shape[0]
Out[82]:
0.9740740740740741
In [ ]: