# ---
# jupyter:
#   jupytext:
#     text_representation:
#       extension: .py
#       format_name: light
#       format_version: '1.5'
#       jupytext_version: 1.14.4
#   kernelspec:
#     display_name: Python 3
#     language: python
#     name: python3
# ---

# + [markdown] slideshow={"slide_type": "slide"}
# # Основные понятия и примеры прикладных задач

# + [markdown] slideshow={"slide_type": "slide"}
# # Используемые пакеты

# + slideshow={"slide_type": "subslide"}
from sklearn.linear_model import LogisticRegression
import matplotlib.pyplot as plt
import seaborn as sns
import numpy as np
import pandas as pd

# + [markdown] slideshow={"slide_type": "slide"}
# # <NAME>
# Выборка взята отсюда: https://archive.ics.uci.edu/ml/datasets/iris

# + [markdown] slideshow={"slide_type": "subslide"}
# ## Загрузка выборки

# + slideshow={"slide_type": "subslide"}
dataset = pd.read_csv('data/iris.csv', 
                      header=None, 
                      names=['длина чашелистика', 'ширина чашелистика', 
                             'длина лепестка', 'ширина лепестка', 'класс'])
dataset.sample(5, random_state=0)

# + [markdown] slideshow={"slide_type": "subslide"}
# ## Начало работы с данными
# 1. Определить множество объектов:
#     * Определить размер выборки
#     * Определить признаки, которыми описываются объекты
# 2. Определить множество ответов
# 3. Определить тип задачи машинного обучения
# 6. ...

# + [markdown] slideshow={"slide_type": "subslide"}
# ### Множество объектов
# В данной задачи множество объектов описывается $n=4$ признаками:
# 1. Длина чашелистика
# 2. Ширина чашелистика
# 3. Длина лепестка
# 4. Ширина лепестка

# + slideshow={"slide_type": "subslide"}
print('Размер выборки составляет l={} объектов.'.format(len(dataset)))

# + [markdown] slideshow={"slide_type": "subslide"}
# Все признаки являются вещественными признаками. Формально объекты $\mathbf{X}$ представляються в следующем виде:
# $$\mathbf{X} \in \mathbb{R}^{l\times n},$$
# где $l$ число объектов, а $n$ число признаков.
#
# Получаем, что $\mathbf{X}$ это некоторая вещественная матрица размера $l\times n$.

# + [markdown] slideshow={"slide_type": "subslide"}
# ### Множество ответов
# В данной задаче множество ответов состоит из трех элементов:
# 1. Iris-virginica
# 2. Iris-versicolor
# 3. Iris-setosa

# + [markdown] slideshow={"slide_type": "subslide"}
# ### Задача машинного обучения
# В нашем случае, так как мощность множества $|\mathbf{y}|=3 \ll l=150$ получаем задачу классификации на $M=3$ класса.

# + [markdown] slideshow={"slide_type": "subslide"}
# ## Анализ данных
# Сначала проэктируем все объекты на двумерные плоскости, для упрощения анализа

# + slideshow={"slide_type": "subslide"}
sns.pairplot(dataset, hue='класс', height=2)
plt.show()

# + [markdown] slideshow={"slide_type": "subslide"}
# Из рисунка видно, что класс синих точек (Iris-setosa) легко отделяется от двух других цветов. Оранджевые и зеленные точки отделяются не так просто в каждой из проэкций, но все равно можно провести прямую, которая отделит оранджевые точки от зеленых.

# + [markdown] slideshow={"slide_type": "subslide"}
# ## Построение модели
# ### Преобразование данных
# Как было сказано ранее нам требуется решить задачу классификации на 3 класса. Но для наглядноси рассмотрим бинарную классификацию (классификацию на несколько классов рассмотрим в следующей лекции).
#
# Чтобы исходную задачу преобразовать в задачу бинарной классификации уберем из выборки все объекта класса Iris-setosa.

