Понимание серии 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.
- Я использовал 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, ознакомьтесь с моими предыдущими постами в этой серии.