ML: Лінійна модель
Вступ
У цьому документі розглядається багатовимірна лінійна модель. Вона має самостійне значення в деяких задачах машинного навчання і лежить в основі складних архітектур глибоких нейронних мереж. Обговорюються математичні формулювання моделі та аналітичний пошук її параметрів. Наведені приклади коду на Python які можна знайти у файлі: ML_Line_Model.ipynb.
Розглянемо $n_x$-вимірний простір ознак. Кожен об'єкт у цьому просторі описується вектором $\mathbf{x}$. Задача полягає в перетворенні вихідного простору ознак в інший, $n_y$-вимірний простір $\mathbf{y}$. Величини $\mathbf{y}$, самі по собі, можуть мати практичний сенс і тоді це регресія або бути новими ознаками об'єктів, що визначають ймовірності в задачі класифікації.
Лінійна модель є найпростішим зв'язком входу $\mathbf{x}$ і виходу $\mathbf{y}$. Її матричний та індексний записи мають вигляд:
$$ \mathbf{y} = \mathbf{b} + \mathbf{x}\cdot\mathbf{w}, ~~~~~~~~~~~~~~~~~~~~ y_\alpha = b_\alpha + \sum_\beta (x_\beta\, w_{\beta\alpha}) $$У процесі навчання необхідно підібрати оптимальні параметри моделі: матрицю вектор $b_\beta$ і $w_{\alpha\beta}$. Лінійна модель є першим нетривіальним наближенням розкладу в ряд Тейлора довільної функції $\mathbf{y}=f(\mathbf{x}\, \boldsymbol{\omega})$.
Лінійна модель як гіперплощина
Розглянемо геометричну інтерпретацію лінійної моделі.
У $n$-вимірному просторі кожна точка описується $n$ дійсними координатами $\mathbf{x}=\{x_1,...,x_n\}$.
Як і у звичайному 3-вимірному просторі, площина задається вектором нормалі
$\boldsymbol{\omega}=\{w_1,...,w_n\}$ (перпендикуляр до площини)
і довільною точкою
$\mathbf{x}_0=\{x_{01},...,x_{0n}\}$,
що лежить у цій площині (фіксує зміщення площини вздовж нормалі).
Коли $n > 3$ площину прийнято називати гіперплощиною.
Відстань $d$ від гіперплощини до деякої точки $\mathbf{x}=\{x_1,...,x_n\}$ обчислюється за формулою ($\boldsymbol{\omega}^2=1$):
$$ d = b+\mathbf{x}\cdot\boldsymbol{\omega}, ~~~~~~\text{де}~~~~~~~~~~ b ~=~ -\mathbf{x}_0\cdot\boldsymbol{\omega} ~=~ -( x_{01}w_1 + ... + x_{0n}w_{n} ). $$При цьому $d > 0$, якщо точка $\mathbf{x}$ лежить з того боку гіперплощини, куди вказує вектор $\boldsymbol{\omega}$ і $d < 0$, якщо з протилежного. Коли $d=0$ - точка $\mathbf{x}$ лежить у гіперплощині ($b+\mathbf{x}\cdot\boldsymbol{\omega} = 0$ - її рівняння).
Зміна параметра $b$ зсуває площину паралельним чином у просторі. Якщо $b$ зменшується, то площина зміщується в напрямку вектора $\boldsymbol{\omega}$ (відстань $d$ менша), а якщо $b$ збільшується - площина зміщується проти вектора $\boldsymbol{\omega}$. Це безпосередньо випливає з наведеної вище формули.
◄
Запишемо вектор $\mathbf{x}-\mathbf{x}_0$, що починається в точці $\mathbf{x}_0$
(що лежить у площині)
і спрямований у точку $\mathbf{x}$ (див. малюнок праворуч; вектори складаються за правилом трикутника).
Положення точки $\mathbf{x}_0$ обрано в основі вектора $\boldsymbol{\omega}$,
тому $\boldsymbol{\omega}$ і $\mathbf{x}-\mathbf{x}_0$ колінеарні (лежать на одній прямій).
Якщо вектор $\boldsymbol{\omega}$ одиничний ($\boldsymbol{\omega}^2=1$), то скалярний добуток векторів
$\mathbf{x}-\mathbf{x}_0$ і $\boldsymbol{\omega}$ дорівнює відстані точки $\mathbf{x}$ до площини:
$$
d = (\mathbf{x}-\mathbf{x}_0)\cdot \boldsymbol{\omega} ~=~ -\mathbf{x}_0\cdot\boldsymbol{\omega} + \mathbf{x}\cdot\boldsymbol{\omega}
~=~ b + \mathbf{x}\cdot\boldsymbol{\omega}.
$$
Якщо довжина $\omega=|\boldsymbol{\omega}|$ вектора $\boldsymbol{\omega}$ нормалі до гіперплощини відрізняється від одиниці, то $d$ у $\omega$ разів більше ($\omega > 1$) або менше ($\omega < 1$) евклідової відстані у $n$-вимірному просторі. Коли вектори $\mathbf{x}-\mathbf{x}_0$ і $\boldsymbol{\omega}$ спрямовані в протилежні сторони: $d < 0$. ►
Якщо простір має $n$ вимірів,
то гіперплощина це $(n-1)$-вимірний об'єкт.
Вона ділить весь простір на дві
частини.
Для наочності розглянемо 2-вимірний простір.
Гіперплощиною в ньому буде пряма лінія (одновимірний об'єкт).
Праворуч на малюнку кружечок зображає одну точку простору,
а хрестик - іншу. Вони розташовані по різні боки від лінії (гіперплощини).
Якщо довжина вектора $\boldsymbol{\omega}$ набагато більша за одиницю, то і відстані
$d$
від точок до площини за модулем будуть суттєво більшими за одиницю.
Регресії (передбачення дійсного числа)
Найпростішим прикладом регресії є одновимірна лінійна функція $y = b + w \,x$,
у якій параметр $w$ характеризує нахил прямої, а параметр $b$ - зсув (зміщення) по осі $y$ від початку координат.
Наявність подібного зв'язку між $x$ і $y$, зазвичай, легко виявити візуально на графіку даних.
У багатовимірному випадку $y=b + w_1x_1+...w_n x_n$ візуалізувати дані складніше. Щоб переконатися в адекватності лінійної моделі, необхідно знайти оптимальні параметри і обчислити величину помилки (порівнявши її з помилками інших моделей, зокрема з тривіальною $y=b=\text{const}$).
Логістична регресія (задача класифікації)
У задачі класифікації об'єкт $\mathbf{x}$ потрібно віднести до одного або декількох
класів, загальне число $C$ яких фіксоване.
Зазвичай, результатом класифікації є $C$ умовних ймовірностей $p(c|\mathbf{x})$ того,
що об'єкт з ознаками $\mathbf{x}$ належить до $c$-того класу: $c\in [0...C-1]$.
При використанні в задачі класифікації лінійної моделі, її виходи $y_\alpha$ "пропускають" через нелінійну функцію $z_\alpha=f(y_\alpha)$, значення якої знаходяться в діапазоні $[0...1]$. Чим ближче $z_\alpha$ до одиниці, тим впевненіше модель відносить об'єкт до класу з номером $\alpha$.
$$ z_{\alpha} ~=~f(\mathbf{y}) ~=~f\Bigr(b_\alpha + \sum_{\mu} x_{\mu}\, w_{\mu \alpha} \Bigr) $$Якщо об'єкт може належати до декількох класів (класи перетинаються), то як $f(z)$ зручна сигмоїдна функція. Якщо на декількох виходах моделі виходять значення близькі до одиниці, то об'єкт належить усім цим класам. При великих $y_\alpha$ сигмоїд прямує до одиниці, а при $y_\alpha\to -\infty$ - до нуля: $\sigma(y_\alpha) = 1/(1+e^{-y_\alpha})$
Для класів, що не перетинаються, $C > 2$ (об'єкт $\mathbf{x}$ належить лише одному класу), зазвичай, застосовують функцію softmax. На її виході отримуються додатні значення, сума яких дорівнює одиниці. Тому їх можна інтерпретувати як розподіл ймовірностей $p(c|\mathbf{x})$: $$ z_\alpha = \mathrm{sm}(y_\alpha) = \displaystyle \frac{e^{y_\alpha}}{\sum_\beta e^{y_\beta}},~~~~~~~~~~~~~~~~~~~~~\sum_\alpha z_\alpha = 1,~~~~~~~~~z_\alpha > 0. $$
Якщо розділяючі площини для $\{y_0,y_1,y_2\}$ проведені правильно і класи лінійно роздільні, то для точки, що належить до класу $0$, значення виходів прямуватимуть до $\{1,0,0\}$, для класу $1$ - $\{0,1,0\}$ і т.д.
Формула Байєса та софтмакс
Існує чудовий математичний факт, який підводить "теоретичну основу" під використання функції софтмаксу. Нехай приклади кожного класу $c=[0...,C-1]$ у просторі ознак $\mathbf{x}$ утворюють "кластери" з нормальними розподілами, які мають однакову дисперсію $\mathbf{D}$ і різні середні $\bar{\mathbf{x}}_c$. Тоді умовна ймовірність $p(c|\mathbf{x})$ належності об'єкта з ознаками $\mathbf{x}$ до класу $c$, описується лінійною моделлю з софтмакс-функцією на виході: $$ P(\mathbf{x} | c) \sim e^{-\frac{1}{2}\,(\mathbf{x}-\bar{\mathbf{x}}_c)\,\mathbf{D}^{-1}(\mathbf{x}-\bar{\mathbf{x}}_c)}~~~~~~~~~~~~~ \Rightarrow~~~~~~~~~~~~~ p(c|\mathbf{x}) = \frac{e^{y_c}}{\sum_\alpha e^{y_\alpha}},~~~~~~~\mathbf{y} = \mathbf{b} + \mathbf{x}\mathbf{w}. $$
Природно, у реальних задачах класи в просторі ознак рідко задовольняють вихідну передумову. Однак, за допомогою нейронних мереж, часто вдається побудувати підходяще нелінійне перетворення, яке дає новий простір ознак. У цьому просторі об'єкти, що належать до різних класів, утворюють лінійно роздільні, компактні кластери класів. Нижче йде ідея доведення, яку можна пропустити.🔥 З визначення умовної ймовірності випливає формула Байєса: $$ p(c|\mathbf{x}) = \frac{p(c,\mathbf{x})}{p(\mathbf{x})} = \frac{p(c)\,P(\mathbf{x}|c)}{p(\mathbf{x})} = \frac{p(c)\,P(\mathbf{x}|c)}{\sum_\alpha p(\alpha)\,P(\mathbf{x}|\alpha)}, $$ де $p(c)$ - ймовірності належності випадково обраного об'єкта до класу $c$. Розпишемо добуток $(\mathbf{x}-\bar{\mathbf{x}}_c)\,\mathbf{D}^{-1}(\mathbf{x}-\bar{\mathbf{x}}_c)$ під експонентою в $P(\mathbf{x} | c)$. Доданок $-(1/2)\,\mathbf{x}\,\mathbf{D}^{-1}\mathbf{x}$, присутній і в чисельнику, і в знаменнику формули Байєса скорочується. Решту доданків і множник $p(c)$ можна факторизувати таким чином (обернена матриця дисперсії $\mathbf{D}^{-1}$ симетрична): $$ p(c)\,P(\mathbf{x}|c) \sim e^{b_c + \mathbf{x}\,\mathbf{w}_c},~~~~~~~~~ b_c = -\frac{1}{2}\,\bar{\mathbf{x}}_c\,\mathbf{D}^{-1}\bar{\mathbf{x}}_c + \log\, p(c),~~~~~~~~~~ \mathbf{w}_c = \mathbf{D}^{-1}\bar{\mathbf{x}}_c. $$
Батчі та перемноження матриць
Нехай є 2 ознаки: $\{x_0,x_1\}$ (вхід моделі), які визначають три цільові величини: $\{y_0,y_1,y_2\}$ (вихід). Лінійний зв'язок між виходом і входом: $$ \left\{\begin{array}{lcl} y_0 &=& b_0+ x_0 \,w_{00} + x_1\, w_{10} \\ y_1 &=& b_1+ x_0 \,w_{01} + x_1\, w_{11} \\ y_2 &=& b_2+ x_0 \,w_{02} + x_1\, w_{12} \end{array} \right. ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ \mathbf{w} = \overbrace{ \begin{array}{|c|c|c|} \hline w_{00} & w_{01} & w_{02} \\ \hline w_{10} & w_{11} & w_{12} \\ \hline \end{array} }^{\displaystyle n_y} \left. \phantom{ \begin{array}{c} \\ \\ \end{array} } \right\} n_x $$ можна записати в табличному вигляді (крапка - це dot-множення рядка на стовпець):
$$ \begin{array}{|c|c|} \hline b_{0} & b_{1} & b_{2} \\ \hline \end{array} ~~~+~~~ \begin{array}{|c|c|} \hline x_0 & x_1 \\ \hline \end{array} ~\cdot~ \begin{array}{|c|c|c|} \hline w_{00} & w_{01} & w_{02} \\ \hline w_{10} & w_{11} & w_{12} \\ \hline \end{array} ~~~=~~~ \begin{array}{|c|c|} \hline y_{0} & y_{1} & y_{2} \\ \hline \end{array} $$
В алгоритмах машинного навчання обчислення відбувається не з одним прикладом,
а одразу з деякою їх множиною - пачкою (batch).
Тому подамо $\mathbf{x}$ у вигляді 2D матриці,
розташувавши приклади порядково (перший індекс, починаючи з нуля - номер прикладу, а другий - номер ознаки в даному прикладі).
Нижче наведено $N=4$ приклади, $n_x=2$ ознаки і $n_y=3$ виходи моделі:
Розмірності індексів (атрибут shape у numpy)
матриці добутку $\mathbf{x}\cdot\mathbf{w}$ дорівнюють:
$$
(N,n_x) . (n_x,n_y) = (N,n_y).
$$
При згортці матриць:
$(\mathbf{x}\cdot\mathbf{w})_{i\alpha} = \sum_\mu x_{i\mu}\,w_{\mu\alpha}$
діє принцип "рядок на стовпець"
і розмірність середнього індексу скорочується.
До кожного рядка матриці $\mathbf{x}\cdot\mathbf{w}$ форми (N,ny)
додається вектор $\mathbf{b}$ форми (ny,).
У бібліотеці numpy це відповідає правилу розширення
(broadcasting).
Тензорний аналіз
Далі будуть обчислюватися похідні за тензорами і використовуватимуться матричні позначення. Тому нагадаємо деякі важливі моменти.
☝ Матриця, обернена до матриці $\mathbf{A}$, позначається як $\mathbf{A}^{-1}$, а транспонована як $\mathbf{A}^{\top}$. У транспонованої матриці колонки і рядки (індекси) переставлені місцями: $A^\top_{ij}=A_{ji}$, а добуток оберненої на вихідну дає одиничну матрицю (символ Кронекера). Вони мають такі властивості: $$ \mathbf{A}^{-1}\cdot\mathbf{A}=\mathbf{A}\cdot\mathbf{A}^{-1}=\mathbf{1},~~~~~~~~~~ (\mathbf{A}\cdot\mathbf{B})^{-1} = \mathbf{B}^{-1}\cdot\mathbf{A}^{-1},~~~~~~~~~~ (\mathbf{A}\cdot\mathbf{B})^{\top} = \mathbf{B}^{\top}\cdot\mathbf{A}^{\top}. $$
☝ Справедлива формула для часткової похідної (три крапки - це інші індекси):
$$ \frac{\partial W_{\mu\nu...}}{\partial W_{\alpha\beta...}} = \delta_{\mu\alpha}\,\delta_{\nu\beta}\,...,~~~~~~~~~~~~~~ \sum_{\mu} T_{...\mu...}\,\delta_{\mu\alpha} = T_{...\alpha...}, $$де $\delta_{\alpha\beta}$ - одинична квадратна матриця (символ Кронекера), що дорівнює $0$, якщо $\alpha\neq\beta$ і $1$, якщо $\alpha=\beta$. При підсумовуванні із символом Кронекера (за одним із його індексів), сума і символ Кронекера прибираються, а сумаційний індекс замінюється на другий індекс символу Кронекера (вище друга формула).
Формула для $\partial W_{\mu\nu}/\partial W_{\alpha\beta}$, означає, що похідна дорівнює нулю, якщо $\mu\neq\alpha$ і $\nu\neq\beta$. Наприклад, $\partial W_{12}/\partial W_{13} = 0$ так це різні змінні (типу $\partial y/\partial x = 0$), а $\partial W_{12}/\partial W_{12} = 1$ (типу $\partial x/\partial x = 1$).
Аналітичне рішення
У лінійній моделі з mse-помилкою аналітично нескладно знайти оптимальні параметри. Обчислення виявляються помітно простіше, якщо вектор $\mathbf{b}$ і матрицю $\mathbf{w}$ об'єднати в одну матрицю $\mathbf{W}$, додавши в $\mathbf{w}$, як ще один рядок, вектор $\mathbf{b}$. Додатково в матрицю $\mathbf{x}$ додається колонка, що складається з одиниць, що дає матрицю $\mathbf{X}$:
$$ \mathbf{X}\cdot\mathbf{W} ~=~ \begin{array}{|c|c|c|} \hline x_{00} & x_{01} & ~1~ \\ \hline x_{10} & x_{11} & 1 \\ \hline x_{20} & x_{21} & 1 \\ \hline x_{30} & x_{31} & 1 \\ \hline \end{array} ~\cdot~ \begin{array}{|c|c|} \hline w_{00} & w_{01} & w_{02} \\ \hline w_{10} & w_{11} & w_{12} \\ \hline b_{0} & b_{1} & b_{2} \\ \hline \end{array} ~=~ \begin{array}{|c|c|c|} \hline y_{00} & y_{01} & y_{02} \\ \hline y_{10} & y_{11} & y_{12} \\ \hline y_{20} & y_{21} & y_{22} \\ \hline y_{30} & y_{31} & y_{32} \\ \hline \end{array} ~=~ \mathbf{y}. $$ Такий запис означає введення ще однієї фіктивної ознаки, завжди рівної одиниці: $\mathbf{x}=\{x_0,...,x_{n-1},1\}$.Тепер модель має вигляд: $\mathbf{y} = \mathbf{X}\cdot\mathbf{W}$, що, як неважко перевірити, еквівалентно вихідному запису $\mathbf{y}=\mathbf{b} + \mathbf{x}\mathbf{w}$. Знайдемо мінімум mse-помилки $L$ (опускаємо постійний множник):
$$ L = \sum_{i,\nu} (y_{i\nu} -\hat{y}_{i\nu} )^2,~~~~~~~~~~~~~~~ y_{i\nu} = \sum_{\mu} X_{i\mu} W_{\mu\nu}. $$Запишемо похідні помилки і виходу моделі: $$ \frac{\partial L}{\partial y_{i\nu}}~=~ 2(y_{i\nu} -\hat{y}_{i\nu} ),~~~~~~~~ \frac{\partial y_{i\nu}}{\partial W_{\alpha\beta}} = \sum_{\mu} X_{i\mu}\,\delta_{\mu\alpha}\,\delta_{\nu\beta}. ~=~X_{i\alpha} \,\delta_{\nu\beta}. $$ Тепер неважко знайти похідну помилки $L$ за елементами матриці $W_{\alpha\beta}$: $$ \frac{\partial L}{\partial W_{\alpha\beta}} = \sum_{i,\nu} \frac{\partial L}{\partial y_{i\nu}}\,\frac{\partial y_{i\nu}}{\partial W_{\alpha\beta}} ~=~ 2 \sum_{i} (y_{i\beta} -\hat{y}_{i\beta} )\, X_{i\alpha} ~=~2 \bigr[\mathbf{X}^\top\cdot(\mathbf{y}-\hat{\mathbf{y}} )\bigr]_{\alpha\beta}. $$
Прирівнявши похідну нулю (умова екстремуму, і в даному випадку мінімуму $L$), отримуємо матричне рівняння $\mathbf{X}^\top\cdot(\mathbf{X}\cdot\mathbf{W}-\hat{\mathbf{y}} ) = 0$, яке легко розв'язується:
$$ \mathbf{W} = (\mathbf{X}^\top\cdot \mathbf{X})^{-1}\cdot (\mathbf{X}^\top\cdot \hat{\mathbf{y}}) ~~~~~~~~\text{або}~~~~~~~~~~~~~~ \mathbf{W}= \mathbf{X}^{-1} \cdot \hat{\mathbf{y}}. $$У принципі, можна користуватися будь-якою з цих формул. Перша формула виявляється швидшою, якщо прикладів N суттєво більше, ніж ознак nX = число входів $\mathbf{x}$. Шукати обернену матрицю для квадратної матриці $\mathbf{X}^\top\cdot \mathbf{X}$ форми (nX+1, nX+1) простіше, ніж для матриці $\mathbf{X}^{-1}$ форми (nX+1, N) у другій формулі, хоча при цьому додається перемноження матриць $\mathbf{X}^\top\cdot \mathbf{X}$.
Приклад на numpy
Проведемо обчислення за отриманими формулами за допомогою бібліотеки numpy. Короткий вступ до numpy можна знайти в документі NN_Base_Numpy.
Згенеруємо спочатку модельні дані, в яких виходи лінійно пов'язані зі входами і доданий невеликий випадковий шум:
import numpy as np # робота з тензорами
N = 100 # число точок (прикладів)
nX, nY = 2, 3 # число входів (ознак) і виходів
w = np.array([[ 1, 3, 5], # матриця w форми (nX,nY)
[ 6, 4, 2] ])
b = np.array( [ -3, -2, -1] ) # вектор b форми (nY, )
X = np.random.random ((N, nX)) # рівномірні випадк.точки в [0...1]
Y = np.dot(X, w) + b # виходи моделі
Y += np.random.normal (0, 0.01, (N, nY)) # додаємо випадковий гаусів шум
Отримаємо матриці лінійної моделі за першою і другою формулою. Оскільки матриця в другій формулі не квадратна, для пошуку оберненої до неї матриці використовуємо функцію pinv, а не inv, як у першому випадку:
from numpy.linalg import inv, pinv # обернені для квадрат. і прямокут. XX = np.concatenate( (X, np.ones((N, 1))), axis=1) # додаємо стовпчик одиниць W1 = np.dot( inv(np.dot(XX.T, XX)), np.dot(XX.T, Y) ) # перша формула W2 = np.dot( pinv(XX), Y) # друга формула
Регресія на sklearn
Для розв'язання цієї ж задачі, на практиці зручніше користуватися готовою бібліотекою машинного навчання Scikit-learn (коротко sklearn):
from sklearn.linear_model import LinearRegression
lr = LinearRegression().fit(X,Y)
print(lr.coef_.T) # [[1.007 3.001 5.001] матриця W
# [6.003 4. 1.998]]
print(lr.intercept_) # [-3.002 -2.001 -0.999] вектор b
Виклик LinearRegression() створює екземпляр класу даного методу навчання, якому через функцію fit передаються навчальні дані X,Y. Ця функція знову повертає екземпляр класу, в атрибутах якого знаходяться параметри моделі. Звернемо увагу, що матриця $\mathbf{w}$ (атрибут lr.coef_) у sklearn зберігається в транспонованому вигляді (порівняно з прийнятим вище).
Отримавши параметри моделі, можна тепер обчислити її mse-помилку:
mse = np.mean((X @ lr.coef_.T + lr.intercept_ - Y) ** 2)
print(f"mse:{mse:.5f}, sqrt(mse):{np.sqrt(mse):.5f} ")
# mse:0.00010, sqrt(mse):0.01005
Класифікація на sklearn
Задача класифікації з нелінійними функціями сигмоїда або софтмаксу на виході лінійної моделі не має такого простого розв'язку. Тому доводиться застосовувати чисельні методи, з яких у машинному та глибокому навчанні виявився найефективнішим градієнтний спуск. Тим не менше, у лінійному випадку можна успішно розв'язати задачу багатокласової класифікації, відмовившись від нелінійності на виході моделі.
Розглянемо для наочності двовимірний простір ознак $\mathbf{x}=\{x_0, x_1\}$ і три класи $c=\{0,1,2\}$, до одного з яких потрібно віднести об'єкт. У моделі буде три виходи: $n_y=3$, на яких, як цільові значення, вимагатимемо для першого, другого і третього класів відповідно: $$ \mathbf{y}=\{+1,-1,-1\},~~~~~\mathbf{y}=\{-1,+1,-1\},~~~~~\mathbf{y}=\{-1,-1,+1\}. $$ Як і раніше, будемо використовувати середньоквадратичну помилку, для якої є точний розв'язок. Природно, лінійна модель не зможе для всіх прикладів досягти цільових значень. Однак, три виходи моделі $\mathbf{y}=\{d_0,d_1,d_2\}$ - це три знакозмінні відстані від точки $\mathbf{x}$ до трьох площин. Відстані додатні, якщо вектор нормалі "дивиться" на точку, і від'ємні - якщо в протилежний бік. Метод найменших квадратів прагнутиме розгорнути ці вектори, щоб, принаймні, збігалися знаки виходу моделі і цільових значень. Це дасть необхідний класифікатор.
Підготуємо модельні дані у вигляді трьох кластерів з нормальним розкидом точок навколо їхніх центрів:
nx, ny, N = 2, 3, 300 # число ознак, класів, об'єктів (точок)
C = np.zeros(N) # номери правильних класів
Y = np.full((N,ny), -1) # матриця N x ny для виходів, заповнена -1
X = np.random.normal (0, 0.4, (N,nx)) # хмара N точок з центром у початку координат
X0 = [[-1,1], [1,1], [0,-1]] # центри кластерів кожного класу
nc = int(N/ny) # число прикладів кожного класу
for i, x0 in enumerate(X0):
C[i*nc:(i+1)*nc] = i # номер i-того класу
Y[i*nc:(i+1)*nc, i] = 1 # цільові виходи
X[i*nc:(i+1)*nc] += np.array(x0) # зсуваємо кластер i-того класу
Модель, яка проводить класифікацію, оформимо у вигляді класу:
class Model:
def __init__(self):
"Конструктор"
self.lr = LinearRegression()
def __call__(self, x):
"Функціональний виклик об'єкта"
y = x @ self.lr.coef_.T + self.lr.intercept_
return np.argmax(y, axis=1)
def fit(self, x,y):
"Навчання за прикладами x,y"
self.lr.fit(x,y)
Процес навчання і тестування виглядає наступним чином:
model = Model() # екземпляр моделі model.fit(X,Y) # навчаємо y = model(X) # отримуємо номери класів A = (y==C).mean() # порівнюємо з точними (акуратність)
Щоб отримати графік як на початку розділу, потрібно додати до класу Model наступний метод:
. def plot(self, x1_min=0, x1_max=1, x2_min=0, x2_max=1, n=201):
# сітка точок 201 x 201 в інтервалі x1,x2=[xMin,xMax]
x1,x2 = np.meshgrid(np.linspace(x1_min, x1_max, n),
np.linspace(x2_min, x2_max, n))
grid = np.c_[x1.ravel(), x2.ravel()] # shape = (n*n, 2)
yp = self(grid).reshape(x1.shape) # (n*n, ) -> (n, n)
fig,ax = plt.subplots(figsize=(5, 5)) # розміри картинки (квадрат)
plt.axis([x1_min,x1_max, x2_min,x2_max]) # діапазон зміни осей
plt.imshow(yp, interpolation='nearest', # кольор. карту для 2D масиву yp
extent=(x1.min(), x1.max(), x2.min(), x2.max()),
aspect='auto', origin='lower', alpha=0.2,
vmin=0, vmax=yp.max())
За допомогою цього методу малюємо розділяючі поверхні та навчальні об'єкти:
model.plot(-2, 2, -2, 2) plt.scatter(X[:,0], X[:,1], c=C, s=30, edgecolors='black') plt.show()