<jupyter_start><jupyter_text>Основы программирования в Python*Семинар по регрессии, <NAME>  *  Правила игры и организационные моменты* В ходе курса мы выдадим несколько домашних заданий. Каждое задание будет представлять собой набор данных и тетрадку наподобие этой, в которой вам самим нужно будет выполнить некоторые задания. Тетрадка с решениями должна быть исполняемой, то есть, если запустить все ячейки от начала до конца, ошибок (из-за которых исполнение остановится) быть не должно.  + Задачи, для решения которых предоставлен неработающий код (код, который невозможно запустить из-за наличия грубых ошибок/опечаток), не засчитываются, даже если при этом зафиксирован верный результат.  + Если при проверке работ установлен факт нарушения академической этики, студент получает оценку «0» за данную работу. Работа студента, предоставившего свою работу для списывания, также аннулируется.  Самостоятельный поиск информацииНе у всех задач, которые вам встретятся, есть готовые решения и, тем более, не ко всем вас будут детально готовить. В течение курса вам придется искать много информации самостоятельно — это один из ключевых навыков любой современной профессии. Чтобы искать эффективно, не набирайте в поиске просто формулировку задачи. Подумайте, какое **конкретное** действие вам нужно сделать и какого именно знания вам для этого не хватает, и сформулируйте этот запрос. Лучше всего писать запрос сразу **на английском** — глобальное сообщество программистов и аналитиков (да и в любой другой области) **гораздо** шире, чем русскоязычное.**Пример:** Допустим, вам нужно, как в семинаре, показать студентов с оценкой по английскому выше 7 и по мат. анализу выше 6. Вы помните, как выбирать строки по условию, но не представляете, что делать, если условий несколько.*Очень плохой запрос:* «Сколько студентов имеют выше 7 по английскому языку и выше 6 по мат. анализу python»*Плохой запрос:* «pandas выбрать строки по нескольким условиям»*Хороший запрос:* «pandas select rows by multiple conditions»По хорошему запросу ответ находится с первой ссылки. Запрос на русском плохой, потому что первые результаты в этом случае — машинные переводы вопросов со StackOverflow, а не оригинальный русскоязычный контент. Полезные ссылки* https://google.com/* https://stackoverflow.com/Если у вас не получилось найти гайд/туториал/разбор вашего вопроса, это не повод отчаиваться. Ответ может прятаться в документации к библиотеке. Читать документацию совсем не страшно, и там тоже бывают очень подробные примеры.* https://docs.python.org/3/* https://pandas.pydata.org/pandas-docs/stable/* https://docs.scipy.org/doc/numpy/reference/* https://matplotlib.org/contents.html* http://scikit-learn.org/stable/documentation.html ---  Оптимизация при помощи градиентного спуска. Задача регрессии. Для работы нам понадобятся некоторые библиотеки: * `Pandas` - библиотека для удобной работы с таблицами;* `NumPy` - библиотека для работы с массивами;* `scikit-learn` - библиотека, которая содержит реализации множества алгоритмов машинного обучения;* `Matplotlib` - библиотека для построения графиков.<jupyter_code>import pandas as pd

import numpy as np

from sklearn.linear_model import LinearRegression
from sklearn.datasets import make_regression
from sklearn.model_selection import train_test_split
from sklearn.metrics import mean_absolute_error

import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import Axes3D
%matplotlib inline<jupyter_output><empty_output><jupyter_text>Визуализация градиентного спуска для функции с двумя переменными: Для примера визуализации возьмем функцию: $f(x,y) = x ^ 2 + 5sin(x) + y ^ 2 + 5sin(y)$<jupyter_code>f = lambda x, y: x ** 2 + 5 * np.sin(x) + y ** 2 + 5 * np.sin(y)<jupyter_output><empty_output><jupyter_text>Тогда её частные производные равны:$\frac{df}{dx} = 2x + 5cos(x)$$\frac{df}{dy} = 2y + 5cos(y)$<jupyter_code>df_x = lambda x: 2 * x + 5 * np.cos(x)
df_y = lambda y: 2 * y + 5 * np.cos(y)<jupyter_output><empty_output><jupyter_text>Визуализируем исходную функцию при помощи библиотеки `Matplotlib`:<jupyter_code>x = np.linspace(-5, 7, 1000) # создаем массив из 1000 равноудаленых в промежутке от -5 до 7
y = np.linspace(-7, 5, 1000) # создаем массив из 1000 равноудаленых в промежутке от -7 до 5