# + slideshow={"slide_type": "subslide"}
binary_dataset = dataset.drop(index=dataset.index[dataset['класс'] == 'Iris-setosa'])

# + [markdown] slideshow={"slide_type": "subslide"}
# Классы закодируем целыми числами $-1$ и $1$.

# + slideshow={"slide_type": "subslide"}
binary_dataset.loc[dataset['класс'] == 'Iris-versicolor', dataset.columns == 'класс'] = -1
binary_dataset.loc[dataset['класс'] == 'Iris-virginica', dataset.columns == 'класс'] = 1

# + [markdown] slideshow={"slide_type": "subslide"}
# Получаем задачу бинарной классификации.

# + [markdown] slideshow={"slide_type": "subslide"}
# ### Модель алгоритмов
#
# Модель алгоритмов $\mathfrak{F}$ в машинном обучении это некоторое множество функций, которые действуют из множества объектов в множество ответов, в нашем случае:
# $$\mathfrak{F} = \{f| f: \mathbb{R}^n \to \{-1, 1\}, \text{еще какие-то ограничения}\},$$
# обычно $\mathfrak{F}$ это некоторое параметрическое семество функций, тоесть разные функции $f$ отличаются друг от друга только каким-то параметром. Простым примером параметрическим семейством функций для задачи бинарной классификации является семейство линейный классификатор:
# $$\mathfrak{F}_{bcl} = \left\{f\bigr(\theta, \mathbf{x}\bigr)=\text{sign}\bigr(\theta^{\mathsf{T}}\mathbf{x}\bigr)\bigr| \theta \in \mathbb{R}^{n} \right\}.$$
#

# + [markdown] slideshow={"slide_type": "subslide"}
# ### Функция потерь
#
# Машиное обучение это всегда выбор функции из множества $\mathfrak{F}$. Чтобы выбрать функцию, нужен некоторый критерий по которому она выбирается, то есть нужно упоррядочить все функции от худшей к лучшей. Для этого построем функционал $\mathcal{L}$, который каждой функции $f \in \mathfrak{F}$ ставит в соответствии число из $\mathbb{R}_+$. В машинном обучении обычно функционал качества водиться как некоторая ошибка на выборке. В общем виде функционал качества можно представить в следующем виде:
# $$\mathcal{L}\bigr(f, \mathbf{X}, \mathbf{y}\bigr) = \sum_{i=1}^l\mathcal{q}\bigr(f, \mathbf{x}_i, y_i\bigr),$$
# где $q$ некоторая функция ошибки на некотором объекте $\mathbf{x}$. Функционал качества $\mathcal{L}$ называется эмперическим риском.

# + [markdown] slideshow={"slide_type": "subslide"}
# ### Оптимизационная задача
#
# Далее нужно поставить задачу оптимизации для выбора $f \in \mathfrak{F}$. Здесь все просто, просто минимизируем эмперический риск:
# $$\hat{f} = \arg \min_{f \in \mathfrak{F}} \mathcal{L}\bigr(f, \mathbf{X}, \mathbf{y}\bigr).$$
#
# Важно! В результе функция $\hat{f}$ зависит от выборки $\left(\mathbf{X}, \mathbf{y}\right)$, то есть для разных наборов данных оптимальная функция будет различная.

# + [markdown] slideshow={"slide_type": "subslide"}
# Вернемся к нашей задаче. В нашем случае функционал качества будет иметь следующий вид:
# $$\mathcal{L}\bigr(\theta, \mathbf{X}, \mathbf{y}\bigr) = \sum_{i=1}^l\bigr[f\bigr(\theta, \mathbf{x}_i\bigr) \not= y_i\bigr],$$
# и оптимизационная задача переписывается в виде:
# $$\hat{\theta} = \arg \min_{\theta \in \mathbb{R}^n} \sum_{i=1}^l\bigr[f\bigr(\theta, \mathbf{x}_i\bigr) \not= y_i\bigr].$$
#
# И на самом деле в дальнейшем будем решать иммено такие задачи, на поиск оптимального параметра. Само решение задачи линейной бинарной классификации будет на следующей лекции. Сейчас используемся библиотеками для решения данной задачи. Далее в примере будет найден параметр $\hat{\theta}$ не как решение непосредственно этой оптимизационной задачи, а немного измененной, но об этом позже в следующей лекции.

