Задача регрессии (аппроксимации)

Задача регрессии (аппроксимации)#

Пусть имеем конечный набор точек \(\left\{\left(x_i, y_i\right)\right\}_{i=1}^N\), причём \(x_i \in \mathbb{R}^\ell, y_i \in \mathbb{R}\).

Хотим найти функцию \(\check{y}\), наилучшим образом аппроксимирующий данный набор точек.

Простейший пример - провести прямую по точкам (см. картинку выше).

Сразу же возникает несколько поинтов к постановке задачи:


  • …, наилучшим образом … - нужен числовой критерий «качества» аппроксимации. Им, как правило, служит функция ошибки (Loss-function).


  • … функцию \(\check{y}\), …__ - перебрать все функции невозможно (мы тут не на матане), так что придётся сузить круг поиска на некотором семействе функций.

Семейства функций (модели)#

Как мы уже поняли, перебирать все функции мы не в силах из-за ограничений компьютера. Поэтому сузим наш круг поиска на какое-то семейство функций \(\check{y}(x) = g_\mathbf{w}(x)\) (модели), зависящих от какого-то вектора числовых параметров (весов модели) \(\mathbf{w} = (\text{w}_1, \text{w}_2, ..., \text{w}_m)^\top\).

Наша цель в данном контексте - найти особенный набор параметров \(\mathbf{w}^* = (\text{w}^*_1, \text{w}^*_2, ..., \text{w}^*_m)^\top\), который даёт наилучшую функцию \(g_{\mathbf{w}^*}(x)\) в контексте восстановления зависимости точек \(\left\{\left(x_i, y_i\right)\right\}_{i=1}^N\).

Это и будет наша наилучшая аппроксимация \(\check{y}^*(x) \stackrel{\text{def}}{=} g_{\mathbf{w}^*}(x)\).

Как выбирать семейства функций? Есть два главных подхода:

  • Из теории (наука) - знаем зависимость, вопрос в константах (их и «фиттим»).

  • Из головы (искусство) - не знаем ни зависимости, ни констант. Стараемся подобрать получше эмпирически.

  • Пример с многочленами

Пусть есть \(\left\{\left(x_i, y_i\right)\right\}_{i=1}^N\) - набор точек для «фита». Поищем аппроксимирующую функцию среди многочленов второй степени:

\[ g_\mathbf{w}(x) = \text{w}_1 \cdot x^2 + \text{w}_2 \cdot x + \text{w}_3 \]

Как видим, множество числовых параметров в данном случае просто вектор:

\[ \mathbf{w} = (\text{w}_1, \text{w}_2, \text{w}_3)^\top \in \mathbb{R}^3 \]

Это частный случай линейной регрессии, т.к. признаки (в данном случае многочлены) входят в модель линейным образом.

Hide code cell source
from bokeh.io import output_notebook
from bokeh.resources import INLINE
output_notebook(INLINE)
Loading BokehJS ...
Hide code cell source
import numpy as np

from bokeh.layouts import column, row
from bokeh.models import ColumnDataSource, CustomJS, Slider
from bokeh.plotting import figure, show
from bokeh.palettes import Sunset10


x = np.linspace(-5, 5, 500)
y = x**2 
noise = 0.5*np.random.normal(size = 500)

source = ColumnDataSource(data=dict(x=x, y=y))

blue, yellow = Sunset10[0], Sunset10[5]

plot = figure(y_range=(-2, 5), x_range=(-5, 5), width=500, height=300, title=r"$$\\ g_\mathbf{w}(x) = w_1 \cdot x^2 + w_2 \cdot x + w_3$$")
#plot.title.text_font_size = "10px"
plot.xaxis.axis_label = r'$$x$$'
plot.yaxis.axis_label = r'$$y$$'

plot.line('x', 'y', source=source, line_width=5, line_alpha=1, color=blue)

w1 = Slider(start=-5, end=5, value=1, step=.1, title="w1", sizing_mode = "stretch_width")
w2 = Slider(start=-5, end=5, value=0, step=.1, title="w2", sizing_mode = "stretch_width")
w3 = Slider(start=-5, end=5, value=0, step=.1, title="w3", sizing_mode = "stretch_width")


