Понимание серии SVM: Часть 3

После предыдущего подробного обсуждения алгоритма SVM я завершу эту серию применением SVM для классификации рукописных цифр. Здесь мы будем использовать базу данных MNIST для рукописных цифр и классифицировать числа от 0 до 9 с помощью SVM. Исходный набор данных сложно обработать, поэтому я использую набор данных, обработанный Джозефом Redmon.

Я следил за процедурами конкурса Kaggle, и вы можете загрузить набор данных из самого kaggle. Набор данных основан на полутоновых изображениях рукописных цифр, каждое изображение имеет высоту 28 пикселей и ширину 28 пикселей. Каждый пиксель имеет номер, связанный с ним, где 0 представляет темный пиксель, а 255 представляет белый пиксель. Наборы данных для поездов и испытаний содержат 785 столбцов, в которых столбец label представляет рукописную цифру, а оставшиеся 784 столбца представляют значения (28, 28) пикселей. Набор данных для поездов и испытаний содержит 60 000 и 10 000 образцов соответственно. Я буду использовать несколько техник, таких как GridSearchCV и Pipeline, которые я представил в предыдущем посте, а также некоторые новые концепции, такие как представление полутонового изображения в массиве numpy.

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

import math, time 
import matplotlib.pyplot as plt
import numpy as np 
import pandas as pd
start = time.time() 
MNIST_train_small_df = pd.read_csv('mnist_train_small.csv', sep=',', index_col=0)
#print MNIST_train_small_df.head(3)
print MNIST_train_small_df.shape
>> (12000, 785)

Мы можем проверить, смещен ли набор обучающих данных в сторону определенных чисел или нет, распечатав value_counts() и / или из графика распределения этикеток.

sns.countplot(MNIST_train_small_df['label'])
plt.show()# looks kinda okay
# or we can just print
print MNIST_train_small_df['label'].value_counts()
>>
1    1351
7    1279
3    1228
6    1208
0    1206
9    1193
4    1184
2    1176
8    1127
5    1048

Мы видим, что выбор мало смещен в сторону цифры 1, а количество выборок для метки 1 примерно на 30% выше, чем для выборки 5, и эта проблема сохраняется, даже если мы используем набор данных для конкурирующего обучения (60 000 выборок). Итак, продолжаем, пора разделить столбцы меток и пикселей, а метка - это 1-й столбец кадра данных.

X_tr = MNIST_train_small_df.iloc[:,1:] # iloc ensures X_tr will be a dataframe
y_tr = MNIST_train_small_df.iloc[:, 0]

Затем я разделил обучающие и тестовые данные с 20% выборок, зарезервированных для тестовых данных. Я использовал stratify=y для сохранения распределения меток (цифр) -

X_train, X_test, y_train, y_test = train_test_split(X_tr,y_tr,test_size=0.2, random_state=30, stratify=y_tr)

Поскольку значения пикселей варьируются в диапазоне 0–255, пришло время использовать некоторую стандартизацию, и я использовал StandardScaler, который стандартизирует функции, удаляя среднее значение и масштабируя его до единичной дисперсии. Также после проверки всех ядер лучший результат и наименьшее время достигается с полиномиальным ядром. Чтобы узнать больше о трюках ядра, вы можете проверить предыдущий пост.

Здесь мы настроим объект Pipeline с StandardScaler и SVC в качестве преобразователя и оценщика соответственно.

steps = [('scaler', StandardScaler()), ('SVM', SVC(kernel='poly'))]
pipeline = Pipeline(steps) # define Pipeline object

Чтобы определить значение C, gamma, мы будем использовать метод GridSearchCV с 5-кратной перекрестной проверкой. Если вы хотите узнать больше о конвейере и поиске по сетке, посмотрите мой предыдущий пост.

parameters = {'SVM__C':[0.001, 0.1, 100, 10e5], 'SVM__gamma':[10,1,0.1,0.01]}
grid = GridSearchCV(pipeline, param_grid=parameters, cv=5)

Теперь мы готовы протестировать модель и найти наиболее подходящие параметры.

grid.fit(X_train, y_train)
print "score = %3.2f" %(grid.score(X_test, y_test))
print "best parameters from train data: ", grid.best_params_
>> 
score = 0.96
best parameters from train data:  {'SVM__C': 0.001, 'SVM__gamma': 10}
>>
y_pred = grid.predict(X_test)

Точность 96% достигается с 12000 сэмплов, и я ожидаю, что этот результат немного увеличится с полными 60 000 сэмплов. Часть GridSearchCV отнимает много времени, и при желании вы можете напрямую использовать параметры C и гамма.