X, Y = np.meshgrid(x, y) # создаем "сетку" для рисунка
zs = np.array(f(X.reshape(-1), Y.reshape(-1))) # находим результат функции f для каждого элемента сетки
Z = zs.reshape(X.shape) # меняем размер массива zs

fig = plt.figure(figsize=(15, 10))
ax = fig.add_subplot(111, projection='3d')
ax.plot_surface(X, Y, Z)
ax.set_xlabel('x')
ax.set_ylabel('y')
ax.set_zlabel('f(x,y)')
ax.view_init(50, 120)
plt.show()
def optimize_2d(f, df_x, df_y, learning_rate, start_point=[2.5, 2.0], stop_eps = 1e-10, compute_learning_rate=None):
    '''
    Function to calculate and show minimum search by gradient descent.
    
    Args:
        f (function): function to find min
        df_x (function): derivative of initial function by x
        df_y (function): derivative of initial function by y 
        learning_rate (float, int): initial learning rate for gradient descent
        stop_eps (float, int): stop criteria
        compute_learning_rate (function): function to recalculate learning rate
        
    Output:
        x1_list (list): list of ending points for first variable
        x2_list (list): list of ending points for second variable
        y_list (list): list of function value at ending points
    '''
    
    x1_list, x2_list, y_list = [start_point[0]], [start_point[1]], [f(start_point[0], start_point[1])]
    start_point_old = []
    start_point_old.append(start_point[0]+stop_eps*1000)
    start_point_old.append(start_point[1]+stop_eps*1000)
    
    while abs(start_point[0] - start_point_old[0]) > stop_eps and abs(start_point[1] - start_point_old[1]) > stop_eps: 
        
        start_point_old[0] = start_point[0]
        start_point_old[1] = start_point[1]
        
        direction_1 = -df_x(start_point_old[0])
        direction_2 = -df_y(start_point_old[1])
        
        if compute_learning_rate is not None:
            learning_rate = compute_learning_rate(learning_rate)
            
        start_point[0] = start_point_old[0] + learning_rate * direction_1
        start_point[1] = start_point_old[1] + learning_rate * direction_2
        x1_list.append(start_point[0])
        x2_list.append(start_point[1])
        y_list.append(f(start_point[0], start_point[1]))
        
        
    print("Local min:", start_point)
    
    X, Y = np.meshgrid(x, y)
    zs = np.array(f(np.ravel(X), np.ravel(Y)))
    Z = zs.reshape(X.shape)
    
    
    fig = plt.figure(figsize=(15, 10))
    ax = fig.add_subplot(111, projection='3d')
    ax.plot_surface(X, Y, Z, alpha=0.7)
    ax.set_xlabel('x')
    ax.set_ylabel('y')
    ax.set_zlabel('f(x,y)')
    ax.view_init(50, 120)
    ax.scatter3D(x1_list, x2_list, y_list, c='r', marker='o', edgecolors='face', s=20, alpha=1)

    plt.show()
    
    return x1_list, x2_list, y_list
_ = optimize_2d(f, df_x, df_y, 0.1, start_point=[4, -4])<jupyter_output>Local min: [-1.1056550364033655, -1.1105105036023977]<jupyter_text>Проблеммы градиентного спуска: Давайте возьмем простую функцию для примера: $f(x) = x ^ 2 - 20sin(x)$Её производная равна: $\frac{df}{dx} = 2x - 20cos(x)$Мы будем аналогично предыдущему пункту визуализировать алгоритм градиентного спуска при помощи библиотеки `Matplotlib`:<jupyter_code>f = lambda x: x ** 2 - 20 * np.sin(x)
df = lambda x: 2 * x - 20 * np.cos(x)