callback = CustomJS(args=dict(source=source, w1=w1, w2=w2, w3=w3),
                    code="""
    const a = w1.value
    const b = w2.value
    const c = w3.value

    const x = source.data.x
    const y = Array.from(x, (x) => a * x ** 2 + b * x + c)
    source.data = { x, y }
""")


plot.scatter(x, y+noise, size=8, alpha=0.8, fill_color=yellow, line_color="black").level = 'underlay'

w1.js_on_change('value', callback)
w2.js_on_change('value', callback)
w3.js_on_change('value', callback)


show(column(plot, w1, w2, w3))
  • Пример с суммой разных функций

Пусть есть \(\left\{\left(x_i, y_i\right)\right\}_{i=1}^N\) - набор точек для «фита». Поищем аппроксимирующую функцию среди суммы наобум выбранных функций:

\[ g_\mathbf{w}(x) = \text{w}_1 \cdot f_1(x) + \text{w}_2 \cdot f_2(x) \]

где

\[ f_1(x) = e^x, \quad f_2(x) = sin(x) \]

Как видим, множество числовых параметров в данном случае просто вектор:

\[ \mathbf{w} = (\text{w}_1, \text{w}_2)^\top \in \mathbb{R}^2 \]

Это также частный случай линейной регрессии, т.к. признаки (в данном случае функции \(f_1(x), f_2(x)\)) входят в модель линейным образом.

Hide code cell source
import numpy as np

from bokeh.layouts import column, row
from bokeh.models import ColumnDataSource, CustomJS, Slider
from bokeh.plotting import figure, show
from bokeh.palettes import Sunset10
from bokeh.io import output_notebook

x = np.linspace(-10, 10, 400)
y = 0.3*np.exp(x) + 4.5*np.sin(x)

source = ColumnDataSource(data=dict(x=x, y=y))

blue, yellow = Sunset10[0], Sunset10[5]

plot = figure(y_range=(-7, 15), x_range=(-11, 5), width=500, height=300, title=r"$$g_\mathbf{w}(x) = w_1 \cdot f_1(x) + w_2 \cdot f_2(x)$$")
#plot.title.text_font_size = "20px"
plot.xaxis.axis_label = r'$$x$$'
plot.yaxis.axis_label = r'$$y$$'

plot.line('x', 'y', source=source, line_width=5, line_alpha=1, color=blue)

w1 = Slider(start=-5, end=5, value=0.3, step=.1, title="w1", sizing_mode = "stretch_width")
w2 = Slider(start=-5, end=5, value=4.5, step=.1, title="w2", sizing_mode = "stretch_width")


callback = CustomJS(args=dict(source=source, w1=w1, w2=w2),
                    code="""
    const a = w1.value
    const b = w2.value

    const x = source.data.x
    const y = Array.from(x, (x) => a * Math.exp(x) + b * Math.sin(x))
    source.data = { x, y }
""")

x = np.linspace(-10, 10, 60)
y_noised = 0.5*np.exp(x) + 5*np.sin(x) + 0.5*np.random.normal(size = 60)

plot.scatter(x, y_noised, size=8, alpha=0.8, fill_color=yellow, line_color="black").level = 'underlay'

w1.js_on_change('value', callback)
w2.js_on_change('value', callback)

#output_notebook()
show(column(plot, w1, w2))
  • Пример нелинейной регрессии

Пусть есть \(\left\{\left(x_i, y_i\right)\right\}_{i=1}^N\) - набор точек для «фита». Поищем аппроксимирующую функцию среди следующего семейства функций:

\[ g_\mathbf{w}(x) = \frac{\sin(x^{\text{w}_1})}{2 + \cos(x^{\text{w}_2})} \]

Как видим, множество числовых параметров в данном случае просто вектор:

\[ \mathbf{w} = (\text{w}_1, \text{w}_2)^\top \in \mathbb{R}^2 \]

В то же время регрессия тут нелинейная (потому что параметры модели \(\text{w}_i\) не входят в выражение \(g_\mathbf{w}(x)\) линейным образом).

Hide code cell source
import numpy as np

from bokeh.layouts import column, row
from bokeh.models import ColumnDataSource, CustomJS, Slider
from bokeh.plotting import figure, show
from bokeh.palettes import Sunset10

x = np.linspace(0.001, 10, 400)
y = np.sin(x**1.8)/(2 + np.cos(x**1.8))