Мы можем проверить некоторые прогнозы

print y_pred[100:105]
print y_test[100:105]
>> 
[4 2 9 6 0]
1765     4
220      2
932      9
6201     6
11636    0

Теперь мы можем построить цифры, используя python matplotlib pyplot imshow. Мы используем список прогнозов и значения пикселей из списка тестов для сравнения.

for i in (np.random.randint(0,270,6)):
 two_d = (np.reshape(X_test.values[i], (28, 28)) * 255).astype(np.uint8)
 plt.title('predicted label: {0}'. format(y_pred[i]))
 plt.imshow(two_d, interpolation='nearest', cmap='gray')
 plt.show()

Позвольте мне вкратце пояснить вторую строку кода. Поскольку значения пикселей расположены в строке с 784 столбцами в наборе данных, сначала мы используем модуль numpy «изменить форму», чтобы упорядочить его в массиве 28 X 28, а затем умножаем его на 255, поскольку значения пикселей были изначально стандартизированы. Имейте в виду, что X_test.values возвращает "числовое" представление фрейма данных.

Как вы можете видеть на изображениях выше, все они, кроме одного, были правильно классифицированы (я думаю, что изображение (1,1) - это цифра 7, а не 4). Чтобы узнать, сколько цифр было неправильно классифицировано, мы можем распечатать Матрицу путаницы. Согласно определению, данному в scikit-learn

Матрица неточностей C такова, что c (i, j) равно количеству наблюдений, которые, как известно, находятся в группе i, но, по прогнозам, относятся к группе j.

print "confusion matrix: \n ", confusion_matrix(y_test, y_pred)
>>
[[236   0   0   1   1   2   1   0   0   0]
 [  0 264   1   1   0   0   1   1   2   0]
 [  0   1 229   1   2   0   0   0   1   1]
 [  0   0   2 232   0   3   0   2   5   2]
 [  0   1   0   0 229   1   1   0   1   4]
 [  0   0   1   4   1 201   0   0   1   2]
 [  3   1   2   0   3   3 229   0   0   0]
 [  0   1   3   0   6   0   0 241   0   5]
 [  0   0   3   6   1   2   0   0 213   0]
 [  3   1   1   0   1   0   0   1   2 230]]

Итак, если мы рассмотрим 1-ю строку, мы можем понять, что из 241 нуля 236 были правильно классифицированы и так далее.

Теперь мы повторим процесс для набора тестовых данных (mnist_test.csv), но вместо того, чтобы искать лучшие параметры для SVM (C, гамма) с помощью GridSearchCV, я использовал те же параметры из набора обучающих данных.

Я использовал 5000 образцов вместо 10 000 тестовых образцов, чтобы сократить затраты времени, как упоминалось ранее.

MNIST_df = pd.read_csv('mnist_test.csv')
MNIST_test_small = MNIST_df.iloc[0:5000]
MNIST_test_small.to_csv('mnist_test_small.csv')
MNIST_test_small_df = pd.read_csv('mnist_test_small.csv', sep=',', index_col=0)

Следующий шаг - выбор функций и меток -

X_small_test = MNIST_test_small_df.iloc[:,1:]
Y_small_test = MNIST_test_small_df.iloc[:,0]

Разделите функции и метки на наборы для обучения и тестирования.

X_test_train, X_test_test, y_test_train, y_test_test = train_test_split(X_small_test,Y_small_test,test_size=0.2, random_state=30, stratify=Y_small_test)

Настройте объект Pipeline

steps1 = [('scaler', StandardScaler()), ('SVM', SVC(kernel='poly'))]
pipeline1 = Pipeline(steps1) # define

Настройте объект GridSearchCV, но на этот раз мы используем параметры, оцененные с помощью файла mnist_train.csv.

parameters1 = {'SVM__C':[grid.best_params_['SVM__C']], 'SVM__gamma':[grid.best_params_['SVM__gamma']]} 
grid1 = GridSearchCV(pipeline1, param_grid=parameters1, cv=5)
grid1.fit(X_test_train, y_test_train)
print "score on the test data set= %3.2f" %(grid1.score(X_test_test, y_test_test))
print "best parameters from train data: ", grid1.best_params_ # same as previous with training data set
>>
score on the test data set= 0.93
best parameters from train data:  {'SVM__C': 0.001, 'SVM__gamma': 10}
>>
y_test_pred = grid1.predict(X_test_test)

