{
"cells": [
{
"cell_type": "markdown",
"metadata": {
"id": "q0PjiV7v7aXC"
},
"source": [
"# \n",
"\n",
"# Машинное обучение. ВМК МГУ"
]
},
{
"cell_type": "markdown",
"metadata": {
"id": "uvZhhOzlKCMH"
},
"source": [
"# Практическое задание 3: Основы sklearn на примере KNN. Нормализация признаков и Кросс-валидация\n",
"\n",
"## Уровень: **Базовый (Base)**"
]
},
{
"cell_type": "markdown",
"metadata": {
"id": "uqAqpeYFxg5T"
},
"source": [
"# О формате сдачи\n",
"\n",
"🔷 **При решении ноутбука используйте данный шаблон**\n",
"\n",
" ✅ Можно добавлять новые ячейки любых типов\n",
" ❌ Не нужно удалять текстовые ячейки c разметкой частей ноутбука и формулировками заданий\n",
"\n",
"\n",
"🔷 **При оценивании задач учитывается код**\n",
"\n",
" ✅ Задания, в которых необходим код, обычно помечаются фразами \"Your code here\"/\"Ваш код\" и аналогичными\n",
" ❌ Ответы на вопросы без сопутствующего кода оцениваются в 0 баллов\n",
" ❌ Наличе работоспособного кода в ноутбуке, если на сказано иного, обязательно\n",
"\n",
"🔷 **При оценивании задач учитываются выводы**\n",
"\n",
" ✅ Задания, в которых необходимы выводы, обычно помечаются фразами Вывод\"/\"Ответ на вопрос\"/\"Ваш текст\" и аналогичными\n",
" ✅ Обычно выводы подразумевают под собой текстовый ответ (можно писать markdown, latex).\n",
" ✅ Сопутствующие изображения, графики, таблички - приветствуются!\n",
" ❌ При отсутствии выводов задание не засчитается на полный балл\n",
"\n",
"-----------\n",
"\n",
"\n",
"* Многие из заданий можно выполнить несколькими способами. Не существуют единственно верного, но попробуйте максимально задействовать арсенал pandas и ориентируйтесь на простоту и понятность вашего кода. Не забывайте, что можно гуглить и что-то искать на stackoverflow, например.\n",
"\n",
"\n",
"\n",
"\n",
"\n"
]
},
{
"cell_type": "markdown",
"metadata": {
"id": "exCE5gc27aXG"
},
"source": [
"**Примерное время выполнения (execution time/время выполнения, если нажать run all) всех ячеек ноутбука при правильной реализации: 10 минут **"
]
},
{
"cell_type": "markdown",
"metadata": {
"id": "ciHgl9KGlJua"
},
"source": [
"# Подготовка рабочей среды\n",
"\n",
"Сначала установим нужные нам версии библиотек. Мы гарантируем, что в данных версиях задание будет корректно отрабатывать.\n",
"\n",
"После установки нужных версий, **возможно,** нужно перезагрузить среду (runtime), но скорее всего вам это не понадобится\n",
"\n",
"\n",
"На скачивание файла и установку понадобится не более 5 минут.\n",
"\n",
"**Важно!**\n",
"\n",
"Устанавливать нужные версии нужно каждый раз, когда создается новый рантайм. Например, если вы 2 часа подряд делаете это задание, то подготовить библиотеки достаточно 1 раз. Но если вы, например, начали в понедельник, затем закрыли/выключили ноутбук, то при продолжении в среду, вам нужно будет запустить рантайм заново и следовательно заново установить библиотеки.\n",
"\n",
"**Важно!**\n",
"Если вы предпочитаете делать практические задания на своем личном ноутбуке, то проверьте, что вы установили рабочее окружение в [соответствии с гайдом](https://github.com/MSU-ML-COURSE/ML-COURSE-24-25/blob/main/tutorials/%D0%A2%D1%83%D1%82%D0%BE%D1%80%D0%B8%D0%B0%D0%BB%20%D0%BF%D0%BE%20%D1%83%D1%81%D1%82%D0%B0%D0%BD%D0%BE%D0%B2%D0%BA%D0%B5%20%D1%80%D0%B0%D0%B1%D0%BE%D1%87%D0%B5%D0%B3%D0%BE%20%D0%BE%D0%BA%D1%80%D1%83%D0%B6%D0%B5%D0%BD%D0%B8%D1%8F%20%D0%B2%20Python%20%D0%B4%D0%BB%D1%8F%20%D1%80%D0%B5%D1%88%D0%B5%D0%BD%D0%B8%D1%8F%20%D0%B7%D0%B0%D0%B4%D0%B0%D1%87%20(2).pdf)"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"id": "fMgnzKcqlIen"
},
"outputs": [],
"source": [
"! curl https://raw.githubusercontent.com/MSU-ML-COURSE/ML-COURSE-25-26/refs/heads/master/requirements/requirements.txt -o ./requirements_2025_26_for_colab_small.txt\n",
"! pip install -r ./requirements_2025_26_for_colab_small.txt"
]
},