# + [markdown] slideshow={"slide_type": "subslide"}
# ### Поиск оптимального вектора параметров
# Перейдем к двум матрицам:
# 1. Матрице объектов $\mathbf{X} \in \mathbb{R}^{l\times (n+1)}$
# 2. Вектору ответов $\mathbf{y} \in \{-1,1\}^l$
#
# Заметим, что объекты мы погрузили в пространство более большой размерности, добавив еще один признак, который у всех объектов будет равен $1$.

# + slideshow={"slide_type": "subslide"}
X = binary_dataset.iloc[:, binary_dataset.columns != 'класс'].values
y = binary_dataset.iloc[:, binary_dataset.columns == 'класс'].values.reshape(-1)
X = np.array(np.hstack([X, np.ones([len(X), 1])]), dtype=np.float64)
y = np.array(y, dtype=np.int64)

# + slideshow={"slide_type": "subslide"}
model = LogisticRegression(random_state=0, max_iter=2000)
_ = model.fit(X, y)

# + [markdown] slideshow={"slide_type": "subslide"}
# Получаем вектор оптимальных параметров $\hat{\theta}$

# + slideshow={"slide_type": "subslide"}
model.coef_

# + [markdown] slideshow={"slide_type": "slide"}
# # Переход от бинарной классификации к многоклассовой
# Теперь же остается вопрос: как перейти от задачи бинарной классификации к многоклассовой? В качестве бинарного классификатора рассмотрим все ту же модель алгоритмов: 
# $$\mathfrak{F}_{bcl} = \left\{f\bigr(\theta, \mathbf{x}\bigr)=\text{sign}\bigr(\theta^{\mathsf{T}}\mathbf{x}\bigr)\bigr| \theta \in \mathbb{R}^{n} \right\}.$$
#
# Но знак позволяет отделить только два знака. Какие же есть решения?

# + [markdown] slideshow={"slide_type": "subslide"}
# Рассмотрим вариант перехода, который называется Один против всех (One VS All). Для простоты визуализации рассмотрим пример на синтетических данных. 

# + [markdown] slideshow={"slide_type": "subslide"}
# ## Генерация синтетической выборки

# + slideshow={"slide_type": "subslide"}
np.random.seed(0)
l = 100
n = 2
X1 = np.array([[-1,-1]]) + 0.5*np.random.randn(l, n)
X2 = np.array([[1,1]]) + 0.5*np.random.randn(l, n)
X3 = np.array([[-1,1]]) + 0.5*np.random.randn(l, n)

X = np.vstack([X1, X2, X3])
y = np.hstack([[0]*l, [1]*l, [2]*l])


# + slideshow={"slide_type": "subslide"}
cols = ['blue', 'red', 'green']

# построение точек
for k in np.unique(y):
    plt.plot(X[y==k,0], X[y==k,1], 'o', label='класс {}'.format(k), color=cols[k])

plt.legend(loc='best')
plt.show()

# + [markdown] slideshow={"slide_type": "subslide"}
# ## Один против всех
#
# Данные метод основан на том, что для классификации на $M>2$ классов нужно построить $M$ линейных классификаторов, которые классифицируют $k$-й класс прорив всех остальных классов.

# + [markdown] slideshow={"slide_type": "subslide"}
# Построим $M=3$ классификатора, которые отделяют каждый класс от двух остальных

# + slideshow={"slide_type": "subslide"}
models = []
model = LogisticRegression(random_state=0, max_iter=2000)
_ = model.fit(X, np.array(y==0, dtype=np.int64))
models.append(model)

model = LogisticRegression(random_state=0, max_iter=2000)
_ = model.fit(X, np.array(y==1, dtype=np.int64))
models.append(model)