Оценка по набору тестовых данных составляет 93% по сравнению с 96% по набору данных о поездах. Ниже приведены некоторые случайные изображения из набора тестовых данных по сравнению с прогнозируемым уровнем.

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

print "confusion matrix: \n ", confusion_matrix(y_test_test, y_test_pred)
>>
[[ 91   0   0   0   0   0   0   0   1   0]
 [  0 111   2   0   1   0   0   0   0   0]
 [  0   0  98   1   0   0   1   2   4   0]
 [  0   0   1  91   0   2   0   0   4   2]
 [  0   0   0   1  95   0   0   1   0   3]
 [  0   0   1   3   1  77   4   0   3   2]
 [  1   1   1   0   2   0  85   0   2   0]
 [  0   0   0   1   0   0   0 100   0   2]
 [  0   0   1   1   0   2   0   1  93   0]
 [  0   0   0   0   4   1   0   3   3  93]]

Обратите внимание, что в цифре 0 из 92 ярлыков только 1 неверная классификация. Теперь мы перейдем к обсуждению возможности классификации моих собственных рукописных изображений.

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

Ниже приведены шаги, которые я предпринял для подготовки набора данных, а затем для классификации цифр от 0 до 9.

  1. Я использовал mypaint, чтобы сначала писать (раскрашивать) изображения, а затем использовал Imagemagick для изменения размера изображений с высотой и шириной 28X28 пикселей.
convert -resize 28X28! sample_image0.png sample_image0_r.png

2. Преобразование изображения в массив numpy и проверьте, как распределяются значения пикселей. Вы можете найти код на моем гитхабе и ниже 2 примера -

3. Выровняйте массив (28X28) до (784,) и преобразуйте его в список. Затем запишите его в файл csv, включая метку, то есть цифры, которые представляют пиксели. Таким образом, общее количество столбцов теперь составляет 785, в соответствии с обучающими и тестовыми CSV-файлами, которые я использовал раньше. Коды доступны на github.com.

4. Объедините новый фрейм данных с тестовым фреймом данных, чтобы в новом файле теперь было еще 10 строк.

5. Наконец, запустите тот же процесс классификации с этим новым файлом, только с одним отличием - данные для обучения и тестирования не подготавливаются с использованием метода train_test_split, поскольку моя основная цель - посмотреть, как алгоритм работает с новыми данными. . Итак, я выбрал первые 3500 строк для обучения и оставшиеся строки (включая новые данные) в качестве тестовых образцов.

X_hand_train = new_file_df_hand.iloc[0:3500, 1:]
X_hand_test  = new_file_df_hand.iloc[3500:5011, 1:]
y_hand_test = new_file_df_hand.iloc[3500:5011, 0]
y_hand_train = new_file_df_hand.iloc[0:3500, 0]

6. Чтобы отобразить рукописные изображения и определить, насколько хорошо они соответствуют прогнозируемому результату, я, как и раньше, использовал следующий цикл for: поскольку последние 1500 образцов, включая мои собственные рукописные изображения, берутся в качестве тестовых данных, цикл завершается еще несколько. ряды.

for ik in range(1496, 1511, 1):
 three_d = (np.reshape(X_hand_test.values[ik], (28, 28)) * 255).astype(np.uint8)
 plt.title('predicted label: {0}'. format(y_hand_pred[ik]))
 plt.imshow(three_d, interpolation='nearest', cmap='gray')
 plt.show()

7. Оценка набора тестовых данных, включая мои собственные рукописные данные, составляет 93%.

8. Давайте посмотрим, насколько хорошо классификатор классифицирует мой почерк от 0 до 9.

Итак, 7 из 10 рукописных цифр были правильно классифицированы, и это здорово, потому что если вы сравните с изображениями из базы данных MNIST, мои собственные изображения будут другими, и я думаю, что одна из причин - это выбор кисти. Как я понял, кисть, которую я использовал, давала гораздо более толстые изображения. Особенно при сравнении с изображениями MNIST я вижу, что между краями пиксели ярче (более высокие значения пикселей - ›255) на моих изображениях по сравнению с изображениями MNIST, и это может быть причиной 30% неправильной классификации.

Думаю, у вас есть идея, как использовать машину опорных векторов для решения более реалистичных задач. В качестве мини-проекта вы можете использовать аналогичный алгоритм для классификации модных данных MNIST.

Надеюсь, вам понравился этот пост. Чтобы узнать больше об основах SVM, ознакомьтесь с моими предыдущими постами в этой серии.