Перцептрон и методы опорных сигналов (SVM)
В данной главе рассматриваются фундаментальные аспекты классификации объектов с применением методик машинного обучения, в частности, перцептрона и методы опорных сигналов (SVM) (Support Vector Machine). Все коды, представленные в этом учебнике и в этой главе в частности можно найти по ссылке https://sohoware.ru/SohoBook/. В изучении и применении компьютерных наук и искусственного интеллекта, ключевым элементом является понимание и обработка различных типов данных, которые могут варьироваться от физических объектов...
Ключевые идеи
- Основы классификации объектов и понятий
- Классификаторы
- Линейные дискриминантные функции
- Перцептрон
- Метод опорных векторов на основе ядра (Kernel-Based SVM)
Практическое задание
Создайте 10 двумерных точек двух классов и вручную сравните решение KNN с простой границей дерева решений. Свяжите результат с главой "Перцептрон и методы опорных сигналов (SVM)".
Перцептрон и методы опорных сигналов (SVM)
В данной главе рассматриваются фундаментальные аспекты классификации объектов с применением методик машинного обучения, в частности, перцептрона и методы опорных сигналов (SVM) (Support Vector Machine). Все коды, представленные в этом учебнике и в этой главе в частности можно найти по ссылке https://sohoware.ru/SohoBook/.
6.1 Основы классификации объектов и понятий
6.1.1 Паттерн
В изучении и применении компьютерных наук и искусственного интеллекта, ключевым элементом является понимание и обработка различных типов данных, которые могут варьироваться от физических объектов до абстрактных концепций. Эти данные или элементы информации часто называются "паттернами", термин, охватывающий широкий спектр возможностей. Например, в повседневной жизни и в научных исследованиях мы сталкиваемся с задачей распознавания и классификации разнообразных объектов, таких как люди и предметы мебели, а также более тонких и менее осязаемых аспектов, таких как стили письма или речи.
Когда дело доходит до обработки и анализа этих элементов в компьютерных системах, возникает вопрос об их представлении в формате, понятном для машины. Поскольку прямое сохранение физических объектов или абстрактных концепций в компьютере невозможно, необходим процесс трансформации этих элементов в данные, с которыми может работать машина. Этот процесс называется "представлением" и включает в себя создание упрощенных, но все же точных моделей объектов или понятий, которые сохраняются и обрабатываются компьютером.
Существует несколько способов представления таких элементов. Один из наиболее распространенных подходов — использование векторного пространства, где каждый элемент моделируется как точка или вектор в многомерном пространстве, описываемый набором числовых значений. Эти значения могут отражать различные характеристики объекта или понятия, например, размеры или вес для физических объектов, или частоту употребления определенных слов для текстов. Альтернативный подход заключается в использовании лингвистических или структурных моделей, где элементы представлены с помощью формального языка, описывающего их свойства и взаимосвязи.
Выбор метода представления важен, поскольку он влияет на способность системы эффективно обрабатывать и классифицировать данные. Векторные модели, например, широко используются в машинном обучении и искусственном интеллекте благодаря их способности к точной классификации и анализу сходства или различий между объектами на основе метрик, таких как евклидово расстояние или косинусное сходство.
Важно отметить, что хотя объект или понятие и его компьютерное представление технически являются разными вещами, в контексте обработки данных они часто рассматриваются как взаимозаменяемые. Это означает, что термин, используемый для обозначения физического объекта или абстрактного понятия, также может применяться к его представлению в данных. Например, говоря о "классификации объектов", мы часто имеем в виду классификацию их представлений в компьютерной системе. Несмотря на это различие принято использовать термин "паттерн", а использование уточняется на основе контекста. Коллекция из n-паттернов представляется как {X1, X2, ..., Xn}, где каждый паттерн является p-мерным вектором:Xi={Xi1,Xi2,…Xip}∈Xp
6.1.2 Функция близости
Функция близости играет ключевую роль в задачах классификации, позволяя оценить степень схожести или различия между объектами. Эта оценка может осуществляться через два основных подхода: с использованием функции расстояния или функции сходства.
Функция расстояния устанавливает, насколько далеко друг от друга находятся объекты в пространстве признаков. Самый распространенный метод — евклидово расстояние, которое рассчитывается как корень из суммы квадратов разностей между соответствующими признаками двух объектов Расстояние между объектами Xi и Xj обозначается как d(Xi, Xj), и задаваемое как:
Это расстояние подчиняется трём основным свойствам для любых трех объектов Xi, Xj и Xk:
Неотрицательность: d(Xi, Xj) ≥ 0 расстояние не может быть меньше нуля,
Симметрия: d(Xi, Xj) = d(Xj, Xi) расстояние от одного объекта до другого равно расстоянию от второго до первогоНеравенство треугольникаd(Xi, Xj) + d(Xj, Xk) ≥ d(Xi, Xk) сумма расстояний между двумя парами объектов не может быть меньше расстояния между самой дальней парой. Это свойство полезно для сокращения времени вычислений и установления некоторых полезных ограничений для упрощения анализа нескольких алгоритмов.
Эти свойства делают евклидово расстояние удобным инструментом для многих задач классификации, несмотря на то что в некоторых случаях, например, при работе с векторами разной длины, может быть предпочтительнее использовать другие метрики.
Метрика - это способ измерения и количественной оценки различий между объектами или точками данных.Например, квадрат евклидова расстояния не является метрикой; однако он так же хорош, как евклидово расстояние как в ранжировании, так и в классификации.
Приведемпример:importnumpyasnp
importmatplotlib.pyplotasplt
# начальныеданные
X=np.array([2, 2])
X1=np.array([2, 5])
X2=np.array([5, 5])
X3=np.array([3, 2])
X4=np.array([4, 4])
X5=np.array([6, 6])
# Расчет евклидовых расстояний
d_X_X3=np.linalg.norm(X-X3)
d_X_X1=np.linalg.norm(X-X1)
d_X_X2=np.linalg.norm(X-X2)
# Расчет квадратов евклидовых расстояний
d_X_X3_squared=np.linalg.norm(X-X3)**2
d_X_X1_squared=np.linalg.norm(X-X1)**2
d_X_X2_squared=np.linalg.norm(X-X2)**2
d_X_X4_squared=np.linalg.norm(X-X4)**2
d_X_X5_squared=np.linalg.norm(X-X5)**2
# Вывод результатов
print(f"Евклидово расстояние d(X, X3) = {d_X_X3}< d(X, X1) = {d_X_X1}< d(X, X2) = {d_X_X2}")
print(f"Квадраты евклидовых расстояний d(X, X3)^2 = {d_X_X3_squared}< d(X, X1)^2 = {d_X_X1_squared}< d(X, X2)^2 = {d_X_X2_squared}")
print(f"Квадрат евклидова расстояния d(X, X4)^2 = {d_X_X4_squared}, d(X, X5)^2 = {d_X_X5_squared}")
print(f"Сравнение квадратов расстояний и проверка неравенства треугольника:\n"
f"d(X, X5)^2 = {d_X_X5_squared}> d(X, X4)^2 + d(X4, X5)^2 = {d_X_X4_squared+np.linalg.norm(X4-X5)**2}")
points= [X, X1, X2, X3, X4, X5]
labels= ['X', 'X1', 'X2', 'X3', 'X4', 'X5']
plt.figure(figsize=(8, 6))
forpoint, labelinzip(points, labels):
plt.scatter(point[0], point[1], label=label)
# Подключение точек линиями для наглядности расстояний
plt.plot([X[0], X3[0]], [X[1], X3[1]], 'r--', lw=1)
plt.plot([X[0], X1[0]], [X[1], X1[1]], 'g--', lw=1)
plt.plot([X[0], X2[0]], [X[1], X2[1]], 'b--', lw=1)
plt.plot([X[0], X4[0]], [X[1], X4[1]], 'y--', lw=1)
plt.plot([X[0], X5[0]], [X[1], X5[1]], 'm--', lw=1)
# Добавление легенды и меток осей
plt.legend()
plt.xlabel('X ')
plt.ylabel('Y ')
plt.title('Евклидовы расстояния между точками')
plt.grid(True)
plt.show()
Евклидово расстояние d(X, X3) = 1.0 <d(X, X1) = 3.0 <d(X, X2) = 4.242640687119285
Квадраты евклидовых расстояний d(X, X3)^2 = 1.0 <d(X, X1)^2 = 9.0 <d(X, X2)^2 = 17.999999999999996
Квадрат евклидова расстояния d(X, X4)^2 = 8.000000000000002, d(X, X5)^2 = 32.00000000000001
Сравнение квадратов расстояний и проверка неравенства треугольника:
d(X, X5)^2 = 32.00000000000001 >d(X, X4)^2 + d(X4, X5)^2 = 16.000000000000004Таким образом, неравенство треугольника не выполняется для квадратов евклидовых расстояний.

