📖 Документация Qumir

← Вернуться в Playground

← Все примеры

Перцептрон

Линейный классификатор по правилу Розенблатта для двумерных точек. Программа генерирует случайную разделяющую прямую, создаёт обучающую и тестовую выборки с отступом (маржой) от границы, обучает перцептрон и оценивает точность классификации. Это один из простейших алгоритмов машинного обучения -- прародитель нейронных сетей.

Задача

Дано множество точек на плоскости $[-1,1]^2$, разделённых на два класса ($y = +1$ и $y = -1$) прямой линией $a x_1 + b x_2 + c = 0$. Нужно по обучающей выборке найти веса $w_1, w_2$ и смещение $b$ (bias), задающие разделяющую прямую:

$w_1 x_1 + w_2 x_2 + \text{bias} = 0$

Точки, для которых выражение положительно, относятся к классу $+1$, а для которых отрицательно -- к классу $-1$.

Линейная разделимость означает, что существует прямая, которая полностью разделяет точки двух классов. Чтобы гарантировать это, точки, лежащие слишком близко к разделяющей прямой (ближе маржи $\delta$), отбрасываются при генерации данных.

Идея алгоритма

  1. Генерируем случайную прямую-«учителя» $a x_1 + b x_2 + c = 0$ с нормированными коэффициентами.
  2. Создаём обучающую выборку из 4000 точек: для каждой точки $(x_1, x_2)$ определяем класс по знаку $a x_1 + b x_2 + c$. Точки ближе $\delta = 0.1$ к прямой отбрасываем (они в «зоне неопределённости»).
  3. Создаём тестовую выборку из 20000 точек аналогично, с небольшим шумом в метках (около 1% инвертированных меток).
  4. Инициализируем веса нулями: $w_1 = 0, w_2 = 0, \text{bias} = 0$.
  5. Обучаем по правилу Розенблатта: проходим по обучающей выборке, и если точка классифицирована неверно ($y \cdot (w_1 x_1 + w_2 x_2 + \text{bias}) \leq 0$), корректируем веса: $w := w + \eta \cdot y \cdot x$, $\text{bias} := \text{bias} + \eta \cdot y$.
  6. Если за целую эпоху (проход по всем данным) не было ни одной ошибки -- останавливаемся досрочно.
  7. Вычисляем точность (accuracy) на обучающей и тестовой выборках.

Разбор

Гиперпараметры

| гиперпараметры
цел Ntrain, Ntest, epochs
вещ eta, delta
вещ noise_te         | доля шума в метках теста (для нереализуемости 100%)
Ntrain := 4000
Ntest := 20000
epochs := 50
eta := 0.1
delta := 0.1      | маржа для отбора точек
noise_te := 0.01  | ~1% инвертируем метки на тесте

Генерация прямой-учителя и обучающей выборки

Случайная прямая задаётся коэффициентами $a, b, c$, которые нормируются для удобства. При генерации обучающих данных точки с расстоянием до прямой меньше $\delta$ отбрасываются, чтобы гарантировать разделимость.

| параметры прямой-учителя (a*x1 + b*x2 + c = 0)
вещ la, lb, lc
la := rand(-1.0, 1.0)
lb := rand(-1.0, 1.0)
lc := rand(-0.2, 0.2)
| нормируем (не обязательно, просто косметика)
вещ norm
norm := sqrt(la*la + lb*lb)
если norm <> 0.0 то
  la := la / norm
  lb := lb / norm
  lc := lc / norm
все

| генерация train с отбрасыванием по марже delta
цел i
вещ x1, x2, s, vabs, y
i := 0
нц пока i < Ntrain
  x1 := rand(-1.0, 1.0)
  x2 := rand(-1.0, 1.0)
  s := la*x1 + lb*x2 + lc
  vabs := s; если vabs < 0.0 то vabs := -vabs все
  если vabs >= delta то
    y := 1.0; если s < 0.0 то y := -1.0 все
    X1tr[i] := x1; X2tr[i] := x2; Ytr[i] := y
    i := i + 1
  все
кц

Цикл обучения перцептрона

На каждом проходе (эпохе) проверяем все обучающие точки. Если предсказание неверное (маржа $y \cdot \text{pred} \leq 0$), обновляем веса по правилу Розенблатта. Если за эпоху не было ошибок -- выходим досрочно.

| модель: w ∈ R^2, bias
вещ w1, w2, bias
w1 := 0.0; w2 := 0.0; bias := 0.0

| обучение персептрона
цел e, mistakes
вещ pred, margin
нц для e от 0 до epochs-1
  mistakes := 0
  нц для i от 0 до Ntrain-1
    pred := w1*X1tr[i] + w2*X2tr[i] + bias
    margin := Ytr[i] * pred
    если margin <= 0.0 то
      w1 := w1 + eta * Ytr[i] * X1tr[i]
      w2 := w2 + eta * Ytr[i] * X2tr[i]
      bias := bias + eta * Ytr[i]
      mistakes := mistakes + 1
    все
  кц
  | если ошибок нет — выходим раньше
  если mistakes = 0 то выход все
кц

Подсчёт точности

Функция accuracy считает долю правильно классифицированных точек: для каждой точки вычисляет предсказание и сравнивает его знак с истинной меткой.

алг вещ accuracy(цел n, вещ таб X1[0:n-1], вещ таб X2[0:n-1], вещ таб Y[0:n-1], вещ w1, вещ w2, вещ bias)
нач
  цел i, correct
  вещ pred, y, acc
  correct := 0
  нц для i от 0 до n-1
    pred := w1*X1[i] + w2*X2[i] + bias
    y := 1.0; если pred < 0.0 то y := -1.0 все
    если y = Y[i] то correct := correct + 1 все
  кц
  acc := (1.0 * correct) / n
  знач := acc
