diff --git a/OOP/task/7/TASK.md b/OOP/task/7/TASK.md index e3df38e..73bd9f3 100644 --- a/OOP/task/7/TASK.md +++ b/OOP/task/7/TASK.md @@ -1,5 +1,7 @@ -Ограничение по времени: 3.0 с -Ограничение по памяти: 512 МБ +### Последняя Надежда Эпсилон-9: Протокол «К-Резонанс» + +**Ограничение по времени:** 3.0 с +**Ограничение по памяти:** 512 МБ Год 2142. Межзвездная колония на планете Эпсилон-9 столкнулась с невидимой смертью. Внешне безобидная флора планеты скрывает фатальную угрозу: питательный, спасающий от голода «Звездный Корень» и парализующая нервную систему «Теневая Лоза» визуально абсолютно неразличимы. Их выдают только биоэлектрические поля, которые сканируют наши харвестеры. @@ -7,46 +9,55 @@ Командор колонии Лира врывается в ваш инженерный отсек. В её руках дымящийся кристалл памяти. -— Инженер! — кричит она, смахивая пепел с терминала. — Я знаю, ты привык поднимать надежный бэкенд и обучать сложные модели, но сейчас от твоих навыков зависят наши жизни! Я вытащила из мертвых архивов концепты древнего алгоритма K-Ближайших Соседей. Мы должны воссоздать его с нуля, и нам нужна строгая архитектура, чтобы система не рухнула. Будем писать объектно-ориентированно. Слушай меня внимательно, я зачитаю спецификацию протокола прямо с поврежденных секторов, а ты переноси это в код! +— Инженер! — кричит она, смахивая пепел с терминала. — Я вытащила из мертвых архивов чертежи древнего алгоритма K-Ближайших Соседей. Мы должны воссоздать его с нуля, иначе завтра колония вымрет. И нам нужна строгая архитектура, чтобы система не рухнула. Будем писать объектно-ориентированно. Слушай меня внимательно, я зачитаю спецификацию протокола прямо с поврежденных секторов, а ты пиши код! Лира подключает кристалл к вашему монитору, и аварийное освещение окрашивает отсек в красный цвет. -— Разделим логику выживания на модули, — диктует Командор, глядя на бегущие строки. — Сначала создай класс `DataProcessor`. Харвестеры присылают нам сплошной текстовый мусор. Напиши в нем метод `parse_sensor_data(self, record)`. Он должен принимать сырую строку, где числа и класс разделены запятыми, и возвращать кортеж. Первым элементом в нем пойдет список чисел с плавающей точкой — сырые признаки. Вторым — строка класса растения. +— Разделим логику выживания на модули, — диктует Командор, глядя на бегущие строки. — Для начала создай класс `DataProcessor`, он будет отвечать за очистку. Харвестеры присылают нам сплошной текстовый мусор. В этом классе тебе нужно написать первый метод `parse_sensor_data(self, record)`. Он должен принимать сырую строку, где числа и класс разделены запятыми, и возвращать кортеж. Первым элементом в нем пойдет список чисел с плавающей точкой — это наши сырые признаки. Вторым — строка класса растения. Она нервно стучит по экрану: -— Данные с сенсоров скачут! Чтобы алгоритм не сошел с ума от разницы в масштабах измерений, нужна калибровка. Добавь метод `calculate_statistics(self, dataset)`. Прогони через него размеченные данные и вычисли для каждого признака среднее арифметическое и стандартное отклонение. Верни это как список словарей с ключами `"mean"` и `"std"`. И помни, корень придется извлекать возведением в степень `0.5`, математических модулей у нас нет! +— Данные с сенсоров скачут! Чтобы алгоритм не сошел с ума от разницы в масштабах измерений, нам нужна калибровка. В этот же класс добавь метод `calculate_statistics(self, dataset)`. Прогони через него размеченные данные и вычисли для каждого признака среднее арифметическое и стандартное отклонение. Верни это как список словарей с ключами `"mean"` и `"std"`. И помни, корень придется извлекать возведением в степень `0.5`, у нас нет математических модулей! -— Как только получим статистическую базу, — продолжает Лира, — пиши метод `normalize_features(self, features, stats)`. Каждый сырой признак растения нужно очистить от шума, вычтя среднее и разделив на стандартное отклонение. Верни новый список идеальных, нормализованных чисел. +— Как только получим статистическую базу, — продолжает Лира, — пиши там же метод `normalize_features(self, features, stats)`. Каждый сырой признак растения нужно очистить от шума, вычтя среднее и разделив на стандартное отклонение. Этот метод должен брать список признаков и словарь статистики, а возвращать новый список идеальных, нормализованных чисел. Свет в отсеке мигает. Система охлаждения воет на пределе возможностей. -— Теперь архитектура самого предсказателя! Создай класс `KNNClassifier`. При инициализации через `__init__(self, k)` он должен принимать и сохранять внутри себя количество соседей `k`. -Затем заложи в него геометрию выживания — метод `euclidean_distance(self, vector_a, vector_b)`. Он берет два нормализованных списка чисел и вычисляет дистанцию между ними в многомерном пространстве, возвращая одно число с плавающей точкой. +— Теперь самое главное — архитектура ядра! Создай класс `KNNClassifier`. При инициализации через `__init__(self, k)` он должен принимать и сохранять внутри себя количество соседей `k`. Затем заложи в него геометрию выживания — метод `euclidean_distance(self, vector_a, vector_b)`. Он должен брать два нормализованных списка чисел и вычислять дистанцию между ними в многомерном пространстве. Просто верни одно число с плавающей точкой. Лира выводит на экран голограмму базы данных: -— Систему нужно обучить. Напиши метод `fit(self, train_features, train_labels)`, который просто сохранит нормализованную обучающую выборку и её классы внутри объекта. А чтобы найти сородичей, добавь метод `find_k_nearest(self, test_point)`. Он должен прочесать сохраненную базу `train_features` и найти `k` самых близких соседей для неизвестного растения, вернув список их классов-строк. +— Систему нужно обучить. Напиши метод `fit(self, train_features, train_labels)`, который просто сохранит нормализованную обучающую выборку и её классы внутри объекта классификатора. А чтобы найти сородичей, добавь метод `find_k_nearest(self, test_point)`. Он должен прочесать сохраненную базу и найти `k` самых близких соседей для неизвестного растения `test_point`. От тебя требуется вернуть только список их классов-строк. -— Но соседи могут расходиться во мнениях, — Лира хмурится. — Нам нужна логика большинства. Метод `majority_vote(self, neighbors)` примет этот список классов и вернет тот, который встречается чаще всего. Если будет ничья — возвращай любой из лидирующих классов. +— Но соседи могут расходиться во мнениях, — Лира хмурится. — Нам нужна логика большинства. Метод `majority_vote(self, neighbors)` в этом же классе примет этот список и вернет тот класс, который встречается чаще всего. Если будет ничья — система должна выжить, просто возвращай любой из лидирующих классов. Это и будет наш приговор. Она тяжело вздыхает, опираясь на стол: -— Оберни этот поиск и голосование в единый боевой интерфейс `predict_class(self, test_point)`. Этот метод примет одно неизвестное нормализованное растение и прогонит его через поиск соседей и голосование, чтобы сразу выдать итоговый класс-строку. -А для массовой проверки на архивах добавь метод `calculate_accuracy(self, predictions, true_labels)`. Сравни списки наших предсказаний с истинными классами и верни долю правильных ответов от `0.0` до `1.0`. Цена каждой ошибки — чья-то жизнь. +— Оберни этот поиск и голосование в единый боевой интерфейс `predict_class(self, test_point)`. Ему мы будем скармливать одно неизвестное нормализованное растение, чтобы он сам вызвал поиск соседей, провел голосование и сразу выдал итоговый класс-строку. + +— Но прежде чем отправить харвестеры в поля и кормить людей, — голос Лиры дрожит, — мы обязаны проверить алгоритм на архивах. Создай отдельный класс `AccuracyCalculator`. В нем будет всего один метод `calculate(self, predictions, true_labels)`. Сравни списки наших предсказаний с истинными классами на тестовом полигоне и верни долю правильных ответов от `0.0` до `1.0`. Цена каждой ошибки — чья-то жизнь. Лира кладет руку вам на плечо. В её глазах отражается код аварийного терминала: -— И наконец, Инженер... Великий Синтез. Снаружи классов напиши финальную функцию `run_protocol(train_raw_lines, test_raw_lines, k)`. Она объединит весь пайплайн. Создай экземпляры `DataProcessor` и `KNNClassifier`. Распарси обе выборки. Затем вычисли статистику строго и только по обучающей базе, чтобы не было утечки данных из будущего. Нормализуй обе выборки. Скорми обучающие данные в метод `fit`. Пройдись циклом по тестовым растениям, собирая предсказания через `predict_class`, и рассчитай точность. В самом конце функция должна вернуть словарь с двумя ключами: `"accuracy"` с итоговой точностью и `"predictions"` со списком всех предсказанных классов. +— И наконец, Инженер... Великий Синтез. Вне классов напиши финальную функцию `run_protocol(train_raw_lines, test_raw_lines, k)`. Она должна объединить весь пайплайн воедино. Создай экземпляры `DataProcessor`, `KNNClassifier` и `AccuracyCalculator`. Сначала распарси обе выборки. Затем вычисли статистику строго и только по обучающей базе, чтобы не было утечки данных из будущего. Потом нормализуй обе выборки. Скорми обучающие данные в метод `fit`. Пройдись циклом и сделай предсказания для каждого растения в тесте. В самом конце функция должна рассчитать точность и вернуть словарь с двумя ключами: `"accuracy"` с итоговой точностью и `"predictions"` со списком всех предсказанных классов. -Терминал призывно мигает пустым курсором. Система охлаждения реактора издает последний предупреждающий гудок. Жизнь Эпсилон-9 зависит от чистоты твоей архитектуры. Приступайте. +Терминал призывно мигает пустым курсором. Система охлаждения реактора издает последний предупреждающий гудок. Жизнь Эпсилон-9 зависит от чистоты твоей архитектуры. Приступай. --- -### Блок тестирования протокола (Чек-лист от Командора) +### Блок тестирования: Симуляция запуска на архивных данных -Лира выводит на соседний экран строгую последовательность проверки модулей. -— Как только напишешь классы, прогони систему через этот алгоритм запуска. Никаких отклонений! +Лира выводит на соседний голографический экран небольшую выборку исторических данных. +— Инженер, мы не можем рисковать. Прежде чем подключать твой код напрямую к харвестерам, запусти симуляцию на этом крошечном наборе. Если твой «Синтез» работает верно, мы спасены. -1. **Запросите справку из архивов:** Вызовите документацию (`help()`) по классам `DataProcessor` или `KNNClassifier`, чтобы убедиться, что сектора памяти целы и спецификация ясна. -2. **Создайте объекты спасения:** Инициализируйте экземпляры классов `DataProcessor` и `KNNClassifier` (задайте нужное число соседей `k`). -3. **Откалибруйте систему:** Используя объект класса `DataProcessor`, обработайте сырые данные (рассчитайте статистику и нормализуйте базу). -4. **Попробуйте классифицировать:** Попробуйте прогнать через систему неизвестное растение (`predict_class`), чтобы проверить отклик пустой базы. -5. **Завершите обучение:** Если алгоритм еще не готов к бою (выдает ошибку или пустой ответ из-за отсутствия базы), загрузите в него нормализованную обучающую выборку (вызовите метод `fit`). -6. **Соберите "урожай":** Как только модель обучена, запустите финальное сканирование тестовой базы, классифицируйте все растения и выведите результат предсказаний. \ No newline at end of file +**Требования к проверке:** + +1. Запишите в код следующие архивные данные: +```python +train_raw = [ + "0.5, 1.2, Звездный Корень", + "0.6, 1.3, Звездный Корень", + "5.1, 8.2, Теневая Лоза", + "4.9, 8.1, Теневая Лоза" +] + +test_raw = [ + "0.55, 1.25, Звездный Корень", + "5.0, 8.15, Теневая Лоза" +] \ No newline at end of file