model = LogisticRegression(random_state=0, max_iter=2000)
_ = model.fit(X, np.array(y==2, dtype=np.int64))
models.append(model)

# + slideshow={"slide_type": "subslide"}
cols = ['blue', 'red', 'green']
plt.xlim((-2.5, 2.5))
plt.ylim((-2.5, 2.5))

# построение точек
for k in np.unique(y):
    plt.plot(X[y==k,0], X[y==k,1], 'o', label='класс {}'.format(k), color=cols[k])

# построение прямой перпендикулярной к разделяющей
for k in np.unique(y):
    # нормальный вектор к прямой
    x2, y2 = -models[k].coef_[0][1]/models[k].coef_[0][0], 1
    
    polynomial = np.poly1d(np.polyfit([0, x2], [0, y2], 1))
    x_axis = np.linspace(-10, 10)
    y_axis = polynomial(x_axis)
    plt.plot(x_axis, y_axis, linewidth=2, color=cols[k])

plt.legend(loc='best')
plt.show()

# + [markdown] slideshow={"slide_type": "subslide"}
# По данному рисунку возьникает сразу несколько вопросов:
# 1. Как видно на рисунке в класс зеленых точек попадает много зеленных и много красных точек. Такое случилось, потому, что мы считали просто скалярное произведени, без добавления адитивной константы, которая грубо говоря приближает разделяющую прямую к истинным точкам или отдаляет. 
# 2. Есть точки, которые поадают сразу в несколько областей, что делать с ними? Здесь есть много подходов. Какие есть варианты у вас?

# + [markdown] slideshow={"slide_type": "subslide"}
# Один из вариантов следующий:
# 1. Всего плоскость разделена на 6 областей.
# 2. Считаем в какую область попала точка и получаем нужный класс.

# + [markdown] slideshow={"slide_type": "subslide"}
# ## Методы анализа качества
#
# Самый простой способо это подсчет ошибок, не верных классов:
# $$\mathcal{L}\bigr(\theta, \mathbf{X}, \mathbf{y}\bigr) = \sum_{i=1}^l\bigr[f\bigr(\theta, \mathbf{x}_i\bigr) \not= y_i\bigr].$$
#
# Также можно рассмотреть таблицу попарных ошибок:
#
# |               | y = 1 | y = 2 | y = 3 |
# | ------------- | ----- | ----- | ----- |
# | __f(x) = 1__  |  1-1  |  1-2  |  1-3  |
# | __f(x) = 2__  |  2-1  |  2-2  |  2-3  |
# | __f(x) = 3__  |  3-1  |  3-2  |  3-3  |
#
# Данная таблица показывает, сколько классификатор сделал ошибок между двумя парами классов. Например:
# 1. 2-3 это число, которое обозначает, сколько объектов класса $3$ объект отнес к классу $2$
# 2. 3-1 это число, которое обозначает, сколько объектов класса $1$ объект отнес к классу $3$

# + [markdown] slideshow={"slide_type": "subslide"}
# Посчитаем данную матрицу для синтетической выборки.

# + slideshow={"slide_type": "subslide"}
scores = np.zeros([3,3])
for k in range(3):
    pred = np.argmax(np.vstack([models[i].predict_proba(X[y==k])[:, 1] for i in range(3)]).T, axis=1)
    for i in range(3):
        scores[i, k] = sum(pred == i)
        

# + slideshow={"slide_type": "subslide"}
print(scores)

# + [markdown] slideshow={"slide_type": "subslide"}
# Результаты данной таблицы показывают такой же результат как и картинка. Хуже всего от других отделим второй класс.

# + [markdown] slideshow={"slide_type": "slide"}
# # Переобучения и контроль качества

