Урок 7. Машинное обучение на Python: первая модель
В этом уроке вы напишете программу, которая учится отличать спам от обычных писем по примерам. Сначала — на чистом Python, без библиотек, чтобы было видно, что происходит внутри. Потом — то же самое на scikit-learn, главной библиотеке машинного обучения для Python. Нужен Python 3 и знание основ: списки, функции, циклы (курс — в теме Python).
Признаки и метки
Модель не читает письма. Она видит только числа — признаки. Для простоты признаков два: сколько в письме ссылок и сколько восклицательных знаков. Верный ответ для каждого примера — метка: «спам» или «не спам». Такая задача — выбрать один класс из нескольких — называется классификацией, а обучение на примерах с метками — обучением с учителем (справка Что такое обучение с учителем).
Обучающая и проверочная выборки
Примеры делят на две части:
- обучающая выборка (train) — на ней модель учится;
- проверочная выборка (test) — на ней проверяют, как модель справляется с письмами, которых не видела.
Проверять модель на тех же примерах, на которых она училась, нельзя: так проверяется память, а не умение. Модель, которая просто запомнила все примеры, на них не ошибётся — а на новых провалится. Это называется переобучением, о нём — справка Что такое переобучение модели.
Метод ближайших соседей
Самый понятный способ классификации — метод k ближайших соседей (k-nearest neighbors, kNN). Чтобы решить, спам ли новое письмо, модель находит k самых похожих писем из обучающей выборки и смотрит, каких среди них больше. «Похожесть» — расстояние между точками: письмо — точка на плоскости, где по одной оси ссылки, по другой восклицательные знаки.
Обучения в привычном смысле здесь почти нет: модель просто хранит примеры. Вся работа — при ответе.
Программа на чистом Python
from collections import Counter
# (ссылок, восклицательных знаков) и метка
train = [
((0, 0), "не спам"), ((1, 0), "не спам"), ((0, 1), "не спам"),
((1, 2), "не спам"), ((2, 1), "не спам"),
((5, 4), "спам"), ((6, 7), "спам"), ((4, 6), "спам"),
((7, 3), "спам"), ((3, 5), "спам"),
]
test = [
((0, 2), "не спам"), ((2, 0), "не спам"), ((6, 6), "спам"),
((2, 3), "спам"), ((5, 6), "спам"),
]
def distance(a, b):
return (a[0] - b[0]) ** 2 + (a[1] - b[1]) ** 2
def predict(point, k=3):
nearest = sorted(train, key=lambda item: distance(point, item[0]))[:k]
votes = Counter(label for _, label in nearest)
return votes.most_common(1)[0][0]
correct = 0
for features, answer in test:
guess = predict(features)
if guess == answer:
correct += 1
print(features, "→", guess, "(верно)" if guess == answer else "(ошибка)")
print(f"Точность: {correct} из {len(test)} = {correct / len(test):.0%}")(0, 2) → не спам (верно)
(2, 0) → не спам (верно)
(6, 6) → спам (верно)
(2, 3) → не спам (ошибка)
(5, 6) → спам (верно)
Точность: 4 из 5 = 80%Что здесь происходит:
distanceсчитает квадрат расстояния между двумя точками. Корень не нужен: чтобы найти ближайших, достаточно сравнивать квадраты.predictсортирует обучающие примеры по расстоянию до нового письма, берёт три ближайших и считает голоса с помощьюCounter.most_common(1)возвращает самую частую метку.- Цикл проверяет модель на проверочной выборке и считает долю верных ответов — точность (accuracy).
Письмо (2, 3) на самом деле спам, но модель ошиблась: два его ближайших соседа — обычные письма (1, 2) и (2, 1). Это не ошибка программы, а ограничение данных: у письма немного ссылок и знаков, и по этим двум признакам оно похоже на обычное. Помогают больше примеров рядом с границей и больше признаков (например, есть ли в письме слово «выигрыш»).
То же на scikit-learn
Для настоящих задач алгоритмы не пишут с нуля: в scikit-learn готовы десятки моделей, деление на выборки и оценка качества. Библиотеку нужно установить командой в терминале — лучше в виртуальное окружение (как его создать — в теме Python):
pip install scikit-learnПакет называется scikit-learn, а в программе подключается как sklearn. Пакет с именем sklearn ставить не нужно — это устаревшая заглушка.
Пример ниже работает только после установки библиотеки. Данные — знаменитый набор «Ирисы»: 150 цветков ириса трёх видов и четыре признака каждого — длина и ширина чашелистика и лепестка. Набор входит в саму библиотеку, скачивать его не нужно.
from sklearn.datasets import load_iris
from sklearn.metrics import accuracy_score
from sklearn.model_selection import train_test_split
from sklearn.neighbors import KNeighborsClassifier
X, y = load_iris(return_X_y=True)
X_train, X_test, y_train, y_test = train_test_split(
X, y, test_size=0.25, random_state=0, stratify=y
)
model = KNeighborsClassifier(n_neighbors=3)
model.fit(X_train, y_train)
predicted = model.predict(X_test)
print("Цветков в проверке:", len(X_test))
print("Точность:", round(accuracy_score(y_test, predicted), 2))Программа печатает, сколько цветков попало в проверку (38 — четверть от 150 с округлением вверх), и точность модели. На этом наборе метод ближайших соседей обычно угадывает больше 90 % цветков.
Разберите по строкам:
X— признаки (таблица 150 × 4),y— метки (номер вида: 0, 1 или 2). ИменаXиy— традиция в машинном обучении.train_test_splitделит данные:test_size=0.25— четверть на проверку,random_state=0— деление одинаковое при каждом запуске,stratify=y— в обеих выборках виды представлены в тех же долях, что и во всём наборе.fit— обучение,predict— ответы на новых данных. У всех моделей scikit-learn эти два метода одинаковые. Поэтому, чтобы попробовать другую модель, достаточно заменить одну строку — например, наDecisionTreeClassifierизsklearn.tree(дерево решений).accuracy_score— доля верных ответов, от 0 до 1.
Частые ошибки
ModuleNotFoundError: No module named 'sklearn'— библиотека не установлена в тот Python, которым запускается программа. Установитеscikit-learnв то же окружение.- Проверка на обучающих данных — точность выходит завышенной. Всегда оставляйте отдельную проверочную выборку.
- Признаки в разных масштабах. Если один признак — зарплата в рублях, а другой — возраст в годах, для ближайших соседей расстояние почти целиком определит зарплата. Признаки приводят к одному масштабу, например с помощью
StandardScalerизsklearn.preprocessing.
Коротко
- Модель видит признаки — числа; верные ответы — метки.
- Данные делят на обучающую и проверочную выборки; проверяют только на проверочной.
- Метод ближайших соседей отвечает так, как отвечали k самых похожих примеров.
- Точность (accuracy) — доля верных ответов на проверочной выборке.
- В scikit-learn у всех моделей одинаковые
fitиpredict; ставится пакетscikit-learn.