Часто на месте преступления остаются осколки разных видов стекол, которые можно использовать как улики, если определить тип стекла и от каких оно объектов. Выборка состоит из 9 признаков - химических параметров образцов и 214 объектов. Необходимо каждому образцу сопоставить один из 6 классов (например: стекло автомобиля, осколок посуды, окно здания) и сравнить качество работы решающего дерева и алгоритма решающего дерева и алгоритма k-ближайших соседей. В качестве функции ошибки использовать долю неправильных ответов классификатора. Дает ли масштабирование признаков значительное улучшение в качестве классификации?
import pandas as pd
import sklearn as sc
import numpy as np
import matplotlib.pyplot as plt
from sklearn import tree, model_selection, metrics
from sklearn.neighbors import KNeighborsClassifier
from sklearn.preprocessing import StandardScaler
plt.rcParams['font.family'] = 'serif'
plt.rcParams['font.serif'] = 'FreeSerif'
plt.rcParams['lines.linewidth'] = 2
plt.rcParams['lines.markersize'] = 12
plt.rcParams['xtick.labelsize'] = 24
plt.rcParams['ytick.labelsize'] = 24
plt.rcParams['legend.fontsize'] = 24
plt.rcParams['axes.titlesize'] = 26
plt.rcParams['axes.labelsize'] = 24
columnNames = ['Id','RI', 'Na', 'Mg', 'Al', 'Si', 'K', 'Ca', 'Ba', 'Fe', 'Type of glass']
data = pd.read_csv('https://archive.ics.uci.edu/ml/machine-learning-databases/glass/glass.data',
names = columnNames, header=None)
df = data.drop(list(data)[0], 1)
df.head()
RI | Na | Mg | Al | Si | K | Ca | Ba | Fe | Type of glass | |
---|---|---|---|---|---|---|---|---|---|---|
0 | 1.52101 | 13.64 | 4.49 | 1.10 | 71.78 | 0.06 | 8.75 | 0.0 | 0.0 | 1 |
1 | 1.51761 | 13.89 | 3.60 | 1.36 | 72.73 | 0.48 | 7.83 | 0.0 | 0.0 | 1 |
2 | 1.51618 | 13.53 | 3.55 | 1.54 | 72.99 | 0.39 | 7.78 | 0.0 | 0.0 | 1 |
3 | 1.51766 | 13.21 | 3.69 | 1.29 | 72.61 | 0.57 | 8.22 | 0.0 | 0.0 | 1 |
4 | 1.51742 | 13.27 | 3.62 | 1.24 | 73.08 | 0.55 | 8.07 | 0.0 | 0.0 | 1 |
Посмотрим на соотношение классов.
X = df.drop(list(df)[9], 1)
target = df['Type of glass']
for i in range(1, 8):
print("{0} - {1}".format(i, sum([target[j] == i for j in range(target.shape[0])])))
1 - 70 2 - 76 3 - 17 4 - 0 5 - 13 6 - 9 7 - 29
В качестве критерия сравнения моделей решающего дерева и метода ближайших соседей будем сравнивать доли неправильных ошибок, усреднённых по кросс-валидации с 3 фолдами. Для воспроизводимости результата зафиксируем seed.
clf1 = tree.DecisionTreeClassifier()
np.random.seed(0)
c_v_s = model_selection.cross_val_score(clf1, X, target, cv = 3)
1 - c_v_s.mean()
0.4181989705565946
res = []
k = [i for i in range(1, 20)]
for i in range(1, 20):
clf2 = KNeighborsClassifier(n_neighbors=i)
c_v_s = model_selection.cross_val_score(clf2, X, target, cv = 3)
res.append(1 - c_v_s.mean())
min(res)
0.36784947838224025
Сравним результаты работы моделей после масштабирования признаков. Для метода ближайших соседей построим график зависимости доли ошибок классификатора от числа соседей.
np.random.seed(0)
scaler = StandardScaler()
scaler.fit(X, target)
X_scaled = scaler.transform(X)
clf1 = tree.DecisionTreeClassifier()
c_v_s = model_selection.cross_val_score(clf1, X_scaled, target, cv = 3)
1 - c_v_s.mean()
0.42270347506109907
res1 = []
for i in range(1, 20):
clf2 = KNeighborsClassifier(n_neighbors=i)
c_v_s = model_selection.cross_val_score(clf2, X, target, cv = 3)
res1.append(1 - c_v_s.mean())
min(res1)
0.36784947838224025
fig, ax = plt.subplots(figsize=(12, 8))
plt.xlabel('Number of neighbors, $k$')
plt.ylabel('Error rate, $Q$')
plt.plot(k, res, 'g-', marker='o')
plt.plot(k, res1, 'b-', marker='o')
ax.set_title('Зависимость ошибки от числа соседей')
plt.grid()
plt.show()
fig.savefig('1.svg')
Видим, что качество алгоритма метода ближайших соседей при кросс-валидации с количеством фолдов = 3 будет наилучшим при k = 14 и при таких условиях эффективнее алгоритма решающего дерева с параметрами по умолчанию. Как и следовало ожидать, масштабирование признаков не изменяет качество работы алгоритма ближайших соседей, а для решающего дерева даже немного ухудшает.
Построим график зависимости средней ошибки и его стандартного отклонения алгоритма ближайших соседей от объема выборки на обучении и контроле. В качестве выборки возьмём первые $m$ элементов выборки. Для этого $K = 20$ раз случайно разобьём выборку на обучение и контроль в отношении $1:1$. Модель ближайших соседей имеет единственный параметр - число соседей, так что для каждого разбиения на обучение и контроль подберём нужное число соседей, минимизирующее долю неправильных ответов классификатора на обучении и контроле соответственно. Для этого выберем минимальное целое число в отрезке $[1, size(X_{train})]$, которое будет доставлять минимум функции ошибки на обучении или контроле. Получим два набора из 20 ошибок. Вычислим среднюю ошибку и стандартное отклонение. Повторим процедуру для $m \in [2, 100]$. После этого построим график средней ошибки и стандартного отклонения от $m$.
from sklearn.model_selection import train_test_split
K = 20
error_mean = []
error_std = []
for m in range(2, 101):
X_data = X[0:m]
y_data = target[0:m]
error_data = []
for k in range(K):
X_data_train, X_data_test, y_train, y_test = train_test_split(X_data, y_data, test_size=0.5,
random_state=k)
error = []
for n in range(1, X_data_train.shape[0] + 1):
clf = KNeighborsClassifier(n_neighbors=n)
clf.fit(X_data_train, y_train)
predictions = clf.predict(X_data_test)
error.append(1 - metrics.accuracy_score(y_test, predictions))
error_data.append(min(error))
error_mean.append(np.array(error_data).mean())
error_std.append(np.array(error_data).std())
m = [i for i in range(2, 101)]
fig, ax = plt.subplots(figsize=(12, 8))
plt.xlabel('Size of train data, $m$')
plt.ylabel('Error rate, $Q$')
plt.errorbar(x = m, y = error_mean, yerr = np.array(error_std))
ax.set_title('Зависимость ошибки от объема выборки')
plt.grid()
plt.show()
fig.savefig('ModelBreadSw.png')