Машинное обучение: картинки, звук, текст
Фрагмент лекции: «Все что нужно знать про ИИ айтишнику», 12:15 — отсюда взято содержание этой главы.
Есть утверждение, которое стоит принять до того, как в тексте появится слово «нейросеть»:
Огромная часть того, что в продуктах называют «искусственным интеллектом», — это обычные детерминированные алгоритмы над массивами чисел. Никакого обучения там нет и не требуется.
Поиск дублей в фотоальбоме, «что за песня играет», чтение сканов договоров, поиск почти-одинаковых картинок в модерации — всё это десятилетиями решалось и во многом до сих пор решается кодом, который можно прочитать глазами и посчитать на бумаге. Он воспроизводим: один и тот же вход даёт один и тот же выход, всегда. У него нет весов, датасета и обучающего цикла. Эта глава разбирает три таких алгоритма — по одному на изображение, звук и текст, — а потом отвечает на главный вопрос: где именно кода перестаёт хватать.
Три семейства методов и три домена данных
В лекции звучит фраза «машинное обучение делится на три шага: изображение, аудио, текст». Формулировку надо поправить: изображение, звук и текст — это домены данных (модальности), а не разделы машинного обучения. Машинное обучение делится по способу получения ответа: обучение с учителем, без учителя, с подкреплением — этому посвящён отдельный трек портала, машинное обучение.
Полезнее другое различение — по тому, откуда берётся решающее правило:
| Семейство | Откуда берётся правило | Пример |
|---|---|---|
| Детерминированные алгоритмы | Программист выписал его руками | Перцептивный хеш, OCR по эталонным глифам |
| Поисковая оптимизация (SBSE) | Правило известно, но нужно найти хороший вариант в огромном пространстве | Генерация тестов, автоматический ремонт кода |
| Машинное обучение | Правило не выписывается, параметры подбираются по данным | Классификатор изображений, распознавание речи |
Про среднюю строку отдельно, потому что в лекции аббревиатура употреблена неточно. SBSE — Search-Based Software Engineering, применение метаэвристической поисковой оптимизации (генетические алгоритмы, локальный поиск, роевые методы) к задачам разработки программного обеспечения. Термин ввели Марк Харман и Брайан Джонс в 2001 году в работе «Search-based software engineering» (Information and Software Technology, 43(14), DOI). Это не про цепи Маркова и не про «просто перебор»: перебор — вырожденный частный случай, а весь смысл направления в том, чтобы не перебирать всё. Пересказывать здесь нечего — на портале есть целый трек по SBSE.
Ещё одна поправка к устной формулировке. В лекции применение алгоритма к набору данных описано как «применить функтор по отношению к категории». Теоретико-категорный жаргон здесь неуместен и употреблён неверно. Речь идёт о простой вещи: мы применяем одну и ту же функцию к каждому элементу набора данных — к каждому пикселю, к каждому отсчёту звука, к каждому символу.
Обратите внимание на общий скелет всех трёх веток: свести сырые данные к короткому устойчивому представлению, а потом сравнивать представления, а не данные. Это одна и та же идея в трёх обличьях, и её же под другим именем используют нейросети — там представление называется эмбеддингом, и о нём речь в главе про представления.
Изображения: перцептивный хеш
Пользователь загружает фотографию котика, в базе лежит миллион фотографий, вопрос: есть ли среди них эта же самая или почти эта же самая? Побайтовое сравнение бесполезно — пережатие в JPEG с другим качеством, изменение размера на пиксель, водяной знак в углу меняют файл целиком. Криптографический хеш (SHA-256) бесполезен ещё сильнее: он спроектирован так, чтобы один изменённый бит на входе полностью менял выход.
Значит, нужна функция, у которой всё ровно наоборот: похожие входы дают похожие выходы. Такая функция называется перцептивным хешем.
Алгоритм aHash
Самый простой рабочий вариант — average hash. Ровно тот, который в лекции рисуют на доске.
400x400, RGB"] --> B["Уменьшение до 8x8"] B --> C["Перевод в градации серого"] C --> D["Среднее по 64 пикселям"] D --> E["Бит на пиксель:
ярче среднего или нет"] E --> F["64-битный хеш"] F --> G{"Расстояние Хэмминга
до хеша из базы"} G -->|"меньше порога"| H["Считаем одним изображением"] G -->|"больше порога"| I["Считаем разными"]
Каждый шаг здесь выбрасывает ровно тот вид информации, который мешает:
- Уменьшение до крошечной сетки убивает разрешение и мелкие детали. После него уже не важно, был оригинал 400×400 или 4000×4000, и не важен шум матрицы.
- Градации серого выбрасывают цвет. Для задачи «то же ли это изображение» цветовой сдвиг от другого профиля камеры — помеха.
- Порог по среднему значению самой картинки, а не по константе. Именно поэтому равномерное осветление или затемнение всей фотографии хеш не меняет: среднее уезжает вместе с пикселями.
- Остаётся битовая строка — грубая карта «где светлее, где темнее». Это и есть отпечаток.
from PIL import Image
def average_hash(path: str, size: int = 8) -> int:
"""Перцептивный хеш изображения (aHash).
Возвращает целое число, младшие size*size бит которого и есть хеш.
При size=8 это привычные 64 бита.
"""
# Шаг 1-2: уменьшаем сетку и убираем цвет.
# convert("L") — перевод в один канал яркости (градации серого).
img = Image.open(path).convert("L").resize((size, size), Image.LANCZOS)
pixels = list(img.getdata()) # size*size значений яркости 0..255
# Шаг 3: порог считается по самому изображению, а не задан константой.
avg = sum(pixels) / len(pixels)
# Шаг 4: каждый пиксель превращается ровно в один бит.
bits = 0
for p in pixels:
bits = (bits << 1) | (1 if p >= avg else 0)
return bits
def hamming_distance(a: int, b: int) -> int:
"""Расстояние Хэмминга: в скольких битовых позициях числа различаются.
XOR ставит единицу там, где биты не совпали; остаётся их посчитать.
int.bit_count() доступен с Python 3.10.
"""
return (a ^ b).bit_count()
Сложность. Уменьшение изображения — O(W*H) по времени и памяти на исходную картинку, это доминирующая часть. Всё остальное работает над n = size*size элементами: среднее O(n), бинаризация O(n), память O(n); при size = 8 это 64 операции, то есть бесплатно. Сравнение двух хешей — O(n / w) машинных слов, для 64 бит одна инструкция процессора. Поиск по базе наивно O(N) сравнений на запрос: для миллиона хешей это микросекунды, для миллиарда — уже нет, и тогда берут BK-деревья или multi-index hashing (хеш режется на куски, при малом пороге хотя бы один кусок обязан совпасть точно и ищется обычным индексом).
Родственники: dHash и pHash
aHash — самый грубый член семейства, и он чувствителен к изменению контраста. dHash отличается от кода выше буквально одной строкой: сетка берётся size+1 на size, а бит ставится по сравнению соседних пикселей (px[i] > px[i+1]), а не со средним. Кодируется градиент яркости, поэтому любое монотонное преобразование яркости хеш не меняет; зато появляется чувствительность к сдвигу кадра. pHash объясняет цифру «32×32» из лекции: картинка уменьшается до 32×32, к ней применяется дискретное косинусное преобразование, и берётся верхний левый блок 8×8 — низкие частоты, то есть общая структура без деталей. Он устойчивее всех к масштабу, гамме и лёгкой обрезке и заметно дороже остальных. Готовые реализации всех трёх лежат в библиотеке imagehash.
Чем перцептивный хеш отличается от криптографического
Это принципиальный пункт, и в лекции он формулируется на ощупь: автор колеблется между словами «сжатие» и «шифрование». Ни то, ни другое. Это хеш — необратимая свёртка данных в короткое значение фиксированной длины: сжатие обратимо (иначе оно не сжатие), шифрование обратимо при наличии ключа, хеш необратим в принципе. А внутри мира хешей есть развилка по одному-единственному свойству:
| Свойство | Криптографический хеш (SHA-256) | Перцептивный хеш (aHash, pHash) |
|---|---|---|
| Изменение одного бита входа | Полностью меняет выход (лавинный эффект) | Почти не меняет выход |
| Близкие входы | Дают далёкие выходы — это цель | Дают близкие выходы — это цель |
| Расстояние между хешами | Не несёт смысла | Мера непохожести объектов |
| Задача | Целостность, подписи, пароли | Поиск похожего, дедупликация |
| Атакующий может | Практически не может подобрать коллизию | Легко подбирает коллизию намеренно |
Последняя строка — то, за что платят. Перцептивный хеш сознательно ломает главное свойство криптографического: лавинный эффект. Поэтому его нельзя использовать там, где нужна защита от злонамеренного подбора. Картинку, у которой aHash совпадёт с заданным, злоумышленник построит за секунды.
Порог: откуда берётся число
В лекции звучит «если меньше 20 — похоже, больше — разные». Это устная прикидка, а не константа, которую можно унести к себе в код. Правильный ответ — порог не выдумывается, а подбирается, и вот механизм.
Порог — единственный параметр всей конструкции, и он задаёт компромисс. Опустить его — меньше ложных срабатываний (разные картинки, признанные одинаковыми), но больше пропусков (одинаковые, признанные разными). Поднять — наоборот. Обе ошибки не равноценны, и их цену задаёт задача, а не алгоритм. В дедупликации фотоальбома пропуск дешёв, а ложное срабатывание удаляет чужое фото — порог нужен низкий. В предварительной фильтрации кандидатов перед дорогой точной проверкой всё наоборот: пропуск фатален, ложное срабатывание стоит лишь одного лишнего сравнения.
Подбирают порог так: берут размеченную выборку пар («это одна и та же картинка» / «разные»), считают расстояния для всех пар, строят зависимость доли ложных срабатываний и доли пропусков от порога и выбирают точку по цене ошибок. Порог зависит и от длины хеша: 10 несовпавших бит из 64 и 10 из 256 — это разные степени непохожести, поэтому переносить число между алгоритмами нельзя.
Это ровно та дисциплина, которой посвящена статья про оценку моделей: precision, recall, ROC-кривая и выбор рабочей точки. И это же первый звоночек темы всей главы — один параметр здесь уже подбирается по данным, а не выписывается из первых принципов.
Расстояние Хэмминга: откуда оно на самом деле
Расстояние Хэмминга между двумя строками одинаковой длины — это количество позиций, в которых символы различаются. 10110 и 10011 различаются в двух позициях, расстояние равно 2.
В лекции сказано, что оно «из теории игр», и рядом упоминается равновесие Нэша. Это неверно, и повторять не нужно. Расстояние Хэмминга ввёл Ричард Хэмминг в 1950 году в теории кодирования, в работе «Error Detecting and Error Correcting Codes» (Bell System Technical Journal, 29(2), DOI). Задача была сугубо инженерная: перфокарты и линии связи вносили ошибки в биты, и требовалось не только заметить искажение, но и восстановить исходное сообщение. Отсюда вся конструкция: если любые два допустимых кодовых слова отстоят друг от друга минимум на d, то код гарантированно обнаруживает d - 1 ошибок и исправляет (d - 1) / 2 — принятое слово относят к ближайшему допустимому. Коды Хэмминга до сих пор работают в оперативной памяти с ECC. Для нашей задачи важно и то, что это метрика: неотрицательность, симметричность, неравенство треугольника. Благодаря этому по хешам можно строить метрические индексы (те самые BK-деревья) и говорить о «ближайшем соседе» осмысленно.
Границы применимости
Перцептивный хеш не переживает поворот и зеркальное отражение (лечится грубо: хешируют все четыре поворота и отражение) и разваливается на обрезке, потому что кроп сдвигает всю сетку — там нужны локальные признаки (SIFT, ORB) или нейросетевые эмбеддинги. Но главное ограничение другое: два разных рыжих кота — это два разных изображения. Хеш отвечает на вопрос «это тот же файл?», а не «это тот же объект?»; за второй отвечают свёрточные сети. Разница между этими двумя вопросами и есть граница главы: первый решается кодом, второй — нет.
Звук: спектр, спектрограмма, отпечаток
Звук в файле — это последовательность чисел: амплитуда давления, замеренная 44 100 раз в секунду. Сравнивать эти числа напрямую бессмысленно: сдвиг записи на одну тысячную секунды меняет их все, а на слух не меняет ничего. Поэтому переходят к спектру. Преобразование Фурье раскладывает сигнал на сумму синусоид: вместо «какая амплитуда в момент времени t» получается «какая мощность на частоте f». Ноту «ля» первой октавы задаёт основной тон 440 Гц, а тембр — набор обертонов, кратных частот 880, 1320, 1760 Гц и далее, с индивидуальным для каждого инструмента распределением мощности. Скрипка и труба на одной ноте различаются именно распределением мощности по обертонам.
Одна беда: спектр всей записи выбрасывает время. Поэтому сигнал режут на короткие окна (обычно 20–50 мс с перекрытием) и считают спектр в каждом окне. Результат — спектрограмма, двумерная карта «время × частота», где значение — мощность. Изображение. И дальше со звуком работают ровно теми же приёмами, что и с картинками.
Аудиоотпечаток: пики и пары пиков
Наивная идея «взять спектрограмму и сравнить целиком» не работает: в кафе на запись накладывается шум посуды, динамик заваливает низа, телефон режет верха. Нужны признаки, которые переживут всё это. Такими признаками оказались локальные пики спектрограммы — точки, которые громче всех соседей в окрестности. Шум добавляет энергии, но редко создаёт новые локальные максимумы там, где их не было, и почти не убирает существующие. Дальше — ключевой ход из работы Ави Ванга «An Industrial-Strength Audio Search Algorithm» (ISMIR, 2003), той самой, по которой построен Shazam: хешируется не отдельный пик, а пара пиков — две частоты и временной интервал между ними. Такой хеш гораздо специфичнее одиночного пика и при этом не зависит от абсолютного времени.
import numpy as np
from scipy.signal import stft
from scipy.ndimage import maximum_filter
def fingerprints(samples: np.ndarray, rate: int, fan_out: int = 5, dt_max: float = 5.0):
"""Отпечаток записи: хеши пар пиков спектрограммы.
Отдаёт пары «(частота якоря, частота цели, интервал) -> время якоря».
Абсолютное время в сам хеш не входит, поэтому отпечаток фрагмента
совпадает с отпечатком того же места в полной записи.
"""
freqs, times, z = stft(samples, fs=rate, nperseg=1024, noverlap=512)
mag = np.abs(z) # мощность по осям (частота, время)
# Пик — точка, максимальная в своей окрестности и при этом достаточно
# громкая: слабые локальные максимумы порождает шум, а не музыка.
is_peak = (maximum_filter(mag, size=20) == mag) & (mag > np.percentile(mag, 99))
f_idx, t_idx = np.nonzero(is_peak)
peaks = sorted(zip(times[t_idx], freqs[f_idx])) # [(время, частота), ...]
for i, (t1, f1) in enumerate(peaks):
# Каждый якорь связывается с несколькими ближайшими следующими пиками.
for t2, f2 in peaks[i + 1: i + 1 + fan_out]:
if 0 < t2 - t1 <= dt_max:
yield (int(f1), int(f2), round(t2 - t1, 2)), t1
Сложность. STFT — O(K * W log W) при K окнах длины W, память O(K * W) на спектрограмму. Поиск пиков линеен по её размеру, генерация отпечатков — O(P * fan_out) при P пиках. Поиск по базе — константа на хеш при хеш-индексе, дальше линейное голосование по найденным совпадениям.
Отдельный совпавший хеш ничего не значит: в базе из миллионов треков случайные совпадения неизбежны. Настоящий сигнал даёт согласованность смещений. Если фрагмент действительно взят из трека, то у всех правильных совпадений разность «время в треке минус время в записи» будет одна и та же. Строим гистограмму этих разностей: у верного трека вырастет резкий пик, у остальных — ровный шум.
Сигнал — согласованное смещение.
Открытая реализация той же идеи — Chromaprint / AcoustID. Обучения здесь по-прежнему нет: всё считается формулами, подбираются только несколько порогов.
Распознавание речи: где заканчивается эта аналогия
В лекции распознавание речи описано как «работа с паттернами»: не разбираемся, как физически произносится буква «А», а сравниваем спектральные шаблоны. Для доглубинных систем это описание верное, и именно так строились промышленные распознаватели с 1980-х по начало 2010-х:
- MFCC (мел-частотные кепстральные коэффициенты) — сжатие спектра каждого окна в 13–40 чисел с учётом того, что человеческий слух различает низкие частоты тоньше высоких. Это ручные признаки: формула написана человеком.
- HMM (скрытые марковские модели) — модель того, как скрытые состояния (фонемы) порождают наблюдаемые векторы признаков и переходят друг в друга. Прямая родня цепей Маркова из предыдущей главы, только состояния не наблюдаются напрямую. Каноническая работа — обзор Лоуренса Рабинера 1989 года «A Tutorial on Hidden Markov Models and Selected Applications in Speech Recognition» (DOI).
Связка MFCC + HMM + отдельная языковая модель была отраслевым стандартом два десятилетия. Но это ровно та точка, где нужно сказать прямо: современные системы распознавания речи устроены иначе. Ручные признаки и марковские состояния заменены нейросетевыми энкодерами, которые учат представление звука прямо из данных, а декодирование делается end-to-end. Whisper, на котором, кстати, расшифрована исходная лекция, — модель этого поколения (статья OpenAI, 2022). Как устроены рекуррентные энкодеры, с которых начался этот переход, — в статье про RNN и LSTM.
Текст: OCR
OCR (Optical Character Recognition, оптическое распознавание символов) — превращение изображения текста в текст. Классический конвейер целиком состоит из выписанных руками шагов:
- Бинаризация. Серое изображение → чёрное и белое. Глобальный порог работает только на идеальном скане; на фотографии с неравномерным светом нужен адаптивный (метод Оцу, локальные пороги).
- Выравнивание (deskew). Скан почти всегда положен под углом. Угол оценивают по преобразованию Хафа или по проекционным профилям и поворачивают изображение.
- Сегментация. Разделение на блоки, строки, слова, символы. Обычно — по горизонтальным и вертикальным профилям плотности чёрного: между строками плотность падает до нуля.
- Нормализация глифа. Вырезанный символ обрезается по границам, масштабируется к канонической сетке (например, 16×16), центрируется по центру масс. После этого «А» кеглем 10 и кеглем 40 выглядят одинаково.
- Сопоставление с эталонами. Нормализованный глиф сравнивается с эталонными изображениями букв. Побеждает ближайший.
- Языковая модель поверх. Символы, которые не различаются уверенно, доопределяются по контексту.
Шаги 4–5 — буквально та же схема, что и с картинками: свести к маленькой битовой сетке и сравнить расстоянием Хэмминга.
from math import log
def match_glyph(glyph: int, templates: dict[str, int]) -> list[tuple[str, int]]:
"""Кандидаты для глифа, отсортированные по расстоянию Хэмминга.
glyph и эталоны — битовые маски одной сетки (например, 16x16).
Это буквально метод ближайшего соседа, только метрика битовая.
"""
return sorted(((ch, (glyph ^ m).bit_count()) for ch, m in templates.items()),
key=lambda pair: pair[1])
def pick_with_context(candidates, prev_char: str, bigrams: dict, alpha: float = 8.0):
"""Итоговый выбор: цена = непохожесть картинки минус доверие к языку.
bigrams[(a, b)] — вероятность того, что за символом a идёт b, по корпусу;
это цепь Маркова первого порядка. alpha задаёт баланс между картинкой
и языком и подбирается по размеченной выборке, а не назначается.
"""
def cost(pair):
ch, distance = pair
# Сглаживание: нулевых вероятностей быть не должно, иначе log уйдёт в -inf.
return distance - alpha * log(bigrams.get((prev_char, ch), 1e-6))
return min(candidates, key=cost)[0]
Сложность. Сопоставление одного глифа — O(A) сравнений при алфавите из A эталонов, каждое сравнение O(1) для маски в машинное слово; для страницы из S символов это O(S * A), память O(A) на эталоны. Даже с несколькими начертаниями на букву — тысячи операций на страницу.
Языковая модель поверх: связь с цепями Маркова
Пятый шаг ошибается предсказуемо: 0 и O, 1 и l и I, rn и m, русские с, о, а и латинские c, o, a. По картинке они различаются на единицы пикселей, и никакое улучшение эталонов эту проблему не снимает — информации в изображении просто нет. Зато она есть в языке. Вероятность биграммы «ЩЪ» в русском тексте практически нулевая, а «СТ» — высокая. Ровно эта конструкция разбиралась в главе про цепи Маркова: вероятность следующего символа при условии предыдущего, посчитанная по корпусу. В коде выше она и стоит: сначала картинка даёт список кандидатов с ценой похожести, потом язык переоценивает их своей ценой, и выбирается лучший по сумме.
Это, кстати, честный ответ на вопрос «а машинное обучение тут есть?». Таблица биграмм получена из данных — из корпуса текстов. Но это не обучение в смысле градиентного спуска: это подсчёт частот, одна формула и один проход по корпусу. Граница между «посчитали статистику» и «обучили модель» проходит не по факту использования данных, а по тому, решается ли задача подбором параметров под функцию потерь.
Про историю: что было на самом деле
В лекции история OCR перевёрнута: там звучат «патент под Apple» и «сделано в Советском Союзе» в одном предложении. Восстановим проверяемую часть без выдуманных подробностей.
- Систему рукописного ввода Calligrapher для карманного компьютера Apple Newton MessagePad разработала компания ParaGraph International, основанная Степаном Пачиковым. Это тот самый распознаватель, который угадывал написанное стилусом на экране, — предок жестового ввода на современных планшетах.
- Российская компания ABBYY выпускает OCR-систему FineReader — один из самых известных промышленных распознавателей документов (abbyy.com). Про патенты, приоритеты и «кто был первым» здесь ничего не утверждается: без источника это домыслы.
Из открытых движков стоит знать Tesseract. Его история сама по себе иллюстрирует тему главы: до версии 4 он работал по описанному выше классическому конвейеру, а начиная с версии 4 распознавание строки выполняет LSTM-сеть. Тот же самый переход, что и в распознавании речи, и по той же причине.
Где кончается алгоритм и начинается обучение
Теперь можно сформулировать вывод главы — и он же мост в следующую.
Пока признак, отличающий один класс от другого, можно выписать руками — это код. Когда нельзя — приходится подбирать параметры по данным.
Возьмите три разобранных алгоритма. В каждом человек знал, на что смотреть: на карту светлого и тёмного, на локальные максимумы спектрограммы, на форму глифа в нормализованной сетке. Работа программиста состояла в том, чтобы описать это знание формулой. Обучение здесь не нужно, потому что нечему учиться: правило уже известно.
А теперь задачи, где так не выходит. Отличить кошку от собаки на произвольной фотографии — какой признак? Уши бывают любые, кадр любой, свет любой. Понять, доволен ли клиент по тексту отзыва — список «плохих слов» ломается на первом же сарказме. Распознать речь в шумном помещении с акцентом — спектральных шаблонов не хватает, вариативность больше, чем можно перечислить.
Общее у этих задач одно: признак существует, человек его безошибочно применяет, но не может выписать. Единственный оставшийся путь — задать параметризованное семейство функций и подобрать параметры так, чтобы на размеченных примерах ответы совпадали. Это и есть обучение.
Переход не бинарный, и это важнее, чем кажется. Порог расстояния Хэмминга — уже один параметр, подобранный по данным. Коэффициент alpha в OCR-переоценке — второй. Таблица биграмм — третий, и уже целая таблица. Дальше параметров становится десятки, тысячи, миллиарды, и в какой-то момент перестаёт иметь смысл спрашивать, что значит каждый из них: разница между alpha = 8.0 и весами трансформера количественная, а не качественная.
Практический вывод, который стоит унести в работу: не начинайте с обучения. Если признак выписывается — выпишите. Детерминированный алгоритм отлаживается, воспроизводится, объясняется пользователю и не требует ни разметки, ни видеокарты; перцептивный хеш дедуплицирует миллион фотографий на ноутбуке за минуты, и никакая нейросеть не даст здесь выигрыша, соразмерного её стоимости. Обучение — это то, к чему переходят, когда код измеримо не справился, а не то, с чего начинают. О том, что такое «признаки» и как их готовят, — в статье про данные и признаки; о сопоставлении с эталоном как о полноценном методе машинного обучения — в статье про kNN и наивный Байес, потому что match_glyph выше и есть метод ближайшего соседа.
Источники
- Hamming R. W. «Error Detecting and Error Correcting Codes» // Bell System Technical Journal, 29(2), 1950 — DOI
- Wang A. «An Industrial-Strength Audio Search Algorithm» // ISMIR, 2003 — PDF
- Rabiner L. R. «A Tutorial on Hidden Markov Models and Selected Applications in Speech Recognition» // Proceedings of the IEEE, 77(2), 1989 — DOI
- Harman M., Jones B. F. «Search-based software engineering» // Information and Software Technology, 43(14), 2001 — DOI
- Radford A. et al. «Robust Speech Recognition via Large-Scale Weak Supervision» (Whisper), 2022 — arXiv
- Krawetz N. Looks Like It — разбор перцептивных хешей; рабочие реализации: imagehash, Chromaprint / AcoustID, Tesseract
Мини-итог
- Изображение, звук и текст — это домены данных, а не разделы машинного обучения; схема у всех трёх классических алгоритмов одна: свести сырые данные к короткому устойчивому представлению и сравнивать представления, а не данные.
- Перцептивный хеш — не сжатие и не шифрование, а хеш, у которого намеренно сломан лавинный эффект: близкие входы дают близкие выходы. Криптографический хеш строится ровно наоборот.
- Расстояние Хэмминга — из теории кодирования, введено Ричардом Хэммингом в 1950 году для обнаружения и исправления ошибок передачи; к теории игр отношения не имеет.
- Порог расстояния не берётся из головы: он подбирается по размеченной выборке как компромисс между ложными срабатываниями и пропусками и зависит от длины хеша.
- Аудиоотпечаток строится на локальных пиках спектрограммы и хешах пар пиков, а решение принимается не по факту совпадения, а по согласованности временных смещений.
- MFCC и HMM были стандартом распознавания речи два десятилетия, но современные системы работают на нейросетевых энкодерах; то же произошло и с OCR внутри Tesseract.
- Граница проста: признак выписывается руками — пишите код; не выписывается — подбирайте параметры по данным. Переход количественный, и начинать всегда стоит с кода.
Что дальше
Мы дошли до места, где кода перестаёт хватать. Признак есть, человек его применяет мгновенно, а формулу для него написать невозможно. Следующая глава — про то, что происходит в этот момент: что вообще означает слово «обучить», откуда берутся веса, зачем нужны размеченные данные и почему за словами «обучение с учителем» стоит очень буквальный учитель.