# + [markdown] slideshow={"slide_type": "subslide"}
# В предыдущем примере мы указали, что полученная оптимальная функция сильно зависит от данных и от сложности выбранной модели. Банальный пример в качестве функции мы можем взять просто функцию, которая для каждого элемента выборки возвращает ее метку. Качество на выборке будет идеальным, но ни о какой обобщающей способности не может идти речи.
#
# Поэтому обычно выделяют этапы обучения и контроля. На этап обучения одни данные, а на этапе контроля другие.
# Перепишем постановку задачи следующем образом, пусть задана обучающая выборка:
# $$\mathbf{X}_{tr} \in \mathbb{R}^{l_{tr}\times n}, \quad \mathbf{y}_{tr} \in \mathbb{Y}^{l_{tr}},$$
# и выборка для контроля:
# $$\mathbf{X}_{vl} \in \mathbb{R}^{l_{vl}\times n}, \quad \mathbf{y}_{vl} \in \mathbb{Y}^{l_{vl}}.$$
#
# Также еще есть отложенная выборка, которая вообще не используется в подборе каких либо параметров, тестова выборка:
# $$\mathbf{X}_{ts} \in \mathbb{R}^{l_{ts}\times n}, \quad \mathbf{y}_{ts} \in \mathbb{Y}^{l_{ts}}.$$

# + [markdown] slideshow={"slide_type": "subslide"}
# В этом случае оптимальные параметры $\hat{\theta}$ находятся из оптимизационной задачи:
# $$\hat{\theta} = \arg \min_{\theta \in \mathbb{R}^n} \mathcal{L}\bigr(\theta, \mathbf{X}_{tr}, \mathbf{y}_{tr}\bigr).$$
#
# После этапа получения параметров $\hat{\theta}$ происходит измерения качества модели на выборке $\mathbf{X}_{vl}, \mathbf{y}_{vl}$.
#
# И того получаем две ошибки $\mathcal{L}\bigr(\theta, \mathbf{X}_{tr}, \mathbf{y}_{tr}\bigr)$ и $\mathcal{L}\bigr(\theta, \mathbf{X}_{vl}, \mathbf{y}_{vl}\bigr)$ и в случае, если ошибка на обучении много меньше чем ошибка на контроле, то получаем переобучение.

# + [markdown] slideshow={"slide_type": "subslide"}
# ## Пример переобучения на синтеческих данных

# + slideshow={"slide_type": "subslide"}
np.random.seed(0)
l = 6
n = 1
w = np.random.randn(n)
X_tr = np.random.randn(l, n)
y_tr = X_tr@w + np.random.randn(l)

X_vl = np.random.randn(l, n)
y_vl = X_vl@w + np.random.randn(l)

X_ts = np.random.randn(l, n)
y_ts = X_ts@w + np.random.randn(l)



# + slideshow={"slide_type": "subslide"}
x_begin = -1.05
x_end = 2.5
X_polinom = np.hstack([X_tr**0, X_tr**1, X_tr**2, X_tr**3, X_tr**4, X_tr**5])
w_polinom = np.linalg.inv(X_polinom.T@X_polinom)@X_polinom.T@y_tr
y_polinom = list(map(lambda x: np.array([x**0, x**1, x**2, x**3, x**4, x**5])@w_polinom, 
                     np.linspace(x_begin, x_end)))

# + slideshow={"slide_type": "subslide"}
plt.plot(X_tr, y_tr, 'o', label = 'points train')
plt.plot(X_ts, y_ts, 'o', label = 'points test')
plt.plot(np.linspace(x_begin, x_end), w*np.linspace(x_begin, x_end), '-', label = 'real')
plt.plot(np.linspace(x_begin, x_end), y_polinom, '-', label = 'polinom')

plt.legend(loc='best')
plt.show()

# + [markdown] slideshow={"slide_type": "slide"}
# # Борьба с переобучением

# + [markdown] slideshow={"slide_type": "subslide"}
# Бороться с переобучениям можно многими способами:
# 1. Изменения структуры модели
# 2. Добавление регуляризаторов
# 3. ...
#
# Но большинство методов борьбы с регуляризацией добавляют свои параметры (гиперпараметры), которые также нужно оптимизировать. Обычно для их оптимизации и используется метод Cross-Validation и Leave One Out.

