Глиф Учебники

Урок 7. Машинное обучение на Python: первая модель

Курс «Искусственный интеллект» · урок 7 из 8 · 5 минут чтения

В этом уроке вы напишете программу, которая учится отличать спам от обычных писем по примерам. Сначала — на чистом Python, без библиотек, чтобы было видно, что происходит внутри. Потом — то же самое на scikit-learn, главной библиотеке машинного обучения для Python. Нужен Python 3 и знание основ: списки, функции, циклы (курс — в теме Python).

Признаки и метки

Модель не читает письма. Она видит только числа — признаки. Для простоты признаков два: сколько в письме ссылок и сколько восклицательных знаков. Верный ответ для каждого примера — метка: «спам» или «не спам». Такая задача — выбрать один класс из нескольких — называется классификацией, а обучение на примерах с метками — обучением с учителем (справка Что такое обучение с учителем).

Обучающая и проверочная выборки

Примеры делят на две части:

Проверять модель на тех же примерах, на которых она училась, нельзя: так проверяется память, а не умение. Модель, которая просто запомнила все примеры, на них не ошибётся — а на новых провалится. Это называется переобучением, о нём — справка Что такое переобучение модели.

Все примеры спам не спам Обучающая train Проверочная test Модель учится на обучающих примерах проверка на письмах, которых она не видела точность 4 из 5 = 80% Проверять на обучающих примерах нельзя: так проверяется память, а не умение.
Примеры делят на обучающую выборку, на которой модель учится, и проверочную — письма, которых модель не видела; по ним считают точность, в программе урока 4 из 5 = 80%.

Метод ближайших соседей

Самый понятный способ классификации — метод k ближайших соседей (k-nearest neighbors, kNN). Чтобы решить, спам ли новое письмо, модель находит k самых похожих писем из обучающей выборки и смотрит, каких среди них больше. «Похожесть» — расстояние между точками: письмо — точка на плоскости, где по одной оси ссылки, по другой восклицательные знаки.

Обучения в привычном смысле здесь почти нет: модель просто хранит примеры. Вся работа — при ответе.

Программа на чистом Python

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%

Что здесь происходит:

Письмо (2, 3) на самом деле спам, но модель ошиблась: два его ближайших соседа — обычные письма (1, 2) и (2, 1). Это не ошибка программы, а ограничение данных: у письма немного ссылок и знаков, и по этим двум признакам оно похоже на обычное. Помогают больше примеров рядом с границей и больше признаков (например, есть ли в письме слово «выигрыш»).

0 0 1 1 2 2 3 3 4 4 5 5 6 6 7 7 ссылок восклицательных знаков не спам спам новое письмо Новое письмо (2, 3) k = 3 ближайших соседа расстояние² (1, 2) не спам 2 (2, 1) не спам 4 (3, 5) спам 5 голоса: не спам 2, спам 1 ответ модели: не спам на самом деле спам — ошибка
Письма как точки на плоскости: у нового письма (2, 3) три ближайших соседа — (1, 2), (2, 1) и (3, 5); два из них не спам, поэтому модель ошибается.

То же на scikit-learn

Для настоящих задач алгоритмы не пишут с нуля: в scikit-learn готовы десятки моделей, деление на выборки и оценка качества. Библиотеку нужно установить командой в терминале — лучше в виртуальное окружение (как его создать — в теме Python):

Bash
pip install scikit-learn

Пакет называется scikit-learn, а в программе подключается как sklearn. Пакет с именем sklearn ставить не нужно — это устаревшая заглушка.

Пример ниже работает только после установки библиотеки. Данные — знаменитый набор «Ирисы»: 150 цветков ириса трёх видов и четыре признака каждого — длина и ширина чашелистика и лепестка. Набор входит в саму библиотеку, скачивать его не нужно.

Python
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, y = load_iris(return_X_y=True) X — признаки, таблица 150 × 4 y — метки: номер вида 0, 1 или 2 train_test_split, test_size=0.25 X_train, y_train 112 цветков — на них модель учится X_test, y_test 38 цветков — модель их не видела model.fit(X_train, y_train) обучение predicted = model.predict(X_test) ответы на новых данных accuracy_score(y_test, predicted) доля верных ответов, от 0 до 1 fit и predict одинаковые у всех моделей scikit-learn
Путь данных в scikit-learn: train_test_split делит 150 цветков на 112 для обучения и 38 для проверки, fit обучает модель, predict отвечает на проверочных, accuracy_score считает долю верных.

Частые ошибки

Коротко

Проверьте себя

1. Зачем данные делят на обучающую и проверочную выборки?
2. Как метод ближайших соседей решает, спам ли новое письмо?
3. Какой командой ставится библиотека scikit-learn?
4. Модель угадала 45 писем из 50 проверочных. Какова её точность?