x = np.linspace(-10, 10, 1000)
plt.title('$f(x) = x^2 - 20sin(x)$')
plt.xlabel('x')
plt.ylabel('f(x)')
plt.plot(x, f(x))
plt.show()
def optimize_1d(f, df, learning_rate, start_point=7, stop_eps = 0.05, compute_learning_rate=None):
    '''
    Function to calculate and show minimum search by gradient descent.
    
    Args:
        f (function): function to find min
        df (function): derivative of initial function to find min
        learning_rate (float, int): initial learning rate for gradient descent
        start_point (float, int): starting point for gradient descent 
        stop_eps (float, int): stop criteria
        compute_learning_rate (function): function to recalculate learning rate
        
    Output:
        x_list (list): list of ending points
        y_list (list): list of function value at ending points
    '''
    
    x_list, y_list = [start_point], [f(start_point)]
    start_point_old = np.inf

    while abs(start_point - start_point_old) > stop_eps: 
        
        start_point_old = start_point
        direction = -df(start_point_old)
        
        if compute_learning_rate is not None:
            learning_rate = compute_learning_rate(learning_rate)
            
        start_point = start_point_old + learning_rate * direction
        x_list.append(start_point)
        y_list.append(f(start_point))

        
    print("Минимум =", start_point)
    print("Кол-во шагов =", len(x_list)-1)
    
    plt.scatter(x_list, y_list, c="r")
    plt.plot(x_list, y_list, c="r")
    plt.plot(x, f(x), c="b")
    plt.xlabel('x')
    plt.ylabel('f(x)')
    plt.title("Траектория градиентного спуска")
    
    return x_list, y_list<jupyter_output><empty_output><jupyter_text>Перейдем непосредственно к проблемам с которыми вы можете столкнуться, при использовании алгоритма градиентного спуска. Если функция не выпуклая, то градиентный спуск может сходится к локальному минимуму, а не к глобальному:<jupyter_code>x_list, y_list = optimize_1d(f, df, learning_rate=0.02, start_point=-7)<jupyter_output>Минимум = -4.33106152319398
Кол-во шагов = 8<jupyter_text>Если функция не выпуклая, то при неудачном шаге градиентный спуск может сходится максимуму О_о (это редкая ситуация, но возможная):<jupyter_code>x_list, y_list = optimize_1d(f, df, learning_rate=0.11, start_point=8.54)<jupyter_output>Минимум = 5.268358275007972
Кол-во шагов = 2<jupyter_text>При слишком высоком множителе (learning_rate) градиентный спуск может расходится или сходится вне точки глобального минимума:<jupyter_code>x_list, y_list = optimize_1d(f, df, learning_rate=0.5, start_point=3)<jupyter_output>Минимум = -8.964122065469992
Кол-во шагов = 224<jupyter_text>Если все параметры подоброны удачно, то градиентный спуск сходится к глобальному минимуму. Например, в данном случае мы воспользовались маленькой популярной хитростью: при каждом последующем шаге мы уменьшаем learning_rate. Такой подход приводит к тому, что сначала перед антиградиентом стоит большой коэффициент (в примере, `0.15`), но с каждым шагом он уменьшается. На втором шаге перед антиградиентом коэффициент равен `0.15*0.9=0.135`, на третьем - `0.15*0.9*0.9=0.1215`, и так далее:<jupyter_code>x_list, y_list = optimize_1d(f, df, learning_rate=0.15, start_point=9, compute_learning_rate = lambda x: x * 0.9)<jupyter_output>Минимум = 1.4297699594309685
Кол-во шагов = 10<jupyter_text>Решение задачи регрессии: Задача регрессии - предсказать значение целевой переменной по признаку (набору признаков):<jupyter_code>X, Y = make_regression(n_samples=100, n_features=2)

model_reg = LinearRegression().fit(X[:, 0].reshape(-1, 1), Y)
y_pred = model_reg.predict(X[:, 0].reshape(-1, 1))