# + [markdown] slideshow={"slide_type": "subslide"}
# ### LOO
#
# Один из простых методов борьбы с переобучением является метод Leave One Out. Для удобства обозначим $\hat{\theta}\bigr(\mathbf{X}\bigr)$ как оптимальный вектор для выборки $\mathbf{X}$. Тогда ошибка LOO определяется следующим образом:
# $$LOO\bigr(\mu, \textbf{X}_{tr}, \textbf{y}_{tr}, \textbf{X}_{vl}, \textbf{y}_{vl}\bigr) = 
# \sum_{i=1}^{l_{tr}+l_{vl}}q\bigr(\hat{\theta}\bigr(\mathbf{X}_{tr}\cup\textbf{X}_{vl}\setminus\mathbf{x}_i, \mu\bigr), \mathbf{x}_i, y_i\bigr),$$
# где $\mathbf{x}_i$ это элемент из объединенного датасета обучения и валидации.
#
# После чего оптимальный вектор параметров является решением следующей оптимизационной задачи:
# $$
# \hat{\mu} = \arg\min_{\mu \in \mathfrak{M}} LOO\bigr(\mu, \textbf{X}_{tr}, \textbf{y}_{tr}, \textbf{X}_{vl}, \textbf{y}_{vl}\bigr),
# $$
# $$
# \hat{\theta} = \hat{\theta}\bigr(\mathbf{X}_{tr}\cup\textbf{X}_{vl}, \hat{\mu}\bigr)
# $$

# + [markdown] slideshow={"slide_type": "subslide"}
# Для нашего синтетического примера. В качестве параметра $\mu$ можно рассмоттреть степень полинома для аппроксимации (изменения структуры модели).
#
# Пример, где реально используется LOO будет рассмотрен на следующей лекции.

# + [markdown] slideshow={"slide_type": "slide"}
# # Разные задачи машинного обучения
#
# Задачи машиного обучения можно разделить на два типа:
# 1. Решается одна прикладная задача. В данном случае основной целью является получить наилучший результат на отложенной выборке. В данном случае обычно есть одна выборка и нужно перебрать все возможные методы машинного обучения, чтобы получить лучший результат.
# 2. Тестируется метод на большом количестве реальных данных. В данном случае основной целью является протестировать новый метод на широком спектре задач. Рассматриваются задачи из разных областей.
# 3. Тестирования метода на большом количестве синтетических данных/ полусинтеттиических данных. Синтетические данные позволяют проверять разные гипотезы о модели. 

# + [markdown] slideshow={"slide_type": "subslide"}
# ## Прикладные задачи
#
# Обычно решение таких задач состоит из следующих этапов:
# 1. Ищуться модели, которые решают похожие задачи.
# 2. Анализ данных, анализ области откуда пришла задача.
# 3. Адаптация моделей под прикладную задачу.
# 4. Поверхностная оценка качества каждой модели на полученной выборке.
# 5. Обучения 1 или 2 лучших моделей, подбирая все возможные параметры и гиперпараметры для получения лучшего результата.
#
# Главный результат данной задачи, это получить модель (одну функцию из модели алгоритмов), которая решает конкретную задачу, на конкретном наборе данных.

# + [markdown] slideshow={"slide_type": "subslide"}
# ## Исследовательские задачи
#
# Обычно решение таких задач состоит из следующих этапов:
# 1. Анализ некоторой предметной области
# 2. Разработка новой модели машиного обучения
# 3. Проверка работоспособности метода на синтетических данных
# 4. Выявление ограничений метода
# 5. Тестирование на большом количестве реальных данных 
#
# Заметим, что основным результатом в данном случае является не одна функция, которая решает одну поставленую задачу, а новая модель алгоритмов, которая может быть применима для большого количества задач. Важно, что не обязательно, новая модель алгоритмов должна давать лучшее качество на разных выборках.

StarcoderdataJupyterScriptsDedupFiltered