Таким образом, неравенство треугольника не выполняется для квадратов евклидовых расстояний.
Функция сходстваотражает степень похожести между объектами, и одним из наиболее часто используемых методов здесь является косинусное сходство. Он определяется следующим образом:
Рассмотрим объекты X, X1, X2 и X3 из предыдущего примера, мы имеем cos(X, X2) >cos(X, X3) >cos(X, X1). Таким образом, первые три соседа X по порядку сходства - это X2, X3 и X1. Заметим, что X и X2 очень похожи, используя косинусное сходство, поскольку эти два объекта имеют угол в 0 градусов между ними, несмотря на различие в их величинах
Этот метод оценивает косинус угла между векторами признаков объектов, что позволяет определить их направленное сходство независимо от величины (длины) векторов. Косинусное сходство особенно полезно в задачах, связанных с текстами и другими высокоразмерными данными, где важнее сравнение направлений векторов, а не их абсолютных значений.
Связь между скалярным произведением и косинусным сходством заключается в том, что если векторы имеют единичную норму, то их скалярное произведение равно косинусу угла между ними. Это свойство делает скалярное произведение и косинусное сходство взаимозаменяемыми в условиях нормализованных данных. Таким образом, скалярное произведение может использоваться для вычисления степени сходства между нормализованными векторами, обеспечивая эффективный механизм для сравнения объектов в многомерных пространствах.
Важно понимать, что выбор между функцией расстояния и функцией сходства зависит от конкретной задачи и специфики данных. В то время как евклидово расстояние может быть идеальным выбором для пространств с малым количеством измерений, косинусное сходство часто предпочтительнее в высокоразмерных пространствах, таких как текстовые данные, где важнее оценить общее направление векторов признаков, а не их абсолютные расстояния.
6.1.3 Классификация
в машинном обучении классификация относится к процессу определения, к какому классу или категории принадлежит каждый из рассматриваемых объектов или паттернов. Каждый класс представляет собой набор объектов, обладающих общими характеристиками или свойствами, выраженными через их метки классов. В задачах бинарной классификации мы часто сталкиваемся с двумя группами: положительной (C+), ассоциируемой с присутствием определенного признака, и отрицательной (C−), обозначающей его отсутствие. Определение принадлежности объекта к тому или иному классу может осуществляться с помощью функции g, которая преобразует многомерный объект (или паттерн) X в действительное число.
g:Rp→ R
C-={X|g(X)<0} и C+={X|g(X)>0}Если значение функции g для объекта X меньше нуля, объект относится к отрицательному классу C−, а если больше — к положительному классу C+. Это позволяет нам интерпретировать классификацию как процесс разделения объектов на две группы на основе их характеристик.
Функция g может быть определена различными способами, в зависимости от специфики задачи и природы данных, что позволяет гибко подходить к решению разнообразных задач классификации.
Пример:importnumpyasnp
importmatplotlib.pyplotasplt
# Определение точек для двух классов
class_negative=np.array([(0, 0), (2, 2)])
class_positive=np.array([(3, -3), (4, -2), (3, -1)])
# Визуализация точек на графике
plt.figure(figsize=(8, 6))
plt.scatter(class_negative[:, 0], class_negative[:, 1], color='red', label='C-')
plt.scatter(class_positive[:, 0], class_positive[:, 1], color='blue', label='C+')
# Определение и визуализация разделяющей линии
# Для примера используем линейную функцию g(x) = x - 3.5
x_values=np.linspace(0, 8, 100)
y_values=x_values-3.5
plt.plot(x_values, y_values, 'g--', label='g(x) = x - 3.5')
plt.xlabel('X ')
plt.ylabel('Y ')
plt.title('Пример классификации с двумя классами')
plt.legend()
plt.grid(True)
plt.show()
На графике показаны точки для двух классов в двумерном пространстве: класс C- (красный цвет) и класс C+ (синий цвет). Также на графике изображена пунктирная зеленая линия, которая представляет собой предполагаемую границу, разделяющую эти два класса, определенную функцией g(X) = X1 - 3.5. Это иллюстрация того, как функция g(x) может быть использована для определения принадлежности точек к определенному классу в зависимости от их положения относительно границы.
6.2 Классификаторы
6.2.1 Классификатор ближайшего соседа (KNN)
Классификатор ближайшего соседа (KNN) — это один из самых простых методов машинного обучения, используемых для классификации объектов на основе их ближайших соседей в пространстве признаков. Принцип работы KNN заключается в том, что для классификации нового объекта X система сначала идентифицирует ближайшие к нему объекты из обучающего набора данных, а затем присваивает X к тому классу, который наиболее часто встречается среди его ближайших соседей.
Для определения класса объекта X используется специальная функция g()X = g-(X)-g+(X) для X∈Rp где g-(X)=minXj∈C-X,Xj и g+(X)=minXj∈C+X,Xj
Эта функция вычисляет разность между минимальным расстоянием от X до любого объекта в отрицательном классе C− и минимальным расстоянием от X до любого объекта в положительном классе C+. Другими словами, g−(X) представляет собой расстояние от X до его ближайшего соседа в классе C−, а g+(X) — расстояние до ближайшего соседа в классе C+.
Для расчета расстояний может использоваться любая метрика, но в данном примере применяется квадрат евклидова расстояния. Это выбор обусловлен тем, что квадрат евклидова расстояния часто используется в задачах машинного обучения из-за его простоты и эффективности в вычислениях.
importnumpyasnp
importmatplotlib.pyplotasplt
# Определение классов и точек
class_negative=np.array([(0, 0), (2, 2)])
class_positive=np.array([(3, -3), (4, -2), (3, -1)])
X=np.array([1, 2])
X_prime=np.array([4, 0])
# Функция расстояния - квадрат евклидова расстояния
defsquared_euclidean_distance(x1, x2):
returnnp.sum((x1-x2)**2)
# Расчет g−(X) и g+(X)
g_minus_X=np.min([squared_euclidean_distance(X, x) forxinclass_negative])
g_plus_X=np.min([squared_euclidean_distance(X, x) forxinclass_positive])
g_X=g_minus_X-g_plus_X
g_minus_X_prime=np.min([squared_euclidean_distance(X_prime, x) forxinclass_negative])
g_plus_X_prime=np.min([squared_euclidean_distance(X_prime, x) forxinclass_positive])
g_X_prime=g_minus_X_prime-g_plus_X_prime
# Визуализация
plt.figure(figsize=(8, 6))
plt.scatter(class_negative[:, 0], class_negative[:, 1], color='red', label='C-')
plt.scatter(class_positive[:, 0], class_positive[:, 1], color='blue', label='C+')
plt.scatter(X[0], X[1], color='green', label='X (1, 2)', edgecolors='k', s=100, zorder=5)
plt.scatter(X_prime[0], X_prime[1], color="purple", label="X' (4, 0)", edgecolors="k", s=100, zorder=5)
plt.legend()
plt.xlabel('X1')
plt.ylabel('X2')
plt.title('Примерклассификации KNN')
plt.grid(True)
# Выводрезультатов
print(f"g−(X) = {g_minus_X}, g+(X) = {g_plus_X}, следовательно g(X) = {g_X}, принадлежит X to C-")
print(f"g−(X') = {g_minus_X_prime}, g+(X') = {g_plus_X_prime}, следовательно g(X') = {g_X_prime}, принадлежит X' to C+")
plt.show()
g−(X) = 1, g+(X) = 13, следовательно g(X) = -12, принадлежит X to C-
g−(X') = 8, g+(X') = 2, следовательно g(X') = 6, принадлежит X' to C+
В этом примере мы рассчитали квадраты евклидовых расстояний от точки X = (1, 2) до классов C- и C+, где получили значения g−(X)=1 и g+(X) = 13. Таким образом, g(X) = -12<0, что означает, что X ближе к классу C-, и ей присваивается этот класс. Для точки X' = (4, 0) расчеты аналогичны дали g(X') = 6, что ведет к присвоению X' к классу C+.
Если значение функции g(x) получается отрицательным, это означает, что ближайший сосед X находится в классе C−, и, следовательно, X присваивается к этому классу. Аналогично, положительное значение g(X) указывает на то, что X ближе к объектам класса C+, и X присваивается к C+.
Такой подход позволяет классификатору KNN гибко адаптироваться к различным распределениям данных в пространстве признаков и эффективно разделять объекты на классы на основе их близости к уже известным примерам из обучающего набора. Это делает KNN полезным в приложениях, где взаимосвязи между признаками и классами объектов могут быть сложными и нелинейными.
6.2.2 Классификатор k-ближайших соседей (KNNC)
Классификатор k-ближайших соседей (KNNC) — это расширение базового принципа классификации ближайшего соседа, которое позволяет учитывать не одного, а k ближайших соседей тестового объекта X для определения его класса. Вместо того чтобы опираться исключительно на самого близкого соседа, KNNC рассматривает группу из k соседей и присваивает объекту X класс, представители которого преобладают среди этих k соседей.
функция g(x) вычисляется как разность между количеством соседей из положительного класса g+(X) и количеством соседей из отрицательного класса g−(X), где g−(X) = K− и g+(X) = K+ = K − K−. Это означает, что если большинство из k ближайших соседей принадлежит классу C−, то g(X) будет отрицательным, и X присваивается к классу C−. Если же большинство соседей относится к классу C+, то g(X) будет положительным, и X присваивается к C+.
Приведемпример:mportnumpyasnp
importmatplotlib.pyplotasplt
# Функция для вычисления квадрата евклидова расстояния
defsquared_distance(x1, x2):
returnnp.sum((x1-x2) **2)
# Функция для нахождения k ближайших соседей
deffind_k_nearest_neighbors(data, labels, x, k):
distances=np.array([squared_distance(x, point) forpointindata])
indices=np.argsort(distances)[:k]
returnlabels[indices]
# Функция для определения класса по большинству голосов
defclassify_point(k_neighbors):
counts=np.bincount(k_neighbors)
returnnp.argmax(counts)
# Функция для визуализации k ближайших соседей
defplot_k_nearest_neighbors(data, labels, test_point, k):
distances=np.array([squared_distance(test_point, point) forpointindata])
indices=np.argsort(distances)[:k]
nearest_neighbors=data[indices]
# Визуализациясоседей
forneighborinnearest_neighbors:
plt.plot([test_point[0], neighbor[0]], [test_point[1], neighbor[1]], 'k--', lw=1)
# Исходныеданные
class_negative=np.array([(0, 0), (2, 2)])
class_positive=np.array([(3, -3), (4, -2), (3, -1)])
data=np.vstack((class_negative, class_positive))
labels=np.array([0] *len(class_negative) + [1] *len(class_positive)) # 0 - C-, 1 - C+
test_points=np.array([[1, 2], [5, 2]])
# Визуализация и классификация тестовых точек
plt.figure(figsize=(8, 6))
plt.scatter(class_negative[:, 0], class_negative[:, 1], color='red', label='C-')
plt.scatter(class_positive[:, 0], class_positive[:, 1], color='blue', label='C+')
fortest_pointintest_points:
plt.scatter(test_point[0], test_point[1], color='green', edgecolors='k', s=100, zorder=5)
k_neighbors_labels=find_k_nearest_neighbors(data, labels, test_point, 3)
classification=classify_point(k_neighbors_labels)
print(f"Точка{test_point}классифицированакак{'C-'ifclassification==0else'C+'}")
plot_k_nearest_neighbors(data, labels, test_point, 3)
plt.legend()
plt.xlabel('X1')
plt.ylabel('X2')
plt.title('Классификатор 3-ближайших соседей (KNNC)')
plt.grid(True)
plt.show()
Точка [1 2] классифицирована как C-
Точка [5 2] классифицирована как C+
На графике изображены классы C- (красным цветом) и C+ (синим цветом), а также две тестовые точки (зелёным цветом) — [1, 2] и [5, 2]. Для каждой тестовой точки пунктирными линиями показаны связи с их тремя ближайшими соседями, найденными с помощью алгоритма KNN с параметром k=3.
Точка [1, 2] была классифицирована как принадлежащая к классу C−, что демонстрирует, что большинство её ближайших соседей находятся в отрицательном классе.
Точка [5, 2] была классифицирована как принадлежащая к классу C+, что указывает на преобладание соседей из положительного класса среди её ближайших соседей.
Этот график наглядно демонстрирует, как алгоритм KNN определяет класс тестовой точки на основе классов её ближайших соседей, а также показывает взаимосвязь тестовых точек с этими соседями.
6.2.3 Классификатор минимального расстояния (MDC)
Классификатор минимального расстояния (MDC) является методом классификации, который использует концепцию расстояния для определения принадлежности тестового объекта к одному из двух классов на основе его близости к средним значениям (центроидам) этих классов.Рассмотрим более подробно работу MDC:
Определение средних значений классов: Для начала вычисляются средние значения (или центроиды) для каждого из классов. Это делается путем нахождения среднего арифметического всех точек, принадлежащих данному классу. В нашем примере, m− является средним значением точек класса C−, а m+ — средним значением точек класса C+:
m-=ΣXj∈C-XjC-иm+=ΣXj∈C+XjC+
Эти средние значения представляют собой "центральные" точки каждого класса в многомерном пространстве признаков.
Вычисление расстояний до центроидов: Для классификации тестового объекта X сначала вычисляются расстояния от X до каждого из центроидов классов используя функцию растояния. Таким образом, g−(X) представляет собой расстояние от X до центроида класса C−:g-(X)=d(X,m-)
а g+(X)— расстояние от X до центроида класса C+:g+(X)=d(X,m+)Классификация на основе расстояний: Классификация тестового объекта X производится путем сравнения расстояний до центроидов: если расстояние до центроида класса C− меньше, чем до центроида класса C+, то X присваивается классу C−. В противном случае, если расстояние до центроида класса C+ меньше, то X присваивается классу C+.
Приведемпример:importnumpyasnp
importmatplotlib.pyplotasplt
# Функция для вычисления квадратов евклидовых расстояний
defsquared_euclidean_distance(x1, x2):
returnnp.sum((x1-x2) **2)
# Определение классов и их центроидов
class_negative=np.array([[1, 1], [2, 2]])
class_positive=np.array([[6, 2], [7, 2], [7, 3]])
m_negative=np.mean(class_negative, axis=0)
m_positive=np.mean(class_positive, axis=0)
# Тестовые точки
X=np.array([1, 2])
X_prime=np.array([5, 2])
# Расчет расстояний от тестовых точек до центроидов
g_minus_X=squared_euclidean_distance(X, m_negative)
g_plus_X=squared_euclidean_distance(X, m_positive)
g_X=g_minus_X-g_plus_X
g_minus_X_prime=squared_euclidean_distance(X_prime, m_negative)
g_plus_X_prime=squared_euclidean_distance(X_prime, m_positive)
g_X_prime=g_minus_X_prime-g_plus_X_prime
# Визуализация
plt.figure(figsize=(8, 6))
plt.scatter(class_negative[:, 0], class_negative[:, 1], color='red', label='C-')
plt.scatter(class_positive[:, 0], class_positive[:, 1], color='blue', label='C+')
plt.scatter(X[0], X[1], color='green', label='X (1, 2)', edgecolors='k', s=100, zorder=5)
plt.scatter(X_prime[0], X_prime[1], color='purple', label='X (5, 2)', edgecolors='k', s=100, zorder=5)
plt.scatter(m_negative[0], m_negative[1], color='red', marker='x', s=200, label='m-')
plt.scatter(m_positive[0], m_positive[1], color='blue', marker='x', s=200, label='m+')
plt.legend()
plt.xlabel('X1')
plt.ylabel('X2')
plt.title('Минимальный классификатор расстояния (MDC)')
plt.grid(True)
plt.show()
# Выводрезультатов
print(f"g−(X) = {g_minus_X}, g+(X) = {g_plus_X}, hence g(X) = {g_X}, assigning X to {'C-'ifg_X<0else'C+'}")
print(f"g−(X') = {g_minus_X_prime}, g+(X') = {g_plus_X_prime}, hence g(X') = {g_X_prime}, assigning X' to {'C-'ifg_X_prime<0else'C+'}")
g−(X) = 0.5, g+(X) = 32.22222222222223, hence g(X) = -31.72222222222223, assigning X to C-
g−(X') = 12.5, g+(X') = 2.8888888888888897, hence g(X') = 9.61111111111111, assigning X' to C+
На графике показаны два класса: C- (красным цветом) и C+ (синим цветом), а также две тестовые точки X = (1, 2) (зеленым цветом) и X' = (5, 2) (фиолетовым цветом). Центроиды каждого класса обозначены крестиками соответствующих цветов.
Для точки X расчет показал, что квадрат евклидова расстояния до центроида класса C- (g−(X)) равен 0.5, а до центроида класса C+ (g+(X)) — 32.2. Таким образом, разница g(X) = -31.7, что говорит о том, что точка X ближе к центроиду класса C-, и ей присваивается этот класс.
Для точки X' расчет показал, что квадрат евклидова расстояния до центроида класса C- (g−(X')) равен 12.5, а до центроида класса C+ (g+(X')) — 2.9. Следовательно, разница g(X') = 9.6, что указывает на то, что точка X' ближе к центроиду класса C+, и ей присваивается класс C+.
Этот пример иллюстрирует, как классификатор минимального расстояния (MDC) использует расстояния до средних значений классов для определения класса тестовых точек.
6.2.4 Классификатор минимального расстояния Махаланобиса
Это метод классификации, который использует расстояние Махаланобиса для определения принадлежности объекта к одному из двух классов. Этот метод особенно эффективен, когда данные распределены нормально и когда есть необходимость учитывать ковариацию (меру зависимости между двумя или более случайными переменными) между переменными для определения "расстояния" между точками данных.
Основные Концепции:Расстояние Махаланобиса отличается от евклидова расстояния тем, что оно учитывает корреляции между переменными. В контексте классификации это означает, что расстояние между точкой данных и центром класса (среднее значение) измеряется не просто как прямая линия, а с учетом общей структуры данных.
Центр класса (μ) — это среднее значение всех точек в классе. Для каждого класса C− и C+ вычисляются их собственные центры μ− и μ+ соответственно.
Ковариационная матрица (Σ) описывает, как переменные данных связаны между собой и распределены в пространстве.
Как Работает Классификатор:
Вычисление расстояний Махаланобиса:
Для тестовой точки X, расстояния g−(X) и g+(X) до центров классов μ− и μ+ вычисляются следующим образом:Вычитание среднего: Сначала из X вычитается среднее значение соответствующего класса (μ− для класса C− и μ+ для класса C+). Это дает вектор разностей, который направлен от центра класса к точке X.
Применение обратной ковариационной матрицы: Затем этот вектор разностей умножается на обратную ковариационную матрицу Σ−1. Обратная ковариационная матрица учитывает взаимосвязь между переменными и "нормализует" расстояние, учитывая эти корреляции. Если данные в одном направлении имеют большую дисперсию, то расстояние в этом направлении будет учитываться как меньшее.
Вычисление квадрата расстояния Махаланобиса: Результат умножения затем снова скалярно умножается на вектор разностей. Полученное значение представляет собой квадрат расстояния Махаланобиса от X до центра класса. Формула для класса C− выглядит как g−(X) = X – μ-TΣ-1(X − μ−), а для класса C+ :g+(X) = (X – μ+)TΣ-1(X − μ+).
Классификация: Тестовая точка X классифицируется как принадлежащая к тому классу, до центра которого расстояние Махаланобиса минимально. Если g−(X) < g+(X) то X присваивается к классу C−, и наоборот.
6.2.5 Классификатор дерева решений: (DTC)
Дерево решений (DecisionTreeClassifier, DTC) — это алгоритм машинного обучения, который строит модель прогнозирования в форме дерева. Процесс разделения в дереве решений основан на выборе признаков, который позволяет наилучшим образом разделить данные на классы. Целью является создание "чистых" узлов, где каждый узел содержит паттерны (или точки данных), как можно более однородные по классу принадлежности.
Ключевой момент в построении дерева решений — это выбор признака для разделения, который максимизирует "чистоту" узлов после разделения. Чистота означает, что паттерны в узле принадлежат одному и тому же классу. Таким образом, идеальное разделение полностью разделяет паттерны разных классов между разными ветвями дерева.
Представьте, что у вас есть набор данных, разделенный на два класса, и вы рассматриваете два признака (X1 и X2) для разделения. Разделение по признаку X1 может привести к тому, что одна ветвь дерева будет содержать только паттерны класса C+, в то время как другая ветвь будет содержать большинство паттернов класса C−, но с некоторым количеством паттернов класса C+ (это называется примесью). Разделение по признаку X2 может привести к большей примеси в обеих ветвях.
В контексте дерева решений, g(X) = g+(X) − g−(X) где g+(X) и g−(X)— булевы функции, возвращающие 1 или 0 в зависимости от того, следует ли направить паттерн X в класс C+ или C− на основе условий разделения в дереве. Эти условия определяются путем прохождения от корня дерева к его листьям, где каждый лист ассоциирован с меткой класса.
Каждый листовой узел дерева ассоциируется с одним из классов, и решение о классификации объекта принимается на основе пути, который объект проходит от корня дерева к листу. Если в дереве m листовых узлов, и m− из них ассоциированы с классом C−, то g−(X) будет представлять собой дизъюнкцию (логическое "ИЛИ") m− конъюнкций (логических "И"), где каждая конъюнкция соответствует уникальному пути от корня к листу, ассоциированному с C−. Аналогично, g+(X) будет дизъюнкцией оставшихся путей, ведущих к листьям, ассоциированным с C+.
Чтобы понять этот классификатор рассмотрим пример:
В наборе данных шесть паттернов, и метки классов для них следующие:
Отрицательный класс: (1, 1), (2, 2)(красные точки)
Положительный класс: (2, 3), (6, 2), (7, 2), (7, 3) (синие точки)Также у нас есть дерево решений состоящие из три листовых узлов один отрицательный и два положительных.
Таким образом, соответствующие g−(X) и g+(X) выглядят следующим образом:
g−(X)=(X1≤4)∧(X2≤2.5) и
g+(X) = (X1> 4) ∨ (X1 ≤ 4) ∧ (X2> 2.5)

Если X = (1, 2) (зеленая точка), то g−(X) = 1 и g+(X) = 0 (предполагая, что булева функция возвращает значение 0, когда она ЛОЖНА, и значение 1, когда она ИСТИННА). Таким образом, g(X) = g+(X) − g−(X) = 0 − 1 = −1 < 0 следовательно, X присваивается к C−.
Если X = (5, 2) то g−(X) = 0 и g+(X) = 1. Так что g(X) = 1 следовательно, X присваивается к C+.6.2.6 Классификация на основе линейной дискриминантной функции
Классификация на основе линейной дискриминантной функции — это метод, используемый в машинном обучении и статистике для определения принадлежности объектов к определенным классам. Линейная дискриминантная функция является основой этого метода, и она представляет собой уравнение, которое линейно комбинирует признаки объекта с целью принятия решения о его классификации.
Функция имеет вид g(X) = WT X + w0, где:
X — это вектор признаков объекта, который мы хотим классифицировать. Признаки — это характеристики или атрибуты объекта, которые могут быть измерены или оценены.
W — вектор весов, который представляет важность или влияние каждого признака на итоговую классификацию. Каждый элемент в векторе W соответствует весу признака в X.
w0 — скалярное значение, известное как смещение или порог, которое корректирует уровень, на котором функция активируется, чтобы принять решение о классификации.
Процесс классификации с помощью этой функции заключается в подстановке вектора признаков объекта в уравнение, что приводит к получению числового результата. Этот результат затем интерпретируется следующим образом: если значение функции положительно, объект относится к одному классу, а если отрицательно — к другому. Таким образом, линейная граница решения создается в пространстве признаков, разделяя объекты на две группы в соответствии с их классификацией.
Важно отметить, что выбор вектора весов W и смещения w0 является важным этапом в процессе обучения классификатора. Эти параметры обычно определяются на основе обучающих данных — набора объектов с известной классификацией. Целью обучения является настройка W и w0 таким образом, чтобы линейная дискриминантная функция максимально точно разделяла объекты разных классов.
Этот метод широко используется из-за его простоты и эффективности во многих задачах классификации, особенно когда отношения между признаками и классами являются приблизительно линейными. Однако его эффективность может снижаться в случаях, когда данные сложно разделить линейно, что требует использования более сложных нелинейных методов.
Приведемпример:importnumpyasnp
importmatplotlib.pyplotasplt
# Генерациянабораданных
np.random.seed(0)
class1=np.random.randn(100, 2) +np.array([2, 2])
class2=np.random.randn(100, 2) +np.array([-2, -2])
# Визуализациянабораданных
plt.scatter(class1[:, 0], class1[:, 1], color='red', label='класс 1')
plt.scatter(class2[:, 0], class2[:, 1], color='blue', label='класс 2')
# Определение вектора весов и смещения
W=np.array([1, 1])
w0=0.5
# Функция для отрисовки линии разграничения
defdraw_decision_boundary(W, w0):
# Выбор двух точек для определения линии
x_values=np.array(plt.xlim())
y_values=-(W[0]/W[1]) *x_values- (w0/W[1])
plt.plot(x_values, y_values, 'k--', label='границаразграничения') # Используем 'k--' дляпунктирнойлинии
# Функция классификации
defclassify(X, W, w0):
returnnp.dot(X, W) +w0
# Тестовая точка (зеленая точка)
test_point=np.array([0.5, -0.5])
result=classify(test_point, W, w0)
# Визуализация тестовой точки и линии разграничения
draw_decision_boundary(W, w0)
plt.scatter(test_point[0], test_point[1], color='green', label='тестоваяточка')
plt.text(test_point[0], test_point[1], f' Классифицируетсякак{"класс 1"ifresult>0else"класс 2"}', color='green')
plt.legend()
plt.show()
6.2.7 Нелинейная дискриминантная функция(NDF)
Нелинейная дискриминантная функция представляет собой расширение линейной дискриминантной функции для ситуаций, когда данные не могут быть эффективно разделены линейной границей. В отличие от линейной дискриминантной функции, которая определяет границу разделения классов с помощью прямой линии (или гиперплоскости в многомерном пространстве), нелинейная функция позволяет формировать более сложные, криволинейные границы.
Основная идея заключается в использовании нелинейных комбинаций признаков для классификации объектов. Это может включать в себя квадратичные, кубические или другие степенные члены, а также тригонометрические функции или экспоненты, что позволяет создавать более сложные формы разделения между классами в пространстве признаков.
Напримерфункция является нелинейной из-за квадратичного члена x12. Это означает, что разделяющая граница между классами будет криволинейной в двумерном пространстве признаков (X1,X2).

Применение NDFактуально в случаях, когда взаимосвязь между признаками и классами является сложной и не может быть адекватно описана линейными моделями. Это позволяет повысить точность классификации в сложных задачах, где данные обладают сложной структурой или когда классы перекрываются в пространстве признаков.
Приведемпример:importnumpyasnp
importmatplotlib.pyplotasplt
# Генерациянабораданных
np.random.seed(0)
N=100 # Количество точек в каждом классе
r_inner=2
r_outer=4
# Внутренний круг
inner_circle=r_inner*np.random.rand(N, 2)
theta=2*np.pi*np.random.rand(N)
inner_circle[:,0] =r_inner*np.cos(theta)
inner_circle[:,1] =r_inner*np.sin(theta)
# Внешнийкруг
outer_circle=r_outer*np.random.rand(N, 2)
theta=2*np.pi*np.random.rand(N)
outer_circle[:,0] =r_outer*np.cos(theta)
outer_circle[:,1] =r_outer*np.sin(theta)
# Визуализация набора данных
plt.figure(figsize=(8, 8))
plt.scatter(inner_circle[:, 0], inner_circle[:, 1], color='red', label='Class 1')
plt.scatter(outer_circle[:, 0], outer_circle[:, 1], color='blue', label='Class 2')
# Тестовая точка (зеленая)
test_point=np.array([3, 1])
plt.scatter(test_point[0], test_point[1], color='green', label='Test Point')
# Нелинейная граница разделения (средний радиус)
average_radius= (r_inner+r_outer) /2
circle=plt.Circle((0, 0), average_radius, color='black', fill=False, linestyle='--', label='Decision Boundary')
plt.gca().add_artist(circle)
# Классификация с использованием нелинейной функции
defclassify_nonlinear(point):
distance=np.sqrt(point[0]**2+point[1]**2)
ifdistance<average_radius: # Простое условие на основе расстояния
return'Class 1'
else:
return'Class 2'
# Классификациятестовойточки
test_point_class=classify_nonlinear(test_point)
print(f"Тестовая точка классифицирована как: {test_point_class}")
plt.legend()
plt.show()
Тестовая точка классифицирована как: Class 2
Этот код иллюстрирует классификацию линейно неразделимых данных с помощью нелинейной границы. Два класса точек генерируются так, что один находится внутри круга, а другой - снаружи, создавая концентрические круги. Граница классификации, представленная пунктирным кругом, определяется средним радиусом между внутренним и внешним кругами. Зеленая тестовая точка классифицируется на основе ее расстояния от центра: если она находится внутри границы, она принадлежит внутреннему классу; если снаружи - внешнему.
6.2.8 Наивный байесовский классификатор: (NBC)
Наивный байесовский классификатор (NBC) — это простой вероятностный классификатор, основанный на применении теоремы Байеса с предположением о независимости признаков друг от друга внутри класса.
Работа NBC состоит в определении принадлежности объекта X к одному из классов C− или C+ на основе вероятностей.Классификатор сравнивает апостериорные вероятности P(C−∣X) и P(C+∣X)— вероятности того, что объект X принадлежит классу C− или C+, соответственно, после наблюдения X. Объект классифицируется к классу C−, если P(C−∣X)>P(C+∣X), и к классу C+ в противном случае.
Функция дискриминации g(X) для NBC определяется как разность апостериорных вероятностей для двух классов: g(X)=g−(X)−g+(X), где g−(X)=P(C−∣X) и g+(X)=P(C+∣X).
Апостериорная вероятность — это условная вероятность, обусловленная случайно наблюдаемыми данными.
Согласно теореме Байеса, апостериорная вероятность P(C−∣X) может быть выражена через вероятность наблюдения X при условии класса C−, P(X∣C−), априорную вероятность класса C−, P(C−), и полную вероятность наблюдения X, P(X):
PC-X=PXC-PС-PX. Аналогичное выражение справедливо для P(C+∣X).
В контексте NBC, где предполагается условная независимость признаков, вероятность P(X∣C−) раскладывается на произведение вероятностей каждого признака xi при условии класса C−:
и соответственно:6.3 Линейные дискриминантные функции
6.3.1 Граница решения, С+ и С-
Как мы видели ранее в этой главе, линейная дискриминантная функция имеет вид g(X)=WTX+b, где W - это столбец векторов размером p, а b - скаляр. g(X) делит пространство векторов на три части. Они следующие:
Граница решенияDB (DecisionBoundary)В случае линейных дискриминантных функций, g(X)= WTX+b=0 характеризует гиперплоскость (линию в двумерном случае) или границу решения. Граница решения, соответствующая g(X) (DBg), также может быть представлена как: DBg={X∣g(X)=0}
Отрицательное полупространство NHS (Negative Half Space)Это можно рассматривать как набор всех образцов, принадлежащих классу C−. Соответственно, отрицательное полупространство, соответствующее g(X) (NHSg), это набор: NHSg={X∣g(X)<0}=C−
Положительное полупространствоPHS (Positive Half Space)Это набор всех образцов, принадлежащих классу C+. Соответственно, положительное полупространство, соответствующее g(X) (PHSg), задается как: PHSg={X∣g(X)>0}=C+
Заметим, что каждая из этих частей является потенциально бесконечным набором. Однако обучающий набор данных и коллекция тестовых образцов, с которыми сталкиваются, конечны.
6.3.2 Линейная разделимость
Линейная разделимость — это концепция в области машинного обучения, которая относится к способности алгоритма классификации разделять набор данных на классы с помощью линейной функции.
Предположим, что у нас есть набор маркированных образцов X, состоящий из пар "объект-метка класса" (Xi,Ci), где i индексирует образцы в наборе.
Говорят, что набор данных X линейно разделим, если можно найти такие параметры линейной функции — вектор весов W и скалярное смещение b, — что для всех образцов из одного класса (например, C+) значение линейной функции WTXi+b будет больше нуля, а для всех образцов из другого класса (например, C−) — меньше нуля. Это означает, что существует гиперплоскость(линия в двухмерном пространстве), определяемая уравнением WTX+b=0, которая идеально разделяет образцы двух классов в пространстве признаков.
Использование линейных классификаторов становится релевантным, когда данные линейно разделимы, поскольку в таком случае классификатор сможет идеально разделить образцы на классы без ошибок на обучающем наборе. Примером линейно разделимых данных могут служить двумерные образцы, разделяемые прямой линией.
Если данные линейно разделимы, существует не одна, а бесконечное количество линейных дискриминантных функций (ЛДФ), которые могут идеально разделить эти классы, поскольку любая линия (или гиперплоскость в пространствах большей размерности), проходящая между двумя ближайшими точками разных классов, но не пересекающая их, может служить границей решения. Этот факт иллюстрируется на рисунках, где показаны различные возможные линии (гиперплоскости) разделения для линейно разделимого набора данных.

Для наглядности давайте рассмотрим два примера: один с линейно разделимыми данными и другой с линейно неразделимыми данными. Для этого я создам два набора точек на плоскости: один, который можно разделить прямой линией на два класса, и другой, для которого такое разделение невозможно.
Линейно разделимые данные
Допустим, у нас есть два класса точек на двумерной плоскости: один класс в верхнем левом и нижнем правом углах, а другой — в верхнем правом и нижнем левом углах. Эти точки можно разделить прямой линией.
Линейно неразделимые данные
Теперь представим другой набор точек, где один класс точек окружает другой класс точек, например, в виде круга. В этом случае невозможно провести одну прямую линию, которая бы разделила точки двух классов.
Давайте визуализируем оба этих примера с помощью Python.
import matplotlib.pyplot as plt
import numpy as np
# Генерация линейно разделимых данных
np.random.seed(0)
x1 = np.random.randn(100, 2) + np.array([-3, 3]) # Класс 1
x2 = np.random.randn(100, 2) + np.array([3, -3]) # Класс 2
# Генерация линейно неразделимых данных
theta = np.linspace(0, 2*np.pi, 100)
r = 2 + np.cos(5*theta) # Радиальная функция для "внутреннего" класса
x_inner = np.c_[r*np.cos(theta), r*np.sin(theta)]
x_outer = np.random.randn(100, 2) * 3 # "Внешний" класс
# Визуализация
fig, axs = plt.subplots(1, 2, figsize=(12, 6))
# Линейно разделимые данные
axs[0].scatter(x1[:, 0], x1[:, 1], label='Класс 1')
axs[0].scatter(x2[:, 0], x2[:, 1], label='Класс 2')
axs[0].set_title('Линейно разделимые данные')
axs[0].legend()
# Линейно неразделимые данные
axs[1].scatter(x_inner[:, 0], x_inner[:, 1], label='Класс 1')
axs[1].scatter(x_outer[:, 0], x_outer[:, 1], label='Класс 2')
axs[1].set_title('Линейно неразделимые данные')
axs[1].legend()
plt.show()
На визуализации представлены два набора данных:Линейно разделимые данные (слева): Здесь классы точек расположены таким образом, что их можно чётко разделить прямой линией. Один класс находится в верхнем левом и нижнем правом углах, а другой — в верхнем правом и нижнем левом.
Линейно неразделимые данные (справа): В этом примере один класс точек (внутренний) окружён другим классом точек (внешним). Невозможно провести одну прямую линию, которая бы разделила точки двух классов, что делает данные линейно неразделимыми.
6.3.3 Линейная классификация на основе линейной дискриминантной функции
Линейный классификатор описан соответствующей линейной дискриминантной функцией g(X)=WTX+b. WT,X, и bиграют важную роль в осмыслении принципа работы классификатора. Рассмотрим их более подробно.

Граница решения или гиперплоскость в контексте линейного классификатора представляет собой множество точек в пространстве признаков, где классификатор не может однозначно определить, к какому классу отнести данные точки — к положительному (C+) или отрицательному (C−). Математически эта граница определяется уравнением g(X)=WTX+b=0, где W — вектор весов, X — вектор признаков, а b — смещение. Точки, удовлетворяющие этому уравнению, лежат на гиперплоскости, которая делит пространство признаков на две части, каждая из которых ассоциируется с одним из классов.
Когда мы рассматриваем две различные точки X1 и X2, лежащие на этой гиперплоскости, мы видим, что для обеих точек выполняется условие WTX1+b=WTX2+b=0. Вычитая одно уравнение из другого, мы получаем WT (X1−X2)=0. Это указывает на то, что вектор W перпендикулярен вектору, соединяющему точки X1 и X2, и следовательно, перпендикулярен самой гиперплоскости границы решения. Это свойство имеет важное значение, так как оно определяет направление, в котором происходит изменение от одного класса к другому.
Ортогональность(перпендикулярность) вектора весов W к границе решения подразумевает, что направление максимального изменения значения функции g(X), то есть направление, в котором классификатор наиболее "уверен" в изменении классовой принадлежности, совпадает с направлением вектора W. Таким образом, вектор W не только определяет ориентацию границы решения, но и указывает, в каком направлении относительно этой границы находится положительный класс. Это связывает направление вектора W с распределением классов в пространстве признаков.
Положительное полупространство определяется как область пространства признаков, где любой образец X удовлетворяет условию g(X)=WTX+b>0. Это условие указывает на то, что образец принадлежит к положительному классу в контексте линейного классификатора. Давайте разберем это более подробно:
Роль смещения: Параметр b в линейной дискриминантной функции, определяет положение гиперплоскости относительно начала координат. Если рассмотреть значение g(X) в начале координат и предположить, что b>0, то даже при X=0 (начало координат), значение g(0)=b>0, что помещает начало координат в положительное полупространство. Это означает, что при положительном b, даже отсутствие признаков (нулевой вектор X) приведет к классификации образца как принадлежащего к положительному классу.
Если же b=0, то гиперплоскость проходит через начало координат, и значение g(X) для любой точки X, лежащей на гиперплоскости, будет равно нулю. В этом случае начало координат лежит непосредственно на границе решения.
Роль весов: Вектор весов W определяет ориентацию гиперплоскости границы решения в пространстве признаков. Если рассмотреть линейную дискриминантную функцию g(X) с b=0, то g(X)=WTX. Для образцов X, находящихся в положительном полупространстве, g(X)>0, что указывает на то, что W ориентирован в направлении увеличения признаков, соответствующих положительному классу.
То, что WTX>0, можно интерпретировать через косинус угла между векторами W и X. Поскольку косинус угла положителен, когда угол между векторами меньше 90 градусов, это означает, что вектор W направлен в сторону положительного полупространства, поддерживая классификацию образцов в этом полупространстве как принадлежащих к положительному классу.
Отрицательное полупространство определяется как область в пространстве признаков, где каждая точка X классифицируется как принадлежащая к отрицательному классу, то есть для этих точек выполняется условие g(X)<0. Это значит, что значение линейной дискриминантной функции g(X)=WTX+b для этих точек меньше нуля.
Если параметр смещения b равен нулю (b=0) и рассмотреть точку X из отрицательного класса, то условие WTX<0 указывает на то, что вектор признаков X ориентирован таким образом относительно вектора весов W, что угол θ между ними больше 90 градусов и меньше 270 градусов. Это подтверждает, что вектор весов W направлен к положительному полупространству, так как векторы, находящиеся в отрицательном полупространстве, образуют с W угол, выходящий за пределы прямого.
Когда параметр смещения b меньше нуля (b<0), любая точка Xв отрицательном полупространстве удовлетворяет условию g(X)=WTX+b<0. В таком случае, даже в начале координат (X=0) значение g(0)=b<0, помещая начало координат в отрицательное полупространство.

Таким образом, параметры W и b в линейной дискриминантной функции g(X)=WTX+b играют следующие роли:
Значение b определяет положение начала координат. Начало координат находится в положительном полупространстве (PHSg), если b>0; в отрицательном полупространстве (NHSg), если b<0; и находится на границе решения, если b=0.
Вектор весов W ортогонален границе решения и направлен к положительному полупространству. Это означает, что независимо от значения b, ориентация W остается постоянной, и все границы решений, соответствующие различным значениям b, параллельны друг другу.
6.4 Перцептрон
Перцептрон - это один из алгоритмов машинного обучения, используемый для бинарной классификации, то есть для задач, где необходимо определить, принадлежит ли объект к одному из двух возможных классов. Он работает на основе линейной дискриминантной функции, которая принимает входные данные и взвешивает их, чтобы сделать предсказание о принадлежности к классу.
В начале развития искусственного интеллекта перцептрон был одним из исследуемых алгоритмов, так как он лег в основу многих более сложных методов классификации, включая машины опорных векторов (SVM).
Линейная дискриминантная функция- это функция, которая помогает разделить или классифицировать входные данные (например, изображения, текст) на две группы (классы) с помощью линии (в 2D), плоскости (в 3D) или гиперплоскости (в более высоких измерениях).
gX=WTX+bэто математическое представление линейной дискриминантной функции, где gX- это значение функции для входного вектора X, W - вектор весов, b - смещение (или порог), а WT -транспонированный вектор весов. Суть в том, что в зависимости от значения gXмы можем определить, к какому классу относится входной вектор X.
Обучение перцептрона заключается в нахождении оптимальных значений для весов Wи смещения b, чтобы как можно точнее разделить два класса.
Расширенные векторы (Xa и Wa) - это трюк, используемый для упрощения математических операций, где к исходному вектору признаков X и вектору весов W добавляется дополнительное измерение для учета смещения b. Это позволяет интегрировать b в вектор весов и упрощает вычисления.
Классификация с помощью перцептрона происходит путем вычисления gXприсвоения X к классу C-если gX<0 и к классу C+ если gX> 0. Это означает, что если значение функции отрицательно, объект относится к одному классу, а если положительно - к другому.
Линейная разделимость- это предположение для успешного применения персептрона, что существует линия (плоскость или гиперплоскость), которая может безошибочно разделить все входные данные на два класса.
Метка класса (y)- это фактическое обозначение класса для каждого образца в данных. В контексте перцептрона y принимает значение -1 или +1, что соответствует двум классам (C- и C+).
Рассмотрим таблицу, иллюстрирующую процесс классификации с использованием алгоритма перцептрона. В таблице укажем шесть различных образцов (или паттернов), каждый из которых имеет классовую метку (+ или -), и два атрибута (x1 и x2). Также в таблице приведем результат умножения вектора весов WaT на вектор атрибутов xa для каждого образца.
Транспонированный вектор весов WaT представляет собой вектор весов, перевернутый так, чтобы строки стали столбцами (или наоборот). В данном случае он представлен как WaT=-14,1,5T, где апостроф (T) обозначает транспонирование. Это означает, что вектор весов переходит из горизонтального положения в вертикальное. В контексте перцептрона, вектор весов представляет собой коэффициенты, которые определяют важность каждого атрибута в процессе классификации.
Вектор атрибутов xa — это расширенный вектор атрибутов каждого образца, который включает в себя смещение (или порог) как первый элемент, и атрибуты x1 и x2 как второй и третий элементы соответственно.
Скалярное произведение (обозначается как WaTxa) вычисляется путем умножения соответствующих элементов двух векторов и последующего суммирования полученных произведений. Математически это можно записать как:
где "смещение" обычно равно 1 для учета порога активации нейрона.
Результат скалярного произведения используется для определения, какой класс будет присвоен каждому образцу. Если результат положительный, образец классифицируется как принадлежащий к классу C+, а если отрицательный — к классу C−.
y= −1 если X∈C-
y=+1если X∈C+Столбец "1" в таблице представляет собой константный признак, который добавляется к каждому вектору входных данных. Это делается для включения смещения (bias) в модель, которое позволяет алгоритму не только выполнять линейную классификацию с проходом через начало координат, но и сдвигать разделяющую границу от нуля. Таким образом, вместо функции классификации, определяемой только двумя признаками x1 и x2, добавляется константный признак (в данном случае со значением 1), что позволяет модели вычислить и использовать смещение.
Номер образца
Метка класса
-
-1
-1
-1-
-1
-1
-2++++Функция g(yX) описывается как умножение вектора Wa на вектор атрибутов Xa, умноженный на классовую метку y.Если образец принадлежит классу C-, g(X) = WaTxa< 0, что соответствует метке класса y = -1. Если образец принадлежит классу C+, g(X) = WaTxa> 0, что соответствует метке класса y = +1.
Таким образом, функция g(yX) будет положительной независимо от того, принадлежит ли X к классу C- или C+, что упрощает алгоритм обучения. В таблице показано, что вектор весов (-14, 1, 5)' корректно классифицирует все значения yXa.
В оставшейся части этой главы мы используем следующие обозначения в интересах краткости и простоты:
W используется для обозначения Wa, предполагая, что b - это первый элемент в W.X используется для обозначения yXa, предполагая, что X дополнен добавлением 1 как первого компонента, и вектор Xa умножается на y; получающийся вектор обозначается как X.
W обучается из обучающих данных.
Используется алгоритм обучения перцептрона для изучения W.
6.4.1 Алгоритм обучения перцептрона
Инициализация: Алгоритм начинается с инициализации счётчика итераций i нулём и весового вектора Wi нулевым вектором. Нулевой вектор означает, что все его компоненты равны нулю. Весовой вектор используется для определения границы решения в пространстве признаков.
Итерация по образцам: Далее алгоритм итеративно проверяет каждый образец обучающего набора (Xk для k от 1 до n, где n - количество образцов). Если текущий весовой вектор Wi неправильно классифицирует образец Xk (то есть произведение вектора весов и вектора признаков образца меньше или равно нулю), весовой вектор обновляется путём добавления к нему вектора признаков текущего образца. Это обновление предполагает перемещение границы решения ближе к текущему образцу, чтобы правильно его классифицировать. Счётчик итераций i увеличивается на единицу каждый раз, когда происходит обновление.
Повторение до сходимости: Шаг 2 повторяется для всего набора образцов до тех пор, пока в процессе полной итерации (или эпохи) по всем образцам не перестанет изменяться счётчик итераций i. Это означает, что алгоритм достиг сходимости и все образцы классифицируются правильно с текущим весовым вектором, или другими словами, алгоритм нашёл границу решения, которая правильно разделяет два класса в пространстве признаков.
importnumpyasnp
importmatplotlib.pyplotasplt
defperceptron_learning_algorithm(X, Y, max_epochs=1000):
n_samples, n_features = X.shape
W = np.zeros(n_features) # Инициализация весов нулевым вектором
i = 0# Инициализация счётчика итераций
epoch = 0# переменная используется epoch для отслеживания количества эпох для предотвращения бесконечного цикла в случае, если данные не линейно разделимы.
whileepoch<max_epochs:
i_old = i# Сохраняем предыдущее значение счётчика итераций для проверки сходимости
forkinrange(n_samples):
if (np.dot(W, X[k]) * Y[k]) <= 0: # Проверка условия неправильной классификации
W = W + X[k] * Y[k] # Обновление весов
i += 1# Увеличение счётчика итераций
ifi_old == i: # Проверка сходимостиbreak# Выход из цикла, если счётчик итераций не изменился за эпоху
epoch += 1returnW
# Генерация искусственного набора данных
np.random.seed(42) # Задает начальное значение для генератора случайных чисел для воспроизводимости результатов.
n_samples = 20# Определяет количество образцов в каждом из двух классов.
# Генерация положительных образцов:
# np.random.randn(n_samples, 2) создает двумерный массив (матрицу) из n_samples строк и 2 столбцов,
# заполненный случайными числами из стандартного нормального распределения (среднее = 0, стандартное отклонение = 1).
# Добавление [2, 3] смещает распределение так, чтобы средние значения по каждому измерению были равны 2 и 3 соответственно,
# что приводит к тому, что образцы группируются вокруг точки (2, 3) в двумерном пространстве.
X_positive = np.random.randn(n_samples, 2) + [2, 3]
# Генерация отрицательных образцов аналогичным образом, но с центром в точке (1, -2).
X_negative = np.random.randn(n_samples, 2) + [1, -2]
# Объединение положительных и отрицательных образцов в один набор данных:
# np.vstack((X_positive, X_negative)) объединяет массивы вертикально (по строкам),
# результатом является массив, в котором сначала идут все положительные, а затем все отрицательные образцы.
X = np.vstack((X_positive, X_negative))
# Создание меток классов для образцов:
# np.ones(n_samples) создает массив из n_samples элементов, все равные 1, что соответствует положительным образцам.
# -np.ones(n_samples) создает аналогичный массив, но со значениями -1, что соответствует отрицательным образцам.
# np.hstack((...)) объединяет массивы горизонтально (по столбцам), формируя вектор меток классов для всех образцов.
Y = np.hstack((np.ones(n_samples), -np.ones(n_samples)))
# Добавление единичного столбца для смещения:
# np.ones((2*n_samples, 1)) создает вертикальный массив (столбец) из единиц размером 2*n_samples строк на 1 столбец.
# Этот столбец затем добавляется к началу массива X с помощью np.hstack(...),
# что позволяет модели учитывать смещение (т.е. свободный член в линейном уравнении).
X = np.hstack((np.ones((2*n_samples, 1)), X))
# Обучениеперцептрона
weights = perceptron_learning_algorithm(X, Y)
print("Обученные веса:", weights)
# Визуализация результатов
plt.scatter(X_positive[:, 0], X_positive[:, 1], color='blue', marker='o', label='Класс +1')
plt.scatter(X_negative[:, 0], X_negative[:, 1], color='red', marker='x', label='Класс -1')
# Разделяющаялиния
x_values = np.linspace(np.min(X[:, 1]), np.max(X[:, 1]), 100)
y_values = -(weights[1]/weights[2])*x_values - (weights[0]/weights[2])
plt.plot(x_values, y_values, label='Разделяющаялиния')
plt.xlabel('X1')
plt.ylabel('X2')
plt.legend()
plt.grid(True)
plt.show()
Обученные веса: [0. 0.75824757 4.69036742]
6.4.1.1 Обучение булевым функциям
Для наглядности алгоритма воспользуемся примером булевой функции, а именно функцией "или". Соответствующая таблица истинности представлена в таблице ниже.
Таблица истинности для операции логического сложения (ИЛИ)Классификация с использованием векторов вида yXa
Номер образца
Метка класса
-1
-1Мы рассматриваем это как задачу с двумя классами, где выход 0 воспринимается как указание на отрицательный класс, а выход 1 - как индикатор положительного класса. После добавления и умножения с меткой класса y=−1 или +1, соответственно, для отрицательного или положительного классов, у нас есть данные, показанные в «Классификация с использованием векторов вида yXa»
Мы начинаем с W0=0,0,0T. Последовательные обновления W следующие:
W0неправильно классифицирует первый вектор-1,0,0T так как скалярное произведение между ними равно 0. Таким образом,W1=W0+-1,0,0T=-1,0,0T
W1неправильно классифицирует второй паттерн 1,0,1T, так как скалярное произведение равно -1 < 0. Таким образом,W2=W1+1,0,1T=0,0,1T
W2неправильно классифицирует третий паттерн 1,1,0T, скалярное произведение равно 0. Следовательно,W3=W2+1,1,0T=1,1,1T
Обратите внимание, W3 правильно классифицирует четвертый паттерн1,1,1T; скалярное произведение больше 3>0. Теперь мы снова проходим через паттерны, начиная с первого. ВесW3не удается классифицировать первый паттерн-1,0,0T так как скалярное произведение равно −1. Таким образом,W4=W3+-1,0,0T=0,1,1T
Обратите внимание,W4 не удается классифицировать первый паттерн правильно, даже если он классифицирует паттерны под номерами 2, 3 и 4. Таким образом,W5=W4+-1,0,0T=-1,1,1T
W5неправильно классифицирует второй паттерн, и поэтомуW6=W5+1,0,1T=0,1,2T
W6неправильно классифицирует первый паттерн после того, как правильно классифицировал паттерны под номерами 3 и 4, поэтомуW7=W6+-1,0,0T=-1,1,2T
W7неправильно классифицирует третий паттерн; следовательно,W8=W7+1,1,0T=0,2,2T
W8неправильно классифицирует первый паттерн. Таким образом,W9=W8+-1,0,0T=-1,2,2T. Обратите внимание, что W9классифицирует все четыре паттерна правильно. Таким образом, дискриминантная функция gX имеет форму gX=-1,2,21,x2,x2T. Следовательно, решающее правило имеет форму2x1+2x2=1

6.4.1.2 Неуникальность вектора весов в перцептроне
Вектор весов W, который использует перцептрон для классификации, не является уникальным. Это означает, что могут существовать несколько различных векторов весов, которые корректно классифицируют данные. Какой именно вектор весов будет найден, зависит от порядка, в котором алгоритм обрабатывает точки данных.
Существует два разных способа использования паттернов для обновления начального вектора весов W0=0,0,0T. Как только мы получаем W, которое классифицирует все паттерны правильно, мы прекращаем итерации.

Рассмотрим рисунок выше.
Есть четыре паттерна. Они принадлежат двум классам, как показано ниже:Отрицательный класс:1,1T,2,2T
Положительный класс:6,1T,7,1T
Аугментированные (усовершенствованные) паттерны после умножения на y это:
Отрицательный класс:X1=-1,-1,-1T,X2=-1,-2,-2T
Положительный класс:X3=1,6,1T,X4=1,7,1T
При использовании паттернов в последовательностиX1,X2,X3,X4, начальное значение вектора весов W0 принимается равным 0,0,0T, и алгоритм завершает работу на W4=-2,2,-3T. В результате получаем решающую границу в виде функции gx=2x1-3x2=2, которая на графике обозначается пунктирной линией (g(x)=2x1-3x2=2).
В случае, если паттерны используются в обратном порядке X4,X3,X2,X1, начиная с того же начального вектора весов W0=0,0,0T, находим W4=-2,3,-3T, который корректно классифицирует все четыре паттерна. Здесь решающая граница описывается функцией gx=3x1-3x2=2 и представлена на графике сплошной линией (g(x)=3x1-3x2=2).
6.4.1.3 Принцип работы алгоритма обучений
Алгебраический подход
Если весовой вектор Wiнеправильно классифицирует вектор Xk, то скалярное произведение WiTXkне больше 0. Обновленный вектор Wi+1 вычисляется как Wi+Xk, так что Wi+1TXk=Wi+XkTXk=WiTXk+XkTXk. Так как XkTXk=Xk2 всегда положительно (квадрат евклидовой нормы), то Wi+1TXk>XkTXk. Это означает, что Wi+1 лучше подходит для классификации Xk по сравнению с Wi, и скалярное произведение Wi+1TXk может быть положительным, даже если WiTXk не было таковым.
Другими словами, алгебраический подход заключается в том, что если точка данных неправильно классифицируется, то веса можно скорректировать таким образом, чтобы увеличить шанс правильной классификации этой точки при следующей итерации.
Проверка классификации: Для каждой точки данных алгоритм считает скалярное произведение вектора весов W и вектора признаков точки X. Если результат скалярного произведения не соответствует ожидаемой метке класса, точка считается неправильно классифицированной.
Обновление весов: В случае неправильной классификации веса корректируются путем добавления к ним вектора признаков неправильно классифицированной точки (если точка должна быть положительной) или вычитания этого вектора (если точка должна быть отрицательной). Это обновление делает следующую проверку этой точки более склонной к правильной классификации.
Итерации: Процесс повторяется, пока не будет достигнут критерий остановки, например, определенное количество итераций или отсутствие неправильно классифицированных точек.
importnumpyasnp
# Функция для обновления весов
defupdate_weights(W, X, y):"""
Обновляет веса для неправильно классифицированного вектора X.
Параметры:
- W: текущий вектор весов.
- X: вектор признаков, который был неправильно классифицирован.
- y: истинная метка класса для X, +1 или -1.
Возвращает:
- обновленный вектор весов."""
# Обновление весов
W_new = W + y * XreturnW_new
# Исходный вектор весов
W = np.array([0, 0, 0])
# Пример векторов признаков и их меток классов
X_samples = np.array([
[-1, 0, 0], #Пример 1
[1, 0, 1], # Пример 2
[1, 1, 0] # Пример 3
])
y_labels = np.array([-1, 1, 1]) # Метки классов
# Имитация процесса обучения
# zip в Python — это встроенная функция, которая используется для совместной итерации по элементам двух или более итерируемых объектов (например, списков, кортежей, словарей и т. д.), создавая при этом пары или группы элементов из этих объектов. Элементы объединяются по порядку из каждого итерируемого объекта.
# Здесь предполагается, что X_samples и y_labels — это два списка (или итерируемых объекта) одинаковой длины, где X_samples содержит некие образцы (например, данные признаков для машинного обучения), а y_labels содержит соответствующие метки или ответы.
# В каждой итерации цикла zip берет один элемент из X_samples и один элемент из y_labels, объединяет их в пару (кортеж) (X, y), и эта пара используется в теле цикла. Это продолжается до тех пор, пока не будут исчерпаны элементы в одном из итерируемых объектов. Если X_samples и y_labels разной длины, то zip остановится на самом коротком итерируемом объекте, и оставшиеся элементы в более длинном объекте будут проигнорированы.
forX, yinzip(X_samples, y_labels):
# Предсказание класса
prediction = np.dot(W, X)
# Проверкананеправильнуюклассификацию
if (prediction<= 0andy == 1) or (prediction>0andy == -1):
# Обновление весов в случае неправильной классификации
W = update_weights(W, X, y)
print(f"Обновленный вектор весов: {W}")
Обновленный вектор весов: [1 0 1]Геометрический подход

Геометрический подход дает наглядное представление о том, как обновление весов изменяет границу решения для правильной классификации образцов.
Неправильная классификация: Представьте, что у нас есть вектор весов Wi, который неправильно классифицирует точку P3. Граница решения, соответствующая Wi, (обозначена как DBi), не разделяет классы должным образом.
Обновление весов: Когда мы добавляем P3 к Wi, , мы фактически сдвигаем вектор весов так, чтобы он лучше классифицировал P3. Это можно визуализировать как дополнение до параллелограмма, где Wi+1 - это диагональ параллелограмма, образованного векторами Wi, и P3.
Изменение границы решения: Новый вектор весов Wi+1 теперь правильно классифицирует P3, и соответствующая граница решения (обозначена как DBi+1) теперь ортогональна Wi+1, обеспечивая лучшее разделение классов.
Эти подходы объясняют, как алгоритм перцептрона последовательно корректирует вектор весов при каждой неправильной классификации, постепенно улучшая разделение классов до тех пор, пока все образцы не будут правильно классифицированы, или пока не будет достигнуто определенное количество итераций.
6.4.1.4 Сходимость алгоритма перцептрона
Сходимость алгоритма перцептрона относится к свойству алгоритма перцептрона, гарантирующему, что если существует некое разделение данных, то алгоритм сможет найти разделяющую гиперплоскость для этих данных за конечное число шагов.
Теорема о сходимости алгоритма перцептрона была доказана Фрэнком Розенблаттом в 1957 году и утверждает, что если данные линейно разделимы, то алгоритм сойдется к оптимальной разделяющей гиперплоскости после конечного числа итераций обновления весов. Это значит, что алгоритм гарантированно найдет такой набор весов, при котором все образцы будут правильно классифицированы, если такое решение возможно.
Теорема сходимости перцептрона утверждает, что если существуют некоторые веса, которые могут корректно классифицировать обучающие данные, перцептрон сойдется к этим весам за конечное число шагов.
Разберем алгоритм:1. Инициализация
Перед началом обучения перцептрон инициализируется с заданными параметрами скорости обучения и числа итераций. Веса и смещение инициализируются нулями. Эти параметры будут адаптироваться в процессе обучения для минимизации ошибок в предсказаниях.
2. Обучение
Процесс обучения состоит из многократного прохождения по обучающему набору данных, где каждый элемент данных обрабатывается индивидуально. Для каждого примера вычисляется взвешенная сумма его признаков и смещения, после чего применяется функция активации для получения предсказания. Разница между предсказанным и истинным значением используется для обновления весов и смещения, что делается с учетом скорости обучения.
3. Активация
Функция активации в перцептроне — это шаговая функция, которая возвращает 1, если взвешенная сумма входов и смещения больше нуля, и 0 в противном случае. Это позволяет модели делать четкие бинарные предсказания.
4. Предсказание
После обучения перцептрон может быть использован для предсказания классов новых данных. Процесс предсказания аналогичен процессу обучения, но без обновления весов и смещения.
5. Визуализация границы решения
Для наглядности процесса обучения и его результатов можно визуализировать границу решения, которая разделяет классы в пространстве признаков. Это делается путем создания сетки значений и применения модели к каждой точке сетки.
6. Итеративное обучение и визуализация
С использованием класса FuncAnimation из библиотеки Matplotlib, можно создать анимацию, показывающую процесс обучения перцептрона по мере итеративного добавления обучающих данных и изменения границы решения.
# Импорт библиотеки NumPy для работы с массивами
import numpy as np
# Импорт библиотеки Matplotlib для создания графиков
import matplotlib.pyplot as plt
# Импорт класса FuncAnimation для создания анимаций
from matplotlib.animation import FuncAnimation
# Определение класса Perceptron
# В конструкторе класса Perceptron задаются начальные параметры, включая скорость обучения (learning_rate), количество итераций (n_iters), а также инициализируются переменные для весов (self.weights) и смещения (self.bias), которые будут определены в методе fit.
class Perceptron:
def __init__(self, learning_rate=0.01, n_iters=1000):
# Инициализация перцептрона с заданными параметрами скорости обучения и числа итераций
self.lr = learning_rate
self.n_iters = n_itersself.activation_func = self._unit_step_func # Функция активации используется для преобразования взвешенной суммы входных сигналов и смещения в предсказанное значение выходного сигнала. Возвращает 1, если взвешенная сумма больше 0, и 0 в противном случае. Это позволяет модели делать чёткие бинарные предсказания.
self.weights = None # Веса инициализируются в методе fit
self.bias = None # Смещение инициализируется в методе fit
self.weights_history = [] # Для хранения истории весов
# Метод fit отвечает за обучение модели. Веса инициализируются нулями, а затем в цикле для каждого примера из обучающего набора данных вычисляются предсказания и производится обновление весов и смещения.
def fit(self, X, y):
# Обучение модели на данных X и y
n_samples, n_features = X.shape
# Инициализация весов и смещения нулями
self.weights = np.zeros(n_features)
self.bias = 0
# Добавление начального состояния весов и смещения в историю
self.weights_history.append((self.weights.copy(), self.bias))
# Преобразование меток классов
y_ = np.array([1 if i > 0 else 0 for i in y])
# Основной цикл обучения
for _ in range(self.n_iters):
for idx, x_i in enumerate(X):
# Вычисление линейного преобразования и применение функции активации
linear_output = np.dot(x_i, self.weights) + self.bias
y_predicted = self.activation_func(linear_output)
# Обновление весов и смещения
update = self.lr * (y_[idx] - y_predicted)
self.weights += update * x_i
self.bias += update
# Запись в историю
self.weights_history.append((self.weights.copy(), self.bias))
# Функция _unit_step_func является шаговой функцией активации, которая используется для преобразования взвешенной суммы входов в бинарное предсказание.
def _unit_step_func(self, x):
return np.where(x > 0, 1, 0)
# Метод predict используется для вычисления предсказаний на новых данных. Он применяет обученные веса и смещение к данным и возвращает предсказанные классы.
def predict(self, X):
# Предсказание классов для новых данных
linear_output = np.dot(X, self.weights) + self.bias
y_predicted = self.activation_func(linear_output)
return y_predicted
# Функция plot_decision_boundary отвечает за визуализацию границы решения, создавая сетку возможных значений и используя модель для предсказания класса в каждой точке сетки.
def plot_decision_boundary(X, y, classifier, ax):
# Определение диапазона значений для осей
x1_min, x1_max = X[:, 0].min() - 1, X[:, 0].max() + 1
x2_min, x2_max = X[:, 1].min() - 1, X[:, 1].max() + 1
# Создание сетки для визуализации
xx1, xx2 = np.meshgrid(np.arange(x1_min, x1_max, 0.1),
np.arange(x2_min, x2_max, 0.1))
# Предсказание классов для каждой точки сетки
Z = classifier.predict(np.array([xx1.ravel(), xx2.ravel()]).T).reshape(xx1.shape)
# Визуализация границы решения и данных
ax.contourf(xx1, xx2, Z, alpha=0.4)
ax.scatter(X[:, 0], X[:, 1], c=y, s=10, edgecolor='k')
# Вывод весов и смещения
weights, bias = classifier.weights_history[-1]
ax.set_xlabel(f'Веса: {weights}, Смещение: {bias}')
# Установка начального числа для генератора случайных чисел
np.random.seed(1)
# Генерация случайных данных
X = np.random.randn(100, 2)
y = np.array([1 if x[0] + x[1] > 0 else 0 for x in X])
# Создание экземпляра перцептрона
p = Perceptron(learning_rate=0.1, n_iters=10)
# Подготовка объектов для визуализации
fig, ax = plt.subplots()
# Функция обновления для анимации
def update(frame):
ax.clear() # Очистка предыдущего состояния
p.fit(X[:frame+1], y[:frame+1]) # Обучение на части данных
plot_decision_boundary(X[:frame+1], y[:frame+1], p, ax) # Визуализация
ax.set_title(f'Итерации: {frame+1}') # Вывод номера итерации
# Использование FuncAnimation из Matplotlib позволяет создать анимацию, демонстрирующую процесс обучения и изменение границы решения. Функция update, вызываемая на каждом кадре анимации, обучает модель на части данных и обновляет визуализацию.
ani = FuncAnimation(fig, update, frames=range(1, X.shape[0]), interval=100)
plt.show() # Отображение анимации

Важно отметить, что сходимость алгоритма гарантирована только для линейно разделимых данных. Для данных, которые нельзя разделить линейно, алгоритм перцептрона может не сойтись к стабильному решению, что означает, что веса будут продолжать обновляться бесконечно, пытаясь найти решение, которое не существует.
6.4.2 Градиентный спуск
Градиентный спуск — это итеративный алгоритм оптимизации, целью которого является нахождение минимального значения функции потерь (или ошибки). Функция потерь оценивает, насколько хорошо работает модель на данном этапе обучения, выражая разницу между предсказанными значениями и реальными. Градиентный спуск стремится минимизировать эту функцию, подстраивая веса перцептрона таким образом, чтобы достигнуть наименьшей возможной ошибки.
В основе метода лежит концепция градиента функции, который представляет собой вектор частных производных функции потерь по каждому из весов. Градиент указывает направление наибольшего увеличения значения функции. Следовательно, двигаясь в направлении, противоположном градиенту (т.е., выполняя градиентный спуск), можно найти минимум функции.
Существует несколько вариантов градиентного спуска, отличающихся способом вычисления градиента:1. Пакетный градиентный спуск (Batch Gradient Descent): градиент вычисляется по всему набору данных, что обеспечивает стабильность направления спуска, но может быть вычислительно затратным на больших объемах данных.
2. Стохастический градиентный спуск (Stochastic Gradient Descent, SGD): градиент вычисляется для каждого обучающего примера отдельно, что делает процесс более случайным, но значительно ускоряет вычисления.
3. Мини-пакетный градиентный спуск (Mini-batch Gradient Descent): компромиссный вариант, при котором градиент вычисляется на небольших группах (пакетах) обучающих примеров, сочетая преимущества двух предыдущих методов.
Рассмотрим стохастический градиентный спуск на примере кода python, для этого нам нужно модифицировать метод fit, чтобы он обновлял веса после каждого примера обучения, а не после прохода по всему набору данных.
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.animation import FuncAnimation
class Perceptron:
def __init__(self, learning_rate=0.01, n_iters=1000):
self.lr = learning_rate # Скорость обучения
self.n_iters = n_iters # Количество итераций
self.activation_func = self._unit_step_func
self.weights = None # Веса
self.bias = None # Смещение
self.weights_history = [] # Для хранения истории весов
def fit(self, X, y):
n_samples, n_features = X.shape
# Инициализация весов и смещения нулями
self.weights = np.zeros(n_features)
self.bias = 0
# Добавление начального состояния весов и смещения в историю
self.weights_history.append((self.weights.copy(), self.bias))
y_ = np.array([1 if i > 0 else 0 for i in y])
# Основной цикл обучения
for _ in range(self.n_iters):
for idx, x_i in enumerate(X):
# Вычисление взвешенной суммы входов и смещения
linear_output = np.dot(x_i, self.weights) + self.bias
# Применение функции активации
y_predicted = self.activation_func(linear_output)
# Расчет ошибки
error = y_[idx] - y_predicted
# Градиентный спуск: обновление весов и смещения
# Веса обновляются в направлении, противоположном градиенту функции потерь
# Величина обновления определяется скоростью обучения и градиентом
self.weights += self.lr * error * x_i
self.bias += self.lr * error
# Запись в историю
self.weights_history.append((self.weights.copy(), self.bias))
def _unit_step_func(self, x):
# Функция активации: шаговая функция
return np.where(x > 0, 1, 0)
def predict(self, X):
# Вычисление предсказаний на новых данных
linear_output = np.dot(X, self.weights) + self.bias
y_predicted = self.activation_func(linear_output)
return y_predicted
# Функция plot_decision_boundary отвечает за визуализацию границы решения, создавая сетку возможных значений и используя модель для предсказания класса в каждой точке сетки.
def plot_decision_boundary(X, y, classifier, ax):
# Определение диапазона значений для осей
x1_min, x1_max = X[:, 0].min() - 1, X[:, 0].max() + 1
x2_min, x2_max = X[:, 1].min() - 1, X[:, 1].max() + 1
# Создание сетки для визуализации
xx1, xx2 = np.meshgrid(np.arange(x1_min, x1_max, 0.1),
np.arange(x2_min, x2_max, 0.1))
# Предсказание классов для каждой точки сетки
Z = classifier.predict(np.array([xx1.ravel(), xx2.ravel()]).T).reshape(xx1.shape)
# Визуализация границы решения и данных
ax.contourf(xx1, xx2, Z, alpha=0.4)
ax.scatter(X[:, 0], X[:, 1], c=y, s=10, edgecolor='k')
# Вывод весов и смещения
weights, bias = classifier.weights_history[-1]
ax.set_xlabel(f'Веса: {weights}, Смещение: {bias}')
# Установка начального числа для генератора случайных чисел
np.random.seed(1)
# Генерация случайных данных
X = np.random.randn(100, 2)
y = np.array([1 if x[0] + x[1] > 0 else 0 for x in X])
# Создание экземпляра перцептрона
p = Perceptron(learning_rate=0.1, n_iters=10)
# Подготовка объектов для визуализации
fig, ax = plt.subplots()
# Функция обновления для анимации
def update(frame):
ax.clear()
p.fit(X[:frame+1], y[:frame+1])
plot_decision_boundary(X[:frame+1], y[:frame+1], p, ax)
ax.set_title(f'Итерации: {frame+1}')
weights, bias = p.weights_history[-1]
ax.set_xlabel(f'Веса: {weights}, Смещение: {bias}')
ani = FuncAnimation(fig, update, frames=range(1, X.shape[0]), interval=100)
plt.show() # Отображение анимацииОбновление весов и смещения происходит внутри вложенного цикла по каждому образцу данных (в строках с for idx, x_i in enumerate(X):): В стохастическом градиентном спуске веса обновляются для каждого тренировочного примера, а не после прохода всего набора данных, как это делается в пакетном градиентном спуске.
Величина обновления весов зависит от ошибки конкретного образца, скорости обучения и значения признака (self.weights += self.lr * error * x_i): В стохастическом градиентном спуске градиент функции потерь оценивается на основе одного образца, и веса обновляются в направлении, противоположном градиенту, что позволяет алгоритму делать шаги к минимизации общей функции потерь.
Использование скорости обучения (learning_rate или self.lr в коде): Этот параметр контролирует размер шага при обновлении весов. В стохастическом градиентном спуске скорость обучения является гиперпараметром, который помогает в балансировке между скоростью сходимости и риском переобучения или застревания в локальных минимумах.
6.4.3 Нелинейно разделимые данные
Перцептрон в своей базовой форме обучается нахождению линейной границы, разделяющей два класса. Однако, если мы знаем или можем предположить форму нелинейной границы, разделяющей классы, алгоритм обучения перцептрона может быть адаптирован для обучения нелинейного дискриминанта.
Первым шагом является преобразование входных данных так, чтобы они соответствовали ожидаемой форме нелинейности. Это может быть достигнуто через применение функций, которые вводят нелинейность в исходные данные, таких как полиномиальные функции, логарифмические преобразования или тригонометрические функции. Выбор конкретной функции или комбинации функций зависит от характера данных и предполагаемой формы нелинейной границы.
После преобразования данных алгоритм обучения перцептрона применяется к модифицированным данным. В этом контексте, вместо поиска линейной границы, перцептрон будет обучаться на нахождение границы, которая соответствует применённому преобразованию. Это позволяет перцептрону эффективно разделять данные, которые в исходном пространстве признаков не могут быть разделены линейно.
Допустим, данные разделяются кривой второго порядка (параболой). Мы можем преобразовать каждый входной вектор путём добавления нового признака, который представляет собой квадрат исходного признака (или признаков, если их несколько). Такое преобразование изменит исходное пространство данных таким образом, что перцептрон сможет найти линейную границу в новом пространстве, которая соответствует нелинейной границе в исходном пространстве.
Рассмотрим на примере кода python:
import numpy as np # Импортируем библиотеку NumPy для работы с массивами
import matplotlib.pyplot as plt # Импортируем библиотеку Matplotlib для визуализации данных
from matplotlib.animation import FuncAnimation # Импортируем класс для создания анимации
class Perceptron:
def __init__(self, learning_rate=0.01, n_iters=1000):
self.lr = learning_rate # Устанавливаем скорость обучения
self.n_iters = n_iters # Устанавливаем количество итераций обучения
self.activation_func = self._unit_step_func # Устанавливаем функцию активации (единичная ступенчатая функция)
self.weights = None # Инициализируем веса
self.bias = None # Инициализируем смещение
self.weights_history = [] # Список для хранения истории изменений весов
def fit(self, X, y): # Метод для обучения модели
n_samples, n_features = X.shape # Получаем количество образцов и признаков
self.weights = np.zeros(n_features) # Инициализируем веса нулями
self.bias = 0 # Инициализируем смещение нулём
self.weights_history.append((self.weights.copy(), self.bias)) # Записываем начальные веса и смещение в историю
y_ = np.array([1 if i > 0 else 0 for i in y]) # Преобразуем метки классов
for _ in range(self.n_iters): # Основной цикл обучения
for idx, x_i in enumerate(X): # Перебираем все образцы
linear_output = np.dot(x_i, self.weights) + self.bias # Вычисляем линейный выход
y_predicted = self.activation_func(linear_output) # Получаем предсказание модели
update = self.lr * (y_[idx] - y_predicted) # Вычисляем обновление для весов
self.weights += update * x_i # Обновляем веса
self.bias += update # Обновляем смещение
self.weights_history.append((self.weights.copy(), self.bias)) # Записываем обновлённые веса и смещение в историю
def _unit_step_func(self, x): # Определение единичной ступенчатой функции
return np.where(x > 0, 1, 0) # Функция возвращает 1, если x > 0, иначе 0
def predict(self, X): # Метод для прогнозирования классов новых образцов
linear_output = np.dot(X, self.weights) + self.bias # Вычисляем линейный выход
y_predicted = self.activation_func(linear_output) # Получаем предсказание
return y_predicted # Возвращаем предсказанные метки классов
def add_nonlinear_features(X): # Функция для добавления нелинейных признаков
X_nonlinear = np.zeros((X.shape[0], X.shape[1] * 3)) # Создаём массив для нелинейных признаков
X_nonlinear[:,:2] = X # Копируем исходные признаки
X_nonlinear[:, 2] = X[:, 0] ** 2 # Добавляем квадрат первого признака
X_nonlinear[:, 3] = X[:, 1] ** 2 # Добавляем квадрат второго признака
X_nonlinear[:, 4] = X[:, 0] * X[:, 1] # Добавляем взаимодействие между признаками
return X_nonlinear # Возвращаем расширенный набор признаков
# Функция add_nonlinear_features добавляет к исходным данным нелинейные признаки, такие как квадраты и произведения исходных признаков, что позволяет перцептрону находить нелинейные границы решений.
def plot_decision_boundary(X, y, classifier, ax): # Функция для отрисовки границы решений
x1_min, x1_max = X[:, 0].min() - 1, X[:, 0].max() + 1 # Определяем границы для оси X1
x2_min, x2_max = X[:, 1].min() - 1, X[:, 1].max() + 1 # Определяем границы для оси X2
xx1, xx2 = np.meshgrid(np.arange(x1_min, x1_max, 0.1), np.arange(x2_min, x2_max, 0.1)) # Создаём сетку для графика
# Применяем модель к каждой точке сетки
Z = classifier.predict(add_nonlinear_features(np.array([xx1.ravel(), xx2.ravel()]).T)).reshape(xx1.shape)
ax.contourf(xx1, xx2, Z, alpha=0.4) # Заполняем области разными цветами в зависимости от предсказаний модели
ax.scatter(X[:, 0], X[:, 1], c=y, s=10, edgecolor='k') # Отмечаем исходные точки данных
weights, bias = classifier.weights_history[-1] # Получаем последние веса и смещение
ax.set_xlabel(f'Веса: {weights}, Смещение: {bias}') # Выводим информацию о весах и смещении на график
np.random.seed(1) # Устанавливаем начальное значение для генератора случайных чисел
X = np.random.randn(100, 2) # Генерируем случайный набор данных
# Определяем метки классов на основе нелинейной границы решения
y = np.array([1 if x[0]**2 + x[1]**2 < 1 else 0 for x in X])
X_nonlinear = add_nonlinear_features(X) # Добавляем нелинейные признаки к данным
p = Perceptron(learning_rate=0.1, n_iters=10) # Создаём экземпляр перцептрона
fig, ax = plt.subplots() # Создаём фигуру для графика
def update(frame): # Функция для обновления анимации
ax.clear() # Очищаем текущий график
p.fit(X_nonlinear[:frame+1], y[:frame+1]) # Обучаем модель на ограниченном наборе данных
plot_decision_boundary(X_nonlinear[:frame+1], y[:frame+1], p, ax) # Отрисовываем границу решений
ax.set_title(f'Итерация: {frame+1}') # Устанавливаем заголовок графика
ani = FuncAnimation(fig, update, frames=range(1, X.shape[0]), interval=100) # Создаём анимацию
plt.show() # Отображаем график

Функция plot_decision_boundary используется для визуализации границы принятия решений классификатора на графике. Функция сначала определяет минимальные и максимальные значения для двух признаков (x1 и x2), которые используются в данных. К этим значениям добавляется небольшой отступ (-1 и +1), чтобы границы решений не прилегали вплотную к крайним точкам данных. С помощью функции np.meshgrid, создается сетка координат для пространства признаков. Эта сетка покрывает область, определенную минимальными и максимальными значениями признаков, с некоторым шагом (в данном случае 0.1).Сетка используется для вычисления предсказаний классификатора в каждой точке пространства признаков. Для каждой точки сетки (то есть для каждой комбинации значений x1 и x2) вычисляется предсказание классификатора.
Поскольку сетка была создана из двух массивов координат x1 и x2, для предсказания используются соответствующие значения из этих массивов. Для этого значения x1 и x2 "выпрямляются" с помощью метода ravel, объединяются в массив признаков, к которому применяются нелинейные преобразования (если это необходимо), и для полученных точек данных делается предсказание с помощью метода predict классификатора. После этого предсказания преобразуются обратно в форму, соответствующую сетке, с помощью метода reshape. С помощью функции contourf из библиотеки Matplotlib создается заливка областей на графике, которые соответствуют различным классам, определенным предсказаниями классификатора на сетке. Разные классы обозначаются разными цветами. На график также наносятся исходные точки данных с использованием функции scatter, где цвет точек соответствует их истинным меткам классов. В нижней части графика выводится информация о текущих значениях весов и смещении классификатора, что позволяет наблюдать за их изменением в процессе обучения.
6.4.4 Творческие подходы к использованию перцептрона
В предыдущем разделе мы не рассматривали способность перцептрона решать задачи, которые выходят за рамки простых линейно разделимых сценариев. Однако рассмотрим некоторые хитрости.
Например, операция исключающее "ИЛИ" (XOR), когда требуется, чтобы выходной сигнал активировался при включении одного из двух входных сигналов, но не при их одновременной активации. Данная задача представляет собой пример нелинейной классификации, которая не может быть решена с помощью одного перцептрона, использующего стандартные линейные веса и пороги. Однако, переформулировав условия задачи или изменив представление входных данных, можно обучить перцептрон решать XOR задачу, что демонстрирует возможность преодоления ограничений линейной классификации за счет творческого подхода к представлению данных.
Другой пример, демонстрирующий сложную задачу, с которыми может справиться перцептрон, — это функция нечётной четности, при которой выходной сигнал активируется, если активировано нечетное количество из трех входных сигналов. Эта задача также требует нестандартного подхода к обучению перцептрона и представлению входных данных, но так же решаема.
В математике и информатике часто используются функции, которые могут принимать на вход разные данные (называемые аргументами) и в зависимости от них выдавать какой-то результат. Иногда эти функции обладают особенностью, которая называется "инвариантностью перестановки". Это значит, что если мы возьмем функцию с двумя и более входами и поменяем местами эти входы, результат функции не изменится. Пример такой функции — это операция "исключающее ИЛИ" (XOR), которая ведет себя одинаково независимо от порядка входных данных.
Некоторые свойства изображений, например наличие хотя бы одного черного пикселя, тоже не изменяются, если мы переставим пиксели местами — это свойство инвариантности перестановки.
Положительная нормальная форма — это специальный способ записи математических функций, который использует только операции "И" и "НЕ" для их описания.
Минтермы — это простые составляющие более сложных функций, которые содержат все переменные функции. Если функция инвариантна к перестановке, то есть ее результат не зависит от порядка переменных, то все эти минтермы могут быть выражены очень просто, и у всех них будет одинаковый "вес" или коэффициент в формуле функции. Это полезно, потому что упрощает анализ и работу с такими функциями.
Рассмотрим демонстрацию на python, когда результат функции XOR не зависит от порядка входных переменных и что положительная нормальная форма является действительным способом представления такой функции.
# Давайте покажем на примере кода Python, что такое инвариантность перестановки и как работает положительная нормальная форма.
# Функция исключающее "ИЛИ" (XOR) на двух переменных
def xor(x1, x2):
return (x1 and not x2) or (not x1 and x2)
# Проверим инвариантность перестановки для функции XOR
a = 0
b = 1
result1 = xor(a, b)
result2 = xor(b, a)
# Положительная нормальная форма (PNF) для функции XOR
def xor_pnf(x1, x2):
return (1 - x1)*(x2) + (x1)*(1 - x2)
# Проверим работу положительной нормальной формы
pnf_result1 = xor_pnf(a, b)
pnf_result2 = xor_pnf(b, a)
print(result1, result2, pnf_result1, pnf_result2)1 True 1 1
Инкрементные вычисления — это когда вы можете добавлять данные к уже проведенным расчетам без необходимости пересчитывать все с нуля. Это полезно, например, когда вы анализируете изображения и хотите добавить информацию о новых пикселях к уже проведенному анализу.
Например если у нас есть изображение из l пикселей и мы хотим определить, есть ли хотя бы один черный пиксель, то мы можем использовать инкрементный подход. ФункцияgX, которая определяет наличие черного пикселя, может быть вычислена как сумма значений всех пикселей x1+x2+…+xl, где каждый x — это значение конкретного пикселя (например, 1 если пиксель черный и 0 если нет).
Если изображение станет больше и к нему добавятся новые пиксели, то мы можем просто добавить их значения к уже вычисленной сумме, не пересчитывая все заново. Это делает процесс более быстрым и эффективным.
В отличие от этого, некоторые другие функции, такие как функции определения нечетности количества черных пикселей и функция исключающего ИЛИ (xor), не поддаются инкрементным вычислениям. Это означает, что при изменении размера изображения их значения нужно вычислять заново, что является более сложной и время затратной задачей. Эти функции требуют использования всех элементов данных (всех пикселей изображения), и они сильно зависят от порядка, в котором данные подаются на вход, что делает их вычисление сложным для оптимизации с помощью инкрементных методов.
6.5 Метод опорных векторов на основе ядра (Kernel-Based SVM)
В 1960-1970-х годах Владимир Вапник и Алексей Червоненкис предложили метод опорных векторов, который был значительным улучшением по сравнению с перцептроном благодаря использованию оптимизации зазора между классами. Это позволило SVM обеспечить лучшую обобщающую способность на тестовых данных. Тем не менее, SVM по-прежнему оставался линейным классификатором и также не мог решать задачи с нелинейно разделимыми данными.
Прорыв в преодолении этого ограничения произошел с введением ядерного трюка, который позволил использовать линейный SVM в нелинейных задачах. Ядерная функция, используемая в этом методе, позволяла отображать исходные данные в пространство более высокой размерности, где данные становились линейно разделимыми. Таким образом, алгоритм SVM стал способен находить оптимальное решение даже для нелинейно разделимых данных, что было невозможно для перцептрона.
Это открытие способствовало широкому распространению метода опорных векторов в машинном обучении и стало основой для разработки различных ядер, таких как полиномиальное, радиально-базисное (RBF) и сигмоидное, каждое из которых обладает своими преимуществами в определенных условиях.
Метод опорных векторов (SVM) полезен для работы с нелинейной классификацией на основе линейной дискриминантной функции в многомерном (ядерном) пространстве. Линейный SVM широко используется в приложениях, связанных с работой в многомерных пространствах. Однако, в пространствах низкой размерности, SVM на основе ядра является популярным нелинейным классификатором. Он использует ядерный трюк, который позволяет нам работать в пространстве входных данных вместо работы с потенциально многомерным, даже теоретически бесконечным размерным, ядерным (функциональным) пространством. Также ядерный трюк стал настолько популярным, что он используется в различных других алгоритмах распознавания образов и машинного обучения.
6.5.1 Нелинейно разделимые данные
Рассмотрим случай, когда данные не являются линейно разделимыми. Это означает, что данные нельзя разделить прямой линией на два класса так, что каждый класс лежит только по одну сторону линии. В контексте машинного обучения и, в частности, методов опорных векторов (Support Vector Machines, SVM), это ситуация, когда невозможно найти гиперплоскость, которая бы полностью разделяла данные на два класса без ошибок.
Опишем, что делать, если данные не поддаются линейному разделению:Невозможно найти такие веса W и смещение b, чтобы выражение WTX+b=-1, если X принадлежит отрицательному классу, и WTX+b=1, если X принадлежит положительному классу.
Не существует зазора (маржи), который можно было бы максимизировать, поэтому максимизация зазора не имеет смысла. Многие практические проблемы подпадают под эту категорию, где данных не разделяются чёткой границей.
Невозможно найти оптимальную гиперплоскость, как было рассмотрено в предыдущей главе, для случаев, когда данные не линейно разделимы.
Вместо этого создается зазор и оптимизируется, что подразумевает игнорирование некоторых точек данных для создания этого зазора.

Рассмотрим двумерные точки данных, показанные на рисунке.
Положительный паттерн ошибочно классифицирован, если ошибка e1 больше 1. Отрицательный паттерн ассоциируется с ошибкой e2<1, как показано на рисунке. Здесь нет неправильной классификации.
Мы хотели бы минимизировать такие ошибки. Поэтому мы включаем термин, соответствующий сумме таких ошибок в критериальную функцию. Таким образом, задача оптимизации сводится к:minW12W2+Ci=1n ei
Если при классификации Xi нет ошибки, то ei=0. Так же eiне может быть отрицательной, поэтому ei≥0 для всех i.
Аналогично, ограничения теперь могут быть ослаблены как WTXi+b≻1+ei , если yi=-1 и WTXi+b <1 - ei еслиyi=1
Заметим, что введение ошибки ei для паттерна Xi из C+ обеспечивает удовлетворение соответствующего ограничения. Существует три возможности:
Ошибка ei=0. В этом случае Xi находится на опорной плоскости (WTXi+b=1) и поэтому правильно классифицирован.
Если ei<1 тогда WTXi+b >1 - ei>0. Таким образом, Xi будет правильно классифицирован. Однако, если Xi на границе.
Если. ei≥1, тогда WTXi+b≤0. Таким образом, Xi будет неправильно классифицирован.
Аналогичный анализ может быть проведен для паттернов в C−.
Заметим, что независимо от того, находится ли Xi в C+yi=1 или Xi в C-yi=-1, мы имеем yiWTXi+b≥1-ei и также ei≥0 для всех i.
Задача оптимизации включает в себя минимизацию нормы вектора весов W и штраф за ошибки классификации, взвешенный параметром C. Ограничения моделируются таким образом, чтобы обеспечить некоторую устойчивость к ошибкам (т.е. некоторые точки данных могут находиться на неправильной стороне границы, но это допускается в пределах маржи ошибки).
6.5.2 Формулировка с мягкими полями
В контексте обучения перцептрона, концепция "мягких полей" или "мягкого зазора" необходима для создания более гибких и адаптивных моделей. Эта концепция позволяет перцептрону обрабатывать данные, которые не могут быть идеально разделены линейной границей. В этой главе мы подробно рассмотрим, как формулировка с мягкими полями применяется в обучении перцептрона, чтобы обеспечить улучшенную обработку перекрывающихся данных и шума.
Давайте рассмотрим, что такое "Формулировка с мягкими полями", на примере кода, который создает и визуализирует классификатор машинного обучения, разделяющий два класса данных.
В качестве первого шага, мы задаем пример данных в виде двумерных точек, где каждая точка принадлежит одному из двух классов. Например, точки X = [[3, 3], [3, 4], [2, 3], [1, 1], [1, 3], [2, 2]] представляют собой координаты, а массив y = [1, 1, 1, -1, -1, -1] указывает класс каждой точки. Здесь мы имеем два класса: 1 и -1.
Далее, мы создаем классификатор с использованием метода опорных векторов (SVM) с линейным ядром, указывая параметр C=1.0. Этот параметр C является ключом к пониманию "Формулировки с мягкими полями". Он определяет, насколько гибко наш классификатор относится к ошибкам: меньшее значение делает модель более терпимой к ошибкам (то есть к нарушениям маржи), в то время как большее значение требует более строгого разделения между классами без ошибок.
После обучения классификатора на наших данных, мы визуализируем результаты. На графике точки разных классов отмечены разными цветами, и вы можете видеть, как классификатор строит разделяющую линию (или границу) между классами. Также на графике отображены "мягкие поля" вокруг этой границы, представленные пунктирными линиями. Эти поля показывают, где модель может допускать ошибки при классификации, чтобы в целом достичь лучшей точности.
Опорные векторы – это те точки данных, которые лежат ближе всего к разделяющей границе. Они играют ключевую роль в определении положения этой границы, и на графике они выделены черными окружностями.
from sklearn import svm # Импортируем модуль svm из библиотеки sklearn для работы с машинами опорных векторов
import numpy as np # Импортируем библиотеку NumPy для работы с массивами
import matplotlib.pyplot as plt # Импортируем модуль pyplot из библиотеки matplotlib для визуализации данных
# Пример данных: двумерные точки и их метки классов
X = np.array([# Создаем массив точек в двумерном пространстве, где каждая точка представлена парой значений (x, y)
[3, 3],
[3, 4],
[2, 3],
[1, 1],
[1, 3],
[2, 2]
])
y = np.array([1, 1, 1, -1, -1, -1]) # Создаем массив меток классов для каждой точки, где 1 и -1 представляют разные классы
# Создаем классификатор SVM с линейным ядром и мягким зазором
clf = svm.SVC(kernel='linear', C=1.0) # Инициализируем классификатор SVM с линейным ядром; параметр C контролирует мягкость поля
# Обучаем классификатор
clf.fit(X, y) # Обучаем SVM на наших данных, позволяя ему найти оптимальную решающую границу между классами
# Визуализация
plt.scatter(X[:, 0], X[:, 1], c=y, s=50, cmap='autumn') # Визуализируем точки данных, окрашивая их в соответствии с метками классов
ax = plt.gca() # Получаем текущие оси графика для дальнейшей настройки
xlim = ax.get_xlim() # Получаем текущий диапазон по оси X
ylim = ax.get_ylim() # Получаем текущий диапазон по оси Y
# Создаем сетку для оценки модели
xx = np.linspace(xlim[0], xlim[1], 30) # Генерируем последовательные значения по оси X для создания сетки
yy = np.linspace(ylim[0], ylim[1], 30) # Генерируем последовательные значения по оси Y для создания сетки
YY, XX = np.meshgrid(yy, xx) # Создаем двумерную сетку координат
xy = np.vstack([XX.ravel(), YY.ravel()]).T # Преобразуем сетку в список координатных пар для оценки
Z = clf.decision_function(xy).reshape(XX.shape) # Оцениваем решающую функцию SVM на всех точках сетки
# Рисуем решающую границу и поля
ax.contour(XX, YY, Z, colors='k', levels=[-1, 0, 1], alpha=0.5,
linestyles=['--', '-', '--']) # Рисуем контуры на уровнях -1, 0 и 1, обозначающие поля и решающую границу
# Отмечаем опорные векторы
ax.scatter(clf.support_vectors_[:, 0], clf.support_vectors_[:, 1], s=100,
linewidth=1, facecolors='none', edgecolors='k') # Визуализируем опорные векторы большими точками с черными краями
plt.show() # Отображаем график
В этом коде используется мягкий полей (задается параметром C), что позволяет некоторым точкам данных нарушать маржу для достижения лучшего обобщения, когда данные не линейно разделимы. Решающая граница (линия, обозначенная -), поля (линии, обозначенные --) и опорные векторы (отмечены крупными точками) визуализируются для наглядности работы SVM.
6.5.3 Классификация с использованием метода опорных векторов с кривыми
Метод опорных векторов (SVM) традиционно ассоциируется с задачами бинарной классификации, но его принципы могут быть успешно адаптированы и для решения задач многоклассовой классификации. В этой главе мы рассмотрим, как SVM может быть применен для классификации данных на несколько классов, обозначим стратегии и алгоритмы, позволяющие это сделать, и приведем примеры.
Для реализации многоклассовой классификации с использованием SVM, принято использовать два основных подхода: «один против одного» (OvO) и «один против всех» (OvA).
1. Один против одного (OvO): Этот метод предполагает создание бинарного классификатора для каждой пары классов. Если у нас есть N классов, то потребуется обучить (N(N-1)/2) классификаторов. Каждый классификатор обучается на данных только двух классов. При классификации нового примера используется голосование: объект относится к тому классу, который чаще всего выбирался классификаторами.
2. Один против всех (OvA): В этом случае для каждого класса создается отдельный классификатор, который отличает этот класс от всех остальных. Таким образом, если у нас есть N классов, нам потребуется обучить N классификаторов. Для классификации нового примера выбирается класс, классификатор которого дает наибольшую оценку уверенности.
Большинство современных библиотек машинного обучения, такие как scikit-learn, предоставляют встроенную поддержку многоклассовой классификации с использованием SVM, автоматически применяя один из вышеупомянутых методов. При реализации важно учитывать выбор ядра, параметры регуляризации и масштабирование признаков для улучшения производительности модели.
После рассмотрения метода опорных векторов (SVM) в контексте многоклассовой классификации, важно упомянуть вариацию этого метода, известную как Nu-SVM, или Nu-SVC для задач классификации. Nu-SVM представляет собой альтернативный подход к классическому SVM, который позволяет пользователю контролировать количество опорных векторов и ошибок через параметр «nu», который лежит в диапазоне от 0 до 1.
Nu-SVM сохраняет основные принципы SVM, адаптируя их для обеспечения большей гибкости в выборе компромисса между количеством опорных векторов и маржой ошибки. Это делает Nu-SVM особенно полезным для задач, где требуется более тонкая настройка модели или когда данные содержат много шума.
Переход от традиционного SVM к Nu-SVM в контексте многоклассовой классификации позволяет воспользоваться всеми преимуществами Nu-SVM, включая его способность эффективно обрабатывать как линейные, так и нелинейные задачи классификации с использованием различных ядер.
Классификатор NuSVC является одной из реализаций метода опорных векторов (Support Vector Machine, SVM) в библиотеке sklearn для задач классификации.
NuSVC стоит за "Nu-Support Vector Classification". Параметр "Nu" представляет собой верхнюю границу доли неверно классифицированных примеров и нижнюю границу доли векторов поддержки относительно общего числа обучающих примеров. Это позволяет пользователю контролировать количество векторов поддержки и ошибок через один параметр nu, который принимает значения от 0 до 1. Класс NuSVC поддерживает ядра и может работать с линейными и нелинейными данными, что делает его гибким инструментом для решения разнообразных задач классификации.
Библиотека sklearn содержит так же и другие варианты SVM:SVC: Основной класс для классификации с помощью метода опорных векторов. Он предлагает гибкость в выборе ядер (линейное, полиномиальное, радиально-базисное и сигмоидальное) и подходит как для линейных, так и для нелинейных задач.
LinearSVC: Это упрощенная версия SVC, предназначенная для линейной классификации. Она обычно работает быстрее на больших наборах данных и не поддерживает использование ядер, поскольку предполагается линейность разделения классов.
SVR и NuSVR: Эти классификаторы используются для задач регрессии. SVR соответствует классическому методу опорных векторов для регрессии, в то время как NuSVR использует параметр nu аналогично NuSVC для контроля количества векторов поддержки.
Выбор между SVC, NuSVC и LinearSVC зависит от конкретной задачи и набора данных. Если данные линейно разделимы или набор данных велик, предпочтительнее использовать LinearSVC из-за его высокой производительности. Для нелинейно разделимых данных можно использовать SVC или NuSVC с подходящим ядром.
Для демонстрации многоклассовой классификации с использованием SVM на практическом примере, мы использовали библиотеку scikit-learn и ее класс SVC из модуля svm. Однако, прежде чем перейти к коду, важно обсудить, как именно мы можем использовать scikit-learn для решения нашей задачи. Библиотека scikit-learn предлагает широкий спектр инструментов для машинного обучения, в том числе различные алгоритмы классификации, регрессии и кластеризации. В контексте многоклассовой классификации SVM, особый интерес представляют функции и классы, предоставляемые модулем svm.
Модуль svm содержит не только класс SVC, который мы использовали для создания SVM-классификатора, но и другие полезные инструменты и модели, такие как LinearSVC, предназначенный для линейной классификации. Важно понимать, что в зависимости от выбранного типа ядра и параметров, процесс обучения и классификации может значительно отличаться, что, в свою очередь, может повлиять на производительность модели.
Используя синтетически сгенерированный набор данных, основанный на логической операции XOR, мы демонстрируем сложность задачи классификации, возникающую из-за нелинейности распределения классов. В качестве инструмента классификации выбрана модель NuSVC, предоставляемая библиотекой sklearn, которая позволяет найти оптимальную нелинейную решающую границу между классами данных. Результаты обучения модели визуализированы с использованием matplotlib.pyplot, что позволяет наглядно оценить эффективность метода SVM в условиях нелинейной разделимости классов. Визуализация решающей поверхности и распределения исходных данных подчеркивает способность SVM адаптироваться к сложным структурам данных и обеспечивать высокую точность классификации. Данное исследование подтверждает потенциал применения метода опорных векторов в широком спектре задач машинного обучения, требующих эффективной обработки нелинейно разделимых данных.
# Импортируем необходимые библиотеки
import numpy as np
import matplotlib.pyplot as plt
# Импортирует модуль svm из библиотеки sklearn. svm (Support Vector Machines) — это набор методов машинного обучения, используемых для классификации, регрессии и других задач.
from sklearn import svm
# Создает двумерную сетку координат с помощью функции meshgrid. np.linspace(-3, 3, 500) создает равномерно распределенные точки в диапазоне от -3 до 3 (включительно), всего 500 точек. xx и yy представляют собой матрицы координат X и Y соответственно для этой сетки.
xx, yy = np.meshgrid(np.linspace(-3, 3, 500), np.linspace(-3, 3, 500))
# Устанавливает начальное значение для генератора случайных чисел, чтобы результаты были воспроизводимы.
np.random.seed(0)
# Генерирует 300 случайных точек (векторов) с двумя координатами, следуя нормальному распределению.
X = np.random.randn(300, 2)
# Создает массив меток классов, используя операцию "исключающее ИЛИ" (XOR). Если одна из координат точки положительна, а другая отрицательна (или наоборот), то результат будет True (1), иначе False (0).
Y = np.logical_xor(X[:, 0] > 0, X[:, 1] > 0) # Создаем метки классов с помощью XOR
# Создает объект классификатора NuSVC, который является одной из реализаций SVM (Support Vector Machine) для классификации.
clf = svm.NuSVC()
# Обучает классификатор clf на данных X с метками Y.
clf.fit(X, Y)
# Вычисляет значение функции решения для каждой точки на сетке. np.c_[xx.ravel(), yy.ravel()] создает массив точек сетки. ravel() преобразует матрицы в одномерные массивы, а np.c_ объединяет их в массив координат точек.
Z = clf.decision_function(np.c_[xx.ravel(), yy.ravel()])
# Преобразует массив Z обратно в форму матрицы, соответствующую форме сетки xx и yy, чтобы можно было визуализировать результат на графике.
Z = Z.reshape(xx.shape)
# Отображает матрицу Z в виде изображения на графике. Параметры задают способ интерполяции, диапазон осей, соотношение сторон, начальную точку (нижний левый угол) и цветовую карту.
plt.imshow(Z, interpolation='nearest',
extent=(xx.min(), xx.max(), yy.min(), yy.max()), aspect='auto',
origin='lower', cmap=plt.cm.BuGn)
# Добавляет контурные линии на график, показывающие границу решения (levels=[0] означает, что линии будут нарисованы там, где функция решения равна 0).
contours = plt.contour(xx, yy, Z, levels=[0], linewidths=2)
# Отображает точки данных X на графике. Цвет точек указывается в соответствии с их метками Y, используя цветовую карту Paired.
plt.scatter(X[:, 0], X[:, 1], s=30, c=Y, cmap=plt.cm.jet)
# Убирает метки на осях X и Y.
plt.xticks(())
plt.yticks(())
# Задаем границы отображаемой области
plt.axis([-3, 3, -3, 3])
# Показываем график
plt.show()
6.5.4 Многоклассовая классификация с использованием метода опорных векторов
Во множестве приложений машинного обучения часто встречаются данные, разделенные на множество категорий. Метод опорных векторов также подходит для решения задач классификации с многими категориями. Чтобы работать с многоклассовой классификацией, применяют методы, позволяющие свести её к бинарной, в том числе стратегии "Один против всех" и "Один против одного". Давайте проведем сравнительное исследование по этой теме.
Рассмотрим, как метод опорных векторов применяется для многоклассовой классификации, на примере определения видов ирисов по их форме при помощи датасета Iris. Мы оценим работу четырех разных типов ядер SVM: линейного, полиномиального, радиально-базисного (RBF) и метода LinearSVC, анализируя первые два параметра датасета — длину и ширину лепестков. Мы воспользуемся уже знакомыми методами для создания сетки координат и визуализации границ, которые разделяют классы в моделях.
Обратим внимание на то, как различные ядра SVM осуществляют разделение пространства параметров на классы и как они адаптируются к особенностям данных, что будет продемонстрировано на графиках для каждой модели. Также стоит сравнить эффективность линейного ядра с RBF и полиномиальными ядрами и оценить влияние параметра регуляризации C на итоги классификации.
import numpy as np # Импортируем библиотеку NumPy для работы с массивами
import matplotlib.pyplot as plt # Импортируем модуль pyplot из библиотеки matplotlib для построения графиков
from sklearn import svm, datasets # Scikit-learn (sklearn) - это библиотека машинного обучения для Python, предлагающая различные инструменты для моделирования и анализа данных. Модуль svm предоставляет инструменты для работы с алгоритмами Support Vector Machines, а datasets содержит стандартные наборы данных, включая Iris.
def make_meshgrid(x, y, h=.02): # Эта функция создает сетку координат, которая будет использоваться для визуализации границ решений. x и y - массивы значений признаков, а h - шаг сетки.
# Здесь определяются пределы для координатной сетки, расширяя минимальные и максимальные значения x и y, чтобы границы были немного за пределами экстремальных точек данных.
x_min, x_max = x.min() - 1, x.max() + 1 # Вычисляем минимальное и максимальное значения для x
y_min, y_max = y.min() - 1, y.max() + 1 # Вычисляем минимальное и максимальное значения для y
xx, yy = np.meshgrid(np.arange(x_min, x_max, h), np.arange(y_min, y_max, h)) # np.meshgrid создает координатную сетку, которая представляет собой двумерный массив точек. np.arange генерирует значения в заданных пределах с шагом h.
return xx, yy # Возвращаем сетку координат
def plot_contours(ax, clf, xx, yy, **params): # Функция предназначена для отрисовки контуров решений классификатора на графике. ax - это объект оси matplotlib, clf - классификатор, xx и yy - координатные сетки.
Z = clf.predict(np.c_[xx.ravel(), yy.ravel()]) # np.c_ объединяет массивы xx и yy по последней оси, а ravel превращает их в одномерные массивы. clf.predict выполняет предсказания на каждой точке сетки.
Z = Z.reshape(xx.shape) # Преобразуем результаты предсказаний в форму сетки
out = ax.contourf(xx, yy, Z, **params) # Рисуем контуры решений
return out # Возвращаем нарисованные контуры
# Импортируем некоторые данные для экспериментов
iris = datasets.load_iris() # Загружаем датасет Iris
X = iris.data[:,:2] # Берем первые два признака из датасета Iris
y = iris.target # Берем метки классов
C = 1.0 # Параметр регуляризации SVM
# Создаем экземпляры моделей SVM и обучаем их на данных
models = (svm.SVC(kernel='linear', C=C), svm.LinearSVC(C=C, max_iter=10000), svm.SVC(kernel='rbf', gamma=0.7, C=C), svm.SVC(kernel='poly', degree=3, gamma='auto', C=C))
models = (clf.fit(X, y) for clf in models) # Обучаем модели
# Заголовки для графиков
titles = ('SVC с линейным ядром', 'LinearSVC (линейное ядро)', 'SVC с RBF ядром', 'SVC с полиномиальным ядром (степень 3)')
fig, sub = plt.subplots(2, 2, figsize=(10, 8)) # Создаем фигуру и подграфики
plt.subplots_adjust(wspace=0.2, hspace=0.2) # Настраиваем расстояние между подграфиками
X0, X1 = X[:, 0], X[:, 1] # Берем отдельно значения признаков
xx, yy = make_meshgrid(X0, X1) # Создаем сетку координат для отрисовки
# Для каждой модели и соответствующего заголовка рисуем контуры и точки
for clf, title, ax in zip(models, titles, sub.flatten()):
plot_contours(ax, clf, xx, yy, cmap='winter', alpha=0.8) # Используем цветовую карту "winter" для контуров (зеленый и синий)
ax.scatter(X0, X1, c=y, cmap=plt.cm.Greys, s=40, edgecolors='w') # Рисуем точки белым цветом с черными краями
ax.set_xlim(xx.min(), xx.max()) # Устанавливаем пределы для оси X
ax.set_ylim(yy.min(), yy.max()) # Устанавливаем пределы для оси Y
ax.set_xlabel('Длина чашелистика') # Подписываем ось X
ax.set_ylabel('Ширина чашелистика') # Подписываем ось Y
ax.set_xticks(()) # Убираем деления на оси X
ax.set_yticks(()) # Убираем деления на оси Y
ax.set_title(title) # Устанавливаем заголовок для подграфика
plt.show() # Показываем график
Выводы к главе:Метод опорных векторов (SVM) является мощным инструментом машинного обучения, применяемым для задач классификации и регрессии, в том числе для многоклассовой классификации, где он стремится разделить данные на несколько классов, максимизируя зазор между ними с помощью оптимальных гиперплоскостей. В многоклассовой классификации используются стратегии, такие как один против всех и один против одного, для обучения классификаторов, разделяющих классы. Для работы с нелинейно разделимыми данными SVM использует ядровой трюк, позволяющий производить классификацию в пространствах более высокой размерности без необходимости явного перехода в эти пространства, с использованием таких ядерных функций, как полиномиальное, радиально-базисное и сигмоидальное ядро. Важно также правильно выбрать параметры, включая параметр регуляризации C и параметры ядерной функции, для контроля баланса между максимизацией зазора и минимизацией ошибки классификации, что влияет на способность модели к обобщению. Хотя SVM эффективен на данных с четкими границами классов и хорошо справляется с высокоразмерными данными, он может быть чувствителен к выбору параметров и требователен к вычислительным ресурсам, особенно в задачах многоклассовой классификации и при больших объемах данных.
Проверь себя
Какая идея лучше всего описывает фокус главы "Перцептрон и методы опорных сигналов (SVM)"?
В машинном обучении теоретические определения полезно проверять на численных примерах и визуализациях.
Какие действия помогают закрепить материал главы?
Пройти тест