plt.figure(figsize=(8, 6))
plt.title("Задача регрессии")
plt.plot(X[:, 0], y_pred, color='red', linewidth=2)
plt.scatter(X[:, 0], Y, marker='o', s=75)<jupyter_output><empty_output><jupyter_text>Для того чтобы познакомиться с задачей регрессии мы будем пытаться спрогнозировать стоимость продажи дома в зависимости от его характеристик. Доступные нам данные представлены ниже. Датасет состоит из 15 тысяч наблюдений. * `date` – дата продажи дома (string)* `bedrooms` – кол-во спальных комнат (float32)* `bathrooms` – кол-во ванных комнат по отношению к спальням (float32)* `sqft_living` – площадь дома в кв. метрах (int32)* `sqft_lot` – площадь участка в кв. метрах (int32)* `floors` – кол-во этажей в доме (float32)* `waterfront` – есть ли вид на берег (bool)* `condition` – состояние дома (int32)* `grade` – состояние дома и участка (int32)* `sqft_above` – площадь дома отдельно без подвала (int32)* `sqft_basement` – квадратные метры подвала (int32)* `yr_built` – год постройки (int32)* `yt_renovated` – год реновации (int32)* `zipcode` – зип-код (int32)* `lat` – координата по широте (float32)* `long` – координата по долготе (float32)* `price` – стоимость продажи дома (float32) Загружаем данные при помощи уже известной нам библиотеки `pandas`. Заметим, что считывать csv-файлы можно и со сторонних интернет-ресурсов. В домашних работах и при выполнении проектов рекомендуется пользоваться аналогичным подходом.<jupyter_code>df = pd.read_csv('https://raw.githubusercontent.com/Skvidvardin/Fun/master/train_houses.csv')<jupyter_output><empty_output><jupyter_text>Проверим наличие пропусков в колонках:<jupyter_code>df.isnull().any()<jupyter_output><empty_output><jupyter_text>Проведем экспресс-анализ данных:<jupyter_code>df.head()
_ = df.drop('date', axis=1).drop('waterfront', axis=1).hist(figsize=(15, 15))<jupyter_output><empty_output><jupyter_text>Во-первых, таблица содержит абсолютно мультиколлинеарные столбцы `sqft_living`, `sqft_above`, `sqft_above`. Один из них необходимо удалить из выборки.<jupyter_code>np.any(df['sqft_living'] != df['sqft_above']+df['sqft_basement'])
df = df.drop('sqft_living', axis=1)<jupyter_output><empty_output><jupyter_text>Во-вторых, можно заметить, что вместо `NaN` в столбце `yr_renovated` используется значение 0. В данном случае имеется множество способов это исправить. Мы заменим год постройки (`yr_built`) на год реновации (`yr_renovated`), если реновация осуществлялась. Затем удалим столбец `yr_renovated`:<jupyter_code>mask = df.yr_renovated > 0
df.loc[mask, 'yr_built'] = df.yr_renovated
df = df.drop('yr_renovated', axis=1)<jupyter_output><empty_output><jupyter_text>В-третьих, столбец `date` не несет значительной информации, поэтому мы просто его удалим:<jupyter_code>df = df.drop('date', axis=1)<jupyter_output><empty_output><jupyter_text>Итоговые данные для обучения модели:<jupyter_code>df.head()<jupyter_output><empty_output><jupyter_text>Для того чтобы у нас была возможность проверить модели, выборку необходимо разбить на `test` и `train`. На `train` мы будем обучать модели, а на `test` проводить проверки качаства работы. В противном случае, существует вероятность переобучения.<jupyter_code>X_train, X_test, y_train, y_test = train_test_split(df.drop('price', axis=1), df['price'], 
                                                    test_size=0.3, random_state=42)<jupyter_output><empty_output><jupyter_text>Обучим линейную регрессию:<jupyter_code>lr = LinearRegression()