кон

Вывод результатов

вещ acc_tr, acc_te
acc_tr := accuracy(Ntrain, X1tr, X2tr, Ytr, w1, w2, bias)
acc_te := accuracy(Ntest,  X1te,  X2te,  Yte,  w1, w2, bias)

вывод "perceptron: Ntrain=", Ntrain, ", Ntest=", Ntest, ", epochs=", epochs, ", eta=", eta, ", delta=", delta, нс
вывод "teacher line: a=", la, ", b=", lb, ", c=", lc, нс
вывод "train acc=", acc_tr, ", test acc=", acc_te, нс

Полная программа

| Персептрон для линейно разделимых 2D-точек.
| Задача: порождаем обучающую и тестовую выборки из квадрата [-1,1]^2.
| Истинная разметка задаётся случайной прямой a*x1 + b*x2 + c = 0.
| Чтобы гарантировать разделимость, отбрасываем точки ближе маржи δ к прямой.
| Обучаем персептрон по правилу Розенблатта и печатаем точность train/test.

алг main
нач
  | гиперпараметры
  цел Ntrain, Ntest, epochs
  вещ eta, delta
  вещ noise_te         | доля шума в метках теста (для нереализуемости 100%)
  Ntrain := 4000
  Ntest := 20000
  epochs := 50
  eta := 0.1
  delta := 0.1      | маржа для отбора точек
  noise_te := 0.01  | ~1% инвертируем метки на тесте

  | массивы данных
  вещ таб X1tr[0:Ntrain-1], X2tr[0:Ntrain-1], Ytr[0:Ntrain-1]
  вещ таб X1te[0:Ntest-1],  X2te[0:Ntest-1],  Yte[0:Ntest-1]

  | параметры прямой-учителя (a*x1 + b*x2 + c = 0)
  вещ la, lb, lc
  la := rand(-1.0, 1.0)
  lb := rand(-1.0, 1.0)
  lc := rand(-0.2, 0.2)
  | нормируем (не обязательно, просто косметика)
  вещ norm
  norm := sqrt(la*la + lb*lb)
  если norm <> 0.0 то
    la := la / norm
    lb := lb / norm
    lc := lc / norm
  все

  | генерация train с отбрасыванием по марже delta
  цел i
  вещ x1, x2, s, vabs, y
  i := 0
  нц пока i < Ntrain
    x1 := rand(-1.0, 1.0)
    x2 := rand(-1.0, 1.0)
    s := la*x1 + lb*x2 + lc
    vabs := s; если vabs < 0.0 то vabs := -vabs все
    если vabs >= delta то
      y := 1.0; если s < 0.0 то y := -1.0 все
      X1tr[i] := x1; X2tr[i] := x2; Ytr[i] := y
      i := i + 1
    все
  кц

  | генерация test
  i := 0
  нц пока i < Ntest
    x1 := rand(-1.0, 1.0)
    x2 := rand(-1.0, 1.0)
    s := la*x1 + lb*x2 + lc
    vabs := s; если vabs < 0.0 то vabs := -vabs все
    если vabs >= delta то
      y := 1.0; если s < 0.0 то y := -1.0 все
      | внесём небольшой шум в метки теста
      вещ r
      r := rand(0.0, 1.0)
      если r < noise_te то y := -y все
      X1te[i] := x1; X2te[i] := x2; Yte[i] := y
      i := i + 1
    все
  кц

  | модель: w ∈ R^2, bias
  вещ w1, w2, bias
  w1 := 0.0; w2 := 0.0; bias := 0.0

  | обучение персептрона
  цел e, mistakes
  вещ pred, margin
  нц для e от 0 до epochs-1
    mistakes := 0
    нц для i от 0 до Ntrain-1
      pred := w1*X1tr[i] + w2*X2tr[i] + bias
      margin := Ytr[i] * pred
      если margin <= 0.0 то
        w1 := w1 + eta * Ytr[i] * X1tr[i]
        w2 := w2 + eta * Ytr[i] * X2tr[i]
        bias := bias + eta * Ytr[i]
        mistakes := mistakes + 1
      все
    кц
    | если ошибок нет — выходим раньше
    если mistakes = 0 то выход все
  кц

  | точности
  вещ acc_tr, acc_te
  acc_tr := accuracy(Ntrain, X1tr, X2tr, Ytr, w1, w2, bias)
  acc_te := accuracy(Ntest,  X1te,  X2te,  Yte,  w1, w2, bias)

  вывод "perceptron: Ntrain=", Ntrain, ", Ntest=", Ntest, ", epochs=", epochs, ", eta=", eta, ", delta=", delta, нс
  вывод "teacher line: a=", la, ", b=", lb, ", c=", lc, нс
  вывод "train acc=", acc_tr, ", test acc=", acc_te, нс
кон

| --- Вспомогательные функции ---

| точность классификации при данных весах
алг вещ accuracy(цел n, вещ таб X1[0:n-1], вещ таб X2[0:n-1], вещ таб Y[0:n-1], вещ w1, вещ w2, вещ bias)
нач
  цел i, correct
  вещ pred, y, acc
  correct := 0
  нц для i от 0 до n-1
    pred := w1*X1[i] + w2*X2[i] + bias
    y := 1.0; если pred < 0.0 то y := -1.0 все
    если y = Y[i] то correct := correct + 1 все
  кц
  acc := (1.0 * correct) / n
  знач := acc
кон

▶ Запустить пример