
{
"cell_type": "markdown",
"metadata": {
"id": "-0DgvOqZix4h"
},
"source": [
"Проверим версию библиотеки:"
]
},
{
"cell_type": "code",
"execution_count": 4,
"metadata": {
"id": "QwWXXElyiRYq"
},
"outputs": [],
"source": [
"import catboost\n",
"assert(catboost.__version__ == '1.2.8')"
]
},
{
"cell_type": "markdown",
"metadata": {
"id": "53PQKGPVizrv"
},
"source": [
"Теперь можно приступать к выполнению задания! :)"
]
},
{
"cell_type": "markdown",
"metadata": {
"id": "7Z8vzxJLyEDk"
},
"source": [
"-----------\n",
""
]
},
{
"cell_type": "markdown",
"metadata": {
"id": "6MBU1hOZ7aXH"
},
"source": [
"# Часть 0. Знакомство с библиотекой scikit-learn"
]
},
{
"cell_type": "markdown",
"metadata": {
"id": "_ZjnB4VJJuR6"
},
"source": [
"`Scikit-learn` - это библиотека машинного обучения с открытым исходным кодом, которая предоставляет различные инструменты для предобработки данных и обучения моделей.\n",
"\n",
"Подробную документацию с обширным количеством теоретических и практических примеров можно найти на [сайте библиотеки](https://scikit-learn.org/stable/index.html)"
]
},
{
"cell_type": "markdown",
"metadata": {
"id": "m-BabH7eK9GP"
},
"source": [
"## Импорт библиотеки"
]
},
{
"cell_type": "markdown",
"metadata": {
"id": "Um1PZOAzLCyv"
},
"source": [
"Можно импортировать библиотеку целиком:"
]
},
{
"cell_type": "code",
"execution_count": 5,
"metadata": {
"id": "U3dp8WbZLCG7"
},
"outputs": [],
"source": [
"import sklearn"
]
},
{
"cell_type": "markdown",
"metadata": {
"id": "sf0_wD24LBNE"
},
"source": [
"Однако, так как библиотека очень обширная и [содержит большое количество модулей](https://scikit-learn.org/stable/api/index.html), то импортируют либо модуль, либо классы/методы точечно и целенаправленно:"
]
},
{
"cell_type": "code",
"execution_count": 8,
"metadata": {
"id": "DVpuJQwMLO2m"
},
"outputs": [],
"source": [
"from sklearn.neighbors import KNeighborsClassifier # класс, с помощью которого мы в дальнейшем будем обучать kNN"
]
},
{
"cell_type": "markdown",
"metadata": {
"id": "ALAVj82zLhrn"
},
"source": [
"**Подсказка** обычно по названию модуля интуитивно понятно, какие методы и функции в нем содержатся. С увеличением практики использования библиотеки вы сможете лучше и быстрее навигироваться в ней. Например, модуль neighbors содержит различные методы поиска ближайших соседей. Помните, что есть как версии для классификации, так и для задач регрессии? Вот версию для классификации мы выше как раз и импортировали. Однако если провалиться в [описание модуля](https://scikit-learn.org/stable/api/sklearn.neighbors.html) можно увидеть еще много разных классов и функций, которые так или иначе связаны с поиском соседей."
]
},
{
"cell_type": "markdown",
"metadata": {
"id": "OsJ49yZjMXux"
},
"source": [
"## Общее устройство моделей\n",
"\n",
"Одним из несравненных плюсов библиотеки является то, что она предоставляет максимально общий и максимально понятный интерфейс взаимодействия с различными алгоритмами машинного обучения.\n",
"\n",
"Обычно, чтобы решить задачу машинного обучения, нам нужно (как минимум):\n",
"* Знать, какой моделью (алгоритмом) хотим воспользоваться\n",
"* Обучить ее на некоторое обучающей выборке\n",
"* Уметь узнавать предсказания модели на новой выборке\n",
"\n",
"Вам не нужно запоминать для каждой конкретной модели, а как она вообще обучается и как это написать: библиотека все делает за вас."
]
},