source = ColumnDataSource(data=dict(x=x, y=y))

blue, yellow = Sunset10[0], Sunset10[5]

plot = figure(y_range=(-1.5, 1.5), x_range=(0, 5), width=500, height=300, title=r'$$g_\mathbf{w}(x) = (\sin(x^{w_1}))/(2 + \cos(x^{w_2}))$$')
#plot.title.text_font_size = "20px"
plot.xaxis.axis_label = r'$$x$$'
plot.yaxis.axis_label = r'$$y$$'

plot.line('x', 'y', source=source, line_width=5, line_alpha=1, color=blue)

w1 = Slider(start=0, end=10, value=1.8, step=.1, title="w1", sizing_mode = "stretch_width")
w2 = Slider(start=0, end=10, value=1.8, step=.1, title="w2", sizing_mode = "stretch_width")


callback = CustomJS(args=dict(source=source, w1=w1, w2=w2),
                    code="""
    const a = w1.value
    const b = w2.value

    const x = source.data.x
    const y = Array.from(x, (x) => Math.sin(x ** a)/(2 + Math.cos(x ** b)))
    source.data = { x, y }
""")

x = np.linspace(0.001, 5, 50)
y_noised = np.sin(x**2)/(2 + np.cos(x**1.5)) + 0.05*np.random.normal(size = 50)

plot.scatter(x, y_noised, size=8, alpha=0.8, fill_color=yellow, line_color="black").level = 'underlay'

w1.js_on_change('value', callback)
w2.js_on_change('value', callback)

#output_notebook()
show(column(plot, w1, w2))

Функция ошибки (Loss-function)#

Выбирать наилучшую модель «на глаз», конечно, весело, но не очень практично. Нужен числовой критерий «качества» модели для аппроксимируемого набора \(\left\{\left(x_i, y_i\right)\right\}_{i=1}^N\).

Начнём с самого простого - функции ошибки (Loss-function) \(\mathcal{L}\). Как следует из названия - чем больше эта функция, тем хуже модель (и нам надо её минимизировать, удивительно, да).

Естественно, на вход эта функция должна принимать как исследуемую модель \(\check{y}(x) = g_\mathbf{w}(x)\), так и аппроксимируемый набор \(\left\{\left(x_i, y_i\right)\right\}_{i=1}^N\). Получается как-то сложно, поэтому введём определенную терминологию, упрощающую ситуацию.

Определение. Назовём \(\check{y}_i = \check{y}(x_i) = g_\mathbf{w}(x_i)\) предсказаниями модели на исследуемом наборе.

А тогда функция ошибки принимает на вход чистые данные (label - \(y_i\)) и предсказания модели (predict - \(\check{y}_i\)). И наша глобальная цель - минимизировать функцию ошибки (а точнее найти \(\mathbf{w}^*\), то есть аргумент, на котором \(\mathcal{L}\) минимизируется):

\[ \mathbf{w}^* = \text{arg}\min_\mathbf{w} \mathcal{L} (y_i, \check{y}_i) \]

Некоторые популярные функции ошибки:

  • MSE - Mean Square Error (МНК)

\[ \mathcal{L}\left(\check{y}_i, y_i\right)=\frac{1}{N} \sum_{i=1}^N\left(\check{y}_i-y_i\right)^2=\frac{1}{N} \sum_{i=1}^N\left(g_\mathbf{w}\left(x_i\right)-y_i\right)^2 \]

Является самой распростронённой и простой метрикой ошибок. Легко получать аналитические результаты и численно минимизировать.

  • RMSE - Root Mean Square Error

\[ \mathcal{L}\left(\check{y}_i, y_i\right)=\sqrt{M S E}=\sqrt{\frac{1}{N} \sum_{i=1}^N\left(g_\mathbf{w}\left(x_i\right)-y_i\right)^2} \]
  • Cross-Entropy, log-loss

Используется в задаче классификации (когда \(y_i \in \{0, 1\}\)):

\[ \mathcal{L}\left(\check{y}_i, y_i\right)=-\frac{1}{N} \sum_{i=1}^N\left(y_i \cdot \log \left(\check{y}_i\right)+\left(1-y_i\right) \cdot \log \left(1-\check{y}_i\right)\right) \]

В данном семинаре и в целом в курсе будем ориентироваться на MSE.