lr.fit(X_train, y_train)<jupyter_output><empty_output><jupyter_text>Построим предсказания для тестовой выборки:<jupyter_code>pred = lr.predict(X_test)<jupyter_output><empty_output><jupyter_text>Посчитаем среднию ошибку предсказаний по модулю:<jupyter_code>mean_absolute_error(pred, y_test)
y_test.describe()<jupyter_output><empty_output><jupyter_text>(Опционально) Введение в NumPy: Основным объектом NumPy является однородный многомерный массив. Это набор элементов, проиндексированных натуральными числами. Размерности массивов в NumPy называются осями. Как и в Pandas, массивы можно получить из других структур данных. Например из листа листов (в примере) или из таблицы Pandas.<jupyter_code>arr = np.array([[1, 2, 3], [4, 5, 6]])
arr<jupyter_output><empty_output><jupyter_text>Также массивы можно создать при помощи стандартных функций NumPy.<jupyter_code>ones = np.ones((2, 3, 4)) # 3 оси: нулевая ось - 2 элемента, первая - 3, вторая - 4
ones
ints = np.random.randint(low=3, high=10, size=(2, 3, 4))
ints<jupyter_output><empty_output><jupyter_text>Стандартные атрибуты массивов NumPy: Можно посмотреть **количество осей (размеров)** созданного нами массива:<jupyter_code>ints.ndim<jupyter_output><empty_output><jupyter_text>Можно посмотреть **размеры** созданного нами массива:<jupyter_code>ints.shape<jupyter_output><empty_output><jupyter_text>Можно посмотреть **кол-во элементов** созданного нами массива:<jupyter_code>ints.size<jupyter_output><empty_output><jupyter_text>Можно посмотреть **тип данных элементов** созданного нами массива:<jupyter_code>ints.dtype<jupyter_output><empty_output><jupyter_text>Стандартные методы для массивов NumPy: Изменить размерность массива:<jupyter_code>ran = np.arange(1, 7)
ran
ran = ran.reshape((2, 3))
ran<jupyter_output><empty_output><jupyter_text>Сумма элементов массива:<jupyter_code>ran.sum()
ran.sum(axis=1)<jupyter_output><empty_output><jupyter_text>Транспонирование массива (2, 3, 4) -> (4, 3, 2):<jupyter_code>ints.transpose()<jupyter_output><empty_output><jupyter_text>Выборочная дисперсия элементов массива (c 1 элементом свободы):<jupyter_code>ints.var(ddof=1)<jupyter_output><empty_output><jupyter_text>Операции над массивами NumPy:<jupyter_code>ran_one = np.arange(1, 7).reshape((2, 3))
ran_two = np.arange(3, 9).reshape((2, 3))
print(ran_one)
print()
print(ran_two)<jupyter_output>[[1 2 3]
 [4 5 6]]

[[3 4 5]
 [6 7 8]]<jupyter_text>Поэлементное сложение:<jupyter_code>ran_one + ran_two<jupyter_output><empty_output><jupyter_text>Поэлементное умножение:<jupyter_code>ran_one * ran_two<jupyter_output><empty_output><jupyter_text>Поэлементное возведение в степень:<jupyter_code>ran_one**2<jupyter_output><empty_output><jupyter_text>Применение функции синуса/косинуса к элементам массива:<jupyter_code>np.sin(ran)<jupyter_output><empty_output><jupyter_text>Наложение условия на элементы массива:<jupyter_code>ran<5<jupyter_output><empty_output><jupyter_text>Матричное умножение:<jupyter_code>ran_one @ ran_two.transpose()<jupyter_output><empty_output><jupyter_text>Взятие экспоненты от элементов массива:<jupyter_code>np.exp(ran)<jupyter_output><empty_output><jupyter_text>Наибольший элемент массива:<jupyter_code>ran.max()<jupyter_output><empty_output><jupyter_text>Наименьший элемент массива:<jupyter_code>ran.min()<jupyter_output><empty_output>
{'fraction_non_alphanumeric': 0.1075995309620861, 'fraction_numerical': 0.024624490479647106, 'mean_word_length': 5.4447643037063695, 'pattern_counts': {'":': 0, '<': 161, '<?xml version=': 0, '>': 165, 'https://': 7, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 2, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'chain_length': '41', 'dir': 'jupyter-structured-clean-dedup', 'id': '272435', 'license': 'MIT', 'n_tokens_mistral': 7444, 'n_tokens_neox': 7917, 'n_words': 1628, 'path': 'seminar_2_full.ipynb', 'repo_name': 'apnkv/mondrian-forest', 'size': '17916'}