
{
"cell_type": "markdown",
"metadata": {
"id": "Gi32BlL6NFzJ"
},
"source": [
"\n",
"Каждая **модель машинного обучения** имеет два метода: `fit` - метод ответственный за обучения модели и `predict` - метод ответственный за предсказание целевой переменной.\n",
"* Как правило, метод `fit` принимает на вход два аргумента: $X_{train}$ - **обучающая** выборка, $y_{train}$ - значения целевых переменных на объектах обучающей выборки.\n",
"* Метод `predict` принимает на вход набор данных $X_{test}$ и выдает предсказанные значения целевых переменных $y_{test}$ на этом наборе данных.\n",
"* В моделях классификации бывает метод `predict_proba`, который принимает на вход набор данных и выдает вероятности принадлежности классам.\n",
"\n",
"\n",
"\n",
"- Если нами решается задача *классификации*, то вектор целевых переменных $y$ содержит целые числа (или другое множество дискретных величин).\n",
"- При решении задачи *регрессии* вектор $y$ содержит вещественные числа.\n"
]
},
{
"cell_type": "markdown",
"metadata": {
"id": "VSYINz7HN93c"
},
"source": [
"\n",
"**Выборка (данные)** $X$ представляется в виде матрицы размера `(n_samples, n_features)`, то есть каждому *объекту* соответствует отдельная строка, а каждому *признаку* - отдельный столбец.\n",
"\n",
" **Целевая переменная** обычно представляется одномерным вектором размера `(n_samples)` - предсказаниями для каждого объекта, в случае предсказания вероятностей классов - матрицей размера `(n_samples, n_classes)` - значения вероктностей принадлежности объекта к тому или иному классу\n"
]
},
{
"cell_type": "markdown",
"metadata": {
"id": "MzWYc7HPSFSh"
},
"source": [
"Приведем пример стандартной работы с библиотекой `scikit-learn`\n",
"\n",
"**Подсказка** Это совсем-совсем базовый способ обучения моделей, на практике используют много дополнительных приемов, постепенно которые мы будем с вами изучать"
]
},
{
"cell_type": "code",
"execution_count": 9,
"metadata": {
"colab": {
"base_uri": "https://localhost:8080/"
},
"id": "9LX98EkWRXDH",
"outputId": "3015ab1a-f0de-4e07-db2e-b69fd05c469f"
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"[0 1]\n"
]
}
],
"source": [
"#Создаем обучающую выборку\n",
"X_train = [[0], [1], [2], [3]] # матрица размера 4 x 1, 4 - объекта, 1 признак\n",
"y_train = [0, 0, 1, 1] # обучающий вектор целевых переменных\n",
"\n",
"#Создаем тестовую выборку\n",
"X_test = [[1.1], [2.8]] # матрица размера 2x1, 2 - объекта, 1 признак\n",
"\n",
"#Создаем объект класса 3-NN классификатора\n",
"neigh = KNeighborsClassifier(n_neighbors=3)\n",
"\n",
"#Обучаем классифкатора на созданной ранее выборке\n",
"neigh.fit(X_train, y_train)\n",
"\n",
"#Предсказываем метку класса нового объекта с помощью метода predict\n",
"y_test = neigh.predict(X_test)\n",
"print(y_test) # y_test - вектор размера 2 (в тестовой выборке 2 объекта)"
]
},
{
"cell_type": "markdown",
"metadata": {
"id": "XsYzlFYAQSY_"
},
"source": [
"## Общее устройство предобработки данных\n",
"\n",
"На практике почти никогда вы не встретите идеальных данных :) Вам всегда понадобится на них посмотреть, проанализировать, где-то почистить, где-то преобразовать и пр.\n",
"\n",
"Упрощая, все работу с данными можно проделать с помощью 2 библиотек:\n",
"\n",
"- `pandas` - с помощью этой библиотеки проанализировать данные, посмотреть на то, что в них хранится, принять решение о удалении мусора, замены каких-то значений и прочего. Данные техники вы совсем немного затронули в предыдущем домашнем задании, но подробнее мы к ним вернемся в следующем домашнем задании\n",
"\n",
"- `skelarn` - технические способы обработки данных, особенности которых связаны непосредственно с моделью или способом обучения\n",
"\n",
"**Пример** Данная библиотека поможет решить вопрос: а как нам признаки с типом строка (например, название видео) \"запихнуть\" в kNN? Он же только чиселки умеет понимать?\n",
"\n",
"**Пример**\n",
"Или на следующий вопрос: с лекции немного припоминаю, что для более хорошей работы метрических методов хорошо бы данные привести в один масштаб. Как это можно быстро и безболезненно сделать?\n",
"\n",
" Например, есть 2 признака:\n",
"- вероятность покупки $p$ - лежит на отрезке [0, 1]\n",
"- стоимость покупки $s$ - лежит на отрезке [0, 100000500000]\n",
"\n",
"Хотелось бы чтобы оба признака были на отрезке [0, 1], потому что покупка, кажется, может вносить гораздо больший вклад по расстоянию в обучении модели..\n"
]
},
{
"cell_type": "markdown",
"metadata": {
"id": "41UPaR_b7aXH"
},
"source": [
"\n",
"В `sklearn` способы обработки данных также предоставляются через различные классы, которые имеют одинаковый интерфейс:\n",
"\n",
"Каждый **модуль предобработки** данных имеет два метода: `fit` и `transform`.\n",
"\n",
"* Как правило, метод `fit` принимает на вход **обучающую** выборку $X_train$ и считает по ней необходимые для заданного типа преобразования статистики.\n",
"* Метод `transform` преобразует входные данные, используя статистики, посчитанные при вызове метода `fit` и возвращает преобразованные данные.\n",
"\n",
"\n",
"**Пояснение:** Когда не хватает `pandas` или `skelarn`, можно обратиться к другим библиотекам, например, `numpy` :) Sklearn обычно одинаково хорошо работает с данными, представленным как и pd.DataFrame, так и numpy-array или обычными вложенными питоновскими списками\n",
"\n",
"------\n"
]
},
{
"cell_type": "code",
"execution_count": 6,
"metadata": {
"colab": {
"base_uri": "https://localhost:8080/"
},
"id": "WHa66l-oSzgb",
"outputId": "7fa5b729-3a40-434f-94e6-6b6046352353"
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"[[0.000000e+00 0.000000e+00]\n",
" [5.000000e-01 1.000000e+02]\n",
" [1.000000e+00 1.000005e+09]]\n"
]
}
],
"source": [
"import numpy as np\n",
"X_train = np.array([[0, 0], [0.5, 100], [1, 1000005000]]) # numpy используем для удобства отображения\n",
"# выборка из 3 примеров, 1й признак - вероятность покупки p\n",
"# 2й признак - стоимость покупки s\n",
"\n",
"print(X_train)"
]
},
{
"cell_type": "code",
"execution_count": 10,
"metadata": {
"colab": {
"base_uri": "https://localhost:8080/",
"height": 74
},
"id": "EHH79PIHTUI4",
"outputId": "a443bccd-5eb7-4dce-c9dc-e73311c861e4"
},
"outputs": [
{
"data": {
"text/html": [
"
MinMaxScaler()In a Jupyter environment, please rerun this cell to show the HTML representation or trust the notebook.
MinMaxScaler()
GridSearchCV(cv=3,\n",
" estimator=Pipeline(steps=[('normalizer', 'passthrough'),\n",
" ('classifier', KNeighborsRegressor())]),\n",
" n_jobs=-1,\n",
" param_grid={'classifier__metric': ['euclidean', 'cosine'],\n",
" 'classifier__n_neighbors': [1, 5, 10],\n",
" 'classifier__weights': ['uniform', 'distance'],\n",
" 'normalizer': ['passthrough', MinMaxScaler(),\n",
" StandardScaler()]},\n",
" scoring='r2', verbose=10)In a Jupyter environment, please rerun this cell to show the HTML representation or trust the notebook. GridSearchCV(cv=3,\n",
" estimator=Pipeline(steps=[('normalizer', 'passthrough'),\n",
" ('classifier', KNeighborsRegressor())]),\n",
" n_jobs=-1,\n",
" param_grid={'classifier__metric': ['euclidean', 'cosine'],\n",
" 'classifier__n_neighbors': [1, 5, 10],\n",
" 'classifier__weights': ['uniform', 'distance'],\n",
" 'normalizer': ['passthrough', MinMaxScaler(),\n",
" StandardScaler()]},\n",
" scoring='r2', verbose=10)Pipeline(steps=[('normalizer', MinMaxScaler()),\n",
" ('classifier',\n",
" KNeighborsRegressor(metric='euclidean', n_neighbors=10,\n",
" weights='distance'))])MinMaxScaler()
KNeighborsRegressor(metric='euclidean', n_neighbors=10, weights='distance')