Один розкол бреше

Крос-валідація — це не «ще одна метрика». Це спосіб дізнатися, наскільки можна довіряти самій оцінці якості.

Ти навчив модель, відклав 20% даних, поміряв на них якість і отримав акуратне число: 0.87. Питання, яке варто поставити собі негайно: а якби ти відклав інші 20%? Скільки б вийшло тоді — 0.87? 0.91? 0.79?

Це не риторика. Кожна оцінка якості, яку ти коли-небудь бачив, — це випадкова величина. Вона залежить не тільки від моделі, а й від того, які саме обʼєкти випадково опинилися в тестовій частині. Одне число без уявлення про його розкид — це як сказати «температура 20 градусів», не уточнивши, чи це середнє за рік, чи одне вимірювання термометром, який бреше на десять градусів.

Крос-валідація розвʼязує рівно цю проблему. Ідея настільки проста, що її часто недооцінюють: не обирати одне розбиття, а прогнати всі по черзі й усереднити. Далі ми розберемо, чому це працює, скільки саме шуму воно знімає, коли звичайна крос-валідація дає катастрофічно оптимістичну відповідь і що з цим робити.

ПроблемаЧому одна цифра нічого не варта

Почнемо з того, що ми взагалі хочемо виміряти. Наша справжня мета — це очікувана помилка на нових даних: якщо модель завтра побачить обʼєкт, якого не було в навчанні, наскільки вона помилиться в середньому?

Etest = 𝔼(x,y)~P [ L( y, f̂(x) ) ]
Розберемо значки, перш ніж іти далі. x — ознаки одного обʼєкта (рік випуску, пробіг, площа), y — його справжня відповідь. f̂(x) — те, що прогнозує наша навчена модель; дашок над буквою означає «оцінене з даних», а не «справжнє». L(y, f̂(x)) — функція втрат: одне число, яке каже, наскільки боляче помилитись саме так (для регресії це зазвичай квадрат різниці, для класифікації — нуль за влучання й одиниця за промах). P — розподіл, з якого життя бере нові обʼєкти, а запис (x, y) ~ P читається «пара взята з цього розподілу навмання». І 𝔼 — велика латинська «E», математичне сподівання: середнє по всіх можливих випадках, а не по тих кількох, що трапились. Разом формула читається одним реченням: наскільки модель помилятиметься в середньому на обʼєкті, взятому з життя навмання. Цю саму букву 𝔼 ми зустрінемо ще раз — у розділі 11, де вона пояснить, чому «найкраща CV-оцінка» систематично бреше.

Ця величина недосяжна: розподіл P ми не знаємо, у нас є лише скінченна вибірка з нього. Тому ми її оцінюємо — відкладаємо частину даних, робимо вигляд, що вони «нові», і міряємо помилку на них. Оцінка виходить незміщеною за побудовою, але в неї є дисперсія, і ця дисперсія тим більша, чим менша відкладена частина.

Уяви крайній випадок: відклали одну точку. Оцінка — це помилка на одному обʼєкті. Вона може бути нуль (пощастило), може бути жахливою (трапився викид). Відклали двадцять точок — уже краще, але якщо серед них випадково зібралися три складні обʼєкти, оцінка буде песимістичною. Це не проблема моделі. Це проблема вимірювального приладу.

Подивимось, наскільки цей прилад хитається насправді. Візьмемо 200 рядків із набору breast_cancer, який приходить разом зі scikit-learn, навчимо логістичну регресію на 160 з них і поміряємо частку правильних відповідей на 40 відкладених. А потім повторимо це двадцять разів, міняючи лише зерно перемішування — тобто те, які саме рядки випадково стали відкладеними. Дані, модель і код не змінюються ані на символ.

Двадцять розбиттів однієї вибірки — двадцять різних чисел

Кожна крапка — окремий запуск. По горизонталі — частка правильних відповідей на відкладених 40 рядках.

Оцінка — не властивість моделі, а один кидок. Найгірше розбиття дало 0.900, найкраще — 1.000: розмах у десять відсоткових пунктів там, де модель не змінилась ані на коефіцієнт. Усі двадцять чисел кратні 0.025, бо у відкладеній частині рівно 40 обʼєктів, і кожен важить 1/40. Якби ти зупинився на першому розбитті, ти б записав у звіт 0.950 — і не дізнався б, що поруч лежали і 0.900, і 1.000.

Друга біда простого hold-out розбиття — ми викидаємо дані з навчання. Відклавши 30%, ми міряємо якість моделі, навченої на 70% даних. Але у виробництво піде модель, навчена на всіх 100%. А більше даних — це майже завжди менша помилка. Отже, оцінка hold-out систематично песимістична: вона описує гіршу модель, ніж та, яку ми насправді збираємось використати.

Два дефекти, які не зводяться один до одного. Велика відкладена частина → менша дисперсія оцінки, але сильніше зміщення (модель навчена на малих даних). Мала відкладена частина → менше зміщення, але оцінка шумить. Одне розбиття не дозволяє виграти в обох напрямках одночасно. K-Fold дозволяє.

МеханікаK-Fold крок за кроком

Алгоритм K-Fold крос-валідації складається з чотирьох дій. Розберімо кожну.

крок 1 · перемішати й порізати

Перемішуємо дані у випадковому порядку й ділимо на K приблизно рівних частин — фолдів. Якщо n не ділиться на K, перші кілька фолдів беруть на одну точку більше. Перемішування обовʼязкове: якщо дані відсортовані за міткою або за датою, послідовне розрізання дасть фолди, які взагалі не схожі на всю вибірку.

крок 2 · прогнати K разів

Для кожного i від 1 до K: беремо фолд номер i як валідаційний, решту K−1 фолдів склеюємо в навчальну частину. Навчаємо модель з нуля — не донавчаємо, не переносимо ваги — і міряємо метрику на відкладеному фолді.

si = L( Di, f̂−i ) ,   де f̂−i навчена на D \ Di

Значки читаються так: D — уся вибірка, Di — її i-й фолд, а коса риска \ означає «усе, крім»: D \ Di — це вибірка без i-го фолда. Нижній індекс −i у f̂−i нагадує, що ця модель фолда номер i не бачила. L — та сама функція втрат, що й у розділі 01, а si — оцінка, здобута на i-му фолді.

крок 3 · усереднити

Підсумкова оцінка — середнє по фолдах:

CV(K) = ⅟K Σi=1..K si

Велика грецька Σ («сигма») означає «додай усе, що йде за нею»: запис Σi=1..K si — це просто s₁ + s₂ + … + sK, а дріб 1/K перед сумою перетворює її на середнє.

Порахуймо руками на пʼяти числах. Фолди дали 0.81, 0.86, 0.79, 0.88 і 0.84. Сума — 4.18, поділили на 5, вийшло CV(5) = 0.836. Разом із середнім завжди рахують і розкид — стандартне відхилення по цих самих пʼяти числах, тобто типове відхилення фолда від середнього; тут воно 0.033. Розкид каже, наскільки метрика залежить від того, які саме обʼєкти потрапили у валідацію: 0.836 ± 0.033 і 0.836 ± 0.002 — це дуже різні звіти про однакове середнє.

крок 4 · перенавчити на всьому

Жодна з K моделей не є фінальною. Вони існували лише для того, щоб дати оцінку. Фінальну модель навчають ще раз — на всіх даних, — а число CV(K) вважають прогнозом її якості.

Зверни увагу на важливу деталь у кроці 2: кожна точка рівно один раз побуває у валідації й рівно K−1 разів у навчанні. Тобто на оцінку впливає кожен обʼєкт вибірки, а не випадкові 20%.

В інтерактиві нижче — синтетична вибірка з 30 точок і поліноміальна модель. Рухай K і дивись на дві речі одразу: як міняється схема розбиття зверху й наскільки стрибають окремі фолди на стовпчиках знизу. Бірюзова риска — усереднена оцінка цього розбиття, сіра пунктирна — оцінка LOOCV, тобто K-Fold, доведеної до краю: кожна модель там навчається на 29 з 30 точок, майже як фінальна. Саме тому LOOCV зручно взяти за орієнтир «як було б без втрати навчальних даних». Ми повернемось до неї в розділі 08.

Окремо виведено «очікувану оцінку» — середнє CV(K) по 350 різних перемішуваннях. Це те, що дав би метод у середньому; конкретне розбиття завжди відхиляється від нього в той чи інший бік.

K-Fold зсередини

Угорі — хто навчає, хто перевіряє. Унизу — оцінка кожного фолда та їхнє середнє.

оцінка CV—
очікувана—
LOOCV—
навчання—
Спробуй крайні значення: при K = 2 кожна модель бачить лише 15 з 30 точок — очікувана оцінка помітно вища за LOOCV, тобто песимістична. При K = 10 модель навчається на 27 точках, і очікувана оцінка сходиться з сірим пунктиром. Але окремі фолди при цьому стрибають сильніше: у кожному лише три точки. І звертай увагу, як гуляє конкретне розбиття від натискання «перемішати заново» — це і є та сама випадковість, заради якої існує вся ця лекція.

ДисперсіяСкільки шуму знімає усереднення

Тепер найважливіше питання: наскільки взагалі краще стає від усереднення? Тут легко відбутися словами «менше дисперсія», але це якраз той випадок, коли варто побачити число.

Проведемо чесний експеримент. Візьмемо ту саму вибірку й 350 разів перемішаємо її по-новому. Для кожного перемішування порахуємо дві оцінки:

Гістограми нижче показують, як розкидані ці 350 оцінок. Ліворуч ширша, розпливчаста — hold-out. Праворуч вужча, зібрана — K-Fold. Числа під ними — стандартні відхилення й у скільки саме разів усереднення звузило розподіл.

Одне розбиття проти K-Fold

350 незалежних перемішувань тієї самої вибірки. Однакові моделі, однаковий розмір навчання.

σ hold-out—
σ K-Fold—
шум знижено в—
Чому не рівно √K: якби K оцінок були незалежними, усереднення звузило б розподіл рівно в √K разів. У житті діють два ефекти в різні боки. Фолди перекриваються навчальними частинами — сусідні моделі бачать майже ті самі точки, їхні помилки скорельовані, і це зменшує виграш. Зате K-Fold перевіряє модель на всіх 30 точках, а одне hold-out розбиття — лише на 30/K з них: при K = 10 це три точки, і така оцінка шумить катастрофічно. Другий ефект зазвичай сильніший, тому реальний виграш може перевищити √K.
Той самий замір на справжніх даних. Гістограми вище рахує сам браузер на 30 синтетичних точках — так видно механізм у чистому вигляді. Ось те саме число на реальному наборі. Беремо ті самі 200 рядків breast_cancer, що й у розділі 01, і 200 разів перемішуємо їх заново. Для кожного перемішування рахуємо дві речі: оцінку одного розбиття (перший фолд із пʼяти, перевірка на 40 обʼєктах) і оцінку повного 5-Fold (середнє по всіх пʼяти фолдах, перевірка на всіх 200). Навчальні частини в обох випадках містять 160 рядків, тож порівняння чесне. Розкид першої величини — σ = 0.0266, другої — σ = 0.0068. Тобто усереднення звузило розподіл у 3.92 раза — помітно більше за √5 ≈ 2.24. Причина та сама, що в підписі до фігури: перекриття навчальних частин виграш зменшує, а перевірка на 200 обʼєктах замість 40 його збільшує, і другий ефект тут переважив.
Практичний висновок. Різниця між двома конкурентними моделями має сенс лише тоді, коли вона більша за розкид оцінки. Якщо CV дає 0.812 ± 0.030 проти 0.818 ± 0.028 — це однакові моделі, і вибір між ними за середнім є самообманом. Саме тому cross_val_score варто дивитися разом зі .std(), а не лише .mean().

СтратифікаціяКоли класи розподілені нерівно

Випадкове перемішування здається безпечним, доки класи збалансовані. Але уяви задачу виявлення шахрайства: 3% транзакцій — шахрайські, 97% — нормальні. Вибірка з 200 транзакцій містить 6 шахрайських. Розбиваємо на 5 фолдів випадково — і з відчутною ймовірністю в якийсь фолд не потрапить жодної.

Що тоді станеться? Метрики на кшталт precision, recall чи ROC-AUC на такому фолді просто не визначені — ділити нема на що. Бібліотека або поверне nan, або тихо підставить нуль, і твоє середнє буде сміттям. А в іншому фолді, куди рідкісний клас потрапив у подвійній кількості, метрика буде неправдоподібно високою.

Стратифікований K-Fold лікує це тривіально: замість того щоб різати всю вибірку разом, він ріже кожен клас окремо, а потім склеює шматки. У результаті частка кожного класу в кожному фолді збігається з часткою в усій вибірці з точністю до одного обʼєкта.

Дисбаланс і стратифікація

80 обʼєктів, 5 фолдів. Угорі — склад кожного валідаційного фолда. Унизу — збалансована точність.

метрика—
розкид ±—
зіпсовано—
перекіс складу—
Правило: для будь-якої класифікації бери StratifiedKFold за замовчуванням — при збалансованих класах він працює так само, як звичайний, а при дисбалансі рятує. Для регресії аналог — стратифікація за квантилями цільової змінної.

ГрупиКоли рядки не незалежні

Уся математика вище тримається на одному припущенні: рядки вибірки незалежні. На практиці воно ламається постійно, і ламається тихо.

Приклади, які трапляються майже в кожному проєкті:

Симптом завжди однаковий: крос-валідація показує чудові числа, а в продакшені модель провалюється. Причина в тому, що метрика відповідала на питання «наскільки добре модель упізнає вже бачені сутності», тоді як реальна задача — «наскільки добре вона працює на нових сутностях».

Лікування — групова крос-валідація: розбивати не рядки, а групи. Усі записи однієї групи цілком потрапляють або в навчання, або у валідацію, ніколи не розділяючись. У scikit-learn це GroupKFold і StratifiedGroupKFold.

Питання, яке треба ставити щоразу. «Що саме модель побачить уперше в момент реального використання?» Якщо нового користувача — розбивай за користувачами. Якщо новий день — за датами. Якщо нову лікарню — за лікарнями. Розбиття має імітувати ту саму новизну, з якою модель зустрінеться в житті. Усе інше — це вимірювання не тієї величини.

ЧасВитік із майбутнього

Окремий і найпідступніший випадок залежності — час. Тут звичайна крос-валідація не просто трохи оптимістична, вона дає принципово неправильну відповідь.

Причина проста. Перемішавши часовий ряд, ми поклали точку за 15 березня в навчальну частину, а точку за 14 березня — у валідаційну. Модель тренувалася, знаючи майбутнє відносно того моменту, який вона потім «прогнозує». У житті такої розкоші не буде: прогнозуючи завтрашній попит, ти маєш лише вчорашні дані.

Правильна схема — розширювальне вікно (forward chaining): навчаємось на перших m точках, перевіряємось на наступних; потім навчаємось на m + h, перевіряємось на наступних; і так далі. Навчальна частина завжди строго передує валідаційній. У scikit-learn це TimeSeriesSplit.

В інтерактиві — ряд зі 160 точок. Перші 140 доступні для валідації, останні 20 — «справжнє майбутнє», яке ми ніколи не чіпаємо й використовуємо лише як арбітра. Модель тут навмисно проста: прогноз = середнє трьох найближчих у часі навчальних точок. Повзунок керує автокореляцією ряду: чим вона вища, тим сильніше сусідні точки схожі одна на одну — і тим більшим стає витік.

Усі три числа усереднено по 20 незалежних реалізаціях ряду. Без цього вони б стрибали від однієї реалізації до іншої настільки, що порівнювати їх було б нечесно — що, до речі, ще одна ілюстрація головної теми лекції.

Часовий ряд: три способи поміряти

Перемикай схему розбиття. Три числа рахуються завжди — порівнюй їх між собою.

звичайна CV—
часова CV—
справжнє майбутнє—
Що читати: порівнюй кожну оцінку зі «справжнім майбутнім» — це єдине чесне число. Зверни увагу, що при автокореляції 0.00 ряд перетворюється на білий шум, сусіди перестають бути схожими, і обидві схеми дають майже те саме: витік зникає разом із залежністю в даних.

ВитікПередобробка всередині фолда

Є ще один спосіб тихо зіпсувати крос-валідацію, і він не про те, як ти ріжеш дані, а про те, що робиш із ними до розрізання.

Тема 09 · Передобробка даних уже поставила тут правило: усе, що вивчається з даних — медіана для заповнення пропусків, середнє й відхилення для масштабування, список категорій, межі обрізання викидів, — вивчається лише на навчальній частині, а перевірочна це вивчене тільки отримує. Порушиш — і перевірочні рядки встигнуть вплинути на числа, за якими їх потім перевірятимуть.

У крос-валідації в цього правила зʼявляється поворот, на якому спотикаються навіть ті, хто правило знає напамʼять. Межа між навчанням і перевіркою тут не одна. Вона рухається: у 5-Fold вона стає в пʼять різних місць, і кожне з них вимагає своєї медіани, свого середнього, свого списку категорій. Один-єдиний виклик StandardScaler().fit_transform(X), зроблений перед крос-валідацією, формально не порушує нічого — на момент виклику поділу ще й не існувало. Фактично він порушує правило пʼять разів одразу.

так робити не можна
scaler = StandardScaler()
X_scaled = scaler.fit_transform(X)
cross_val_score(model, X_scaled, y, cv=5)

Тут model — будь-яка модель, скажімо LogisticRegression(). Масштабування побачило всі 200 рядків, зокрема й ті, на яких модель потім перевірятимуть у кожному з пʼяти фолдів.

так треба
pipe = make_pipeline(StandardScaler(), model)
cross_val_score(pipe, X, y, cv=5)

Тепер у cross_val_score передається не таблиця чисел, а увесь конвеєр як модель. Кожен фолд навчає його з нуля: свій fit для масштабування, свій fit для класифікатора. Правило «нічого не вивчається на перевірочній частині» виконується пʼять разів, а не жодного.

Просте правило, яке рятує: у cross_val_score, GridSearchCV й усе решта передається конвеєр, а не заздалегідь оброблена таблиця. Усе, що ти зробив із X до цього виклику, крос-валідація перевірити вже не може — вона про це просто не знає.

Скільки це коштує

А тепер несподіванка, заради якої тут стоїть фігура. Ціна цієї помилки різниться не в рази — вона різниться на два порядки, і залежить від однієї властивості кроку, яку назвала тема 09: чи дивиться цей крок на мітки.

Замір перший — масштабування. Ті самі 200 рядків breast_cancer, 5-Fold, 200 різних перемішувань. Якщо StandardScaler навчити один раз на всій таблиці, середня оцінка виходить 0.9659; якщо покласти його в конвеєр — 0.9651. Завищення — 0.0009, тобто менш ніж одна десята відсоткового пункта. Ще промовистіше розкладаються самі перемішування: у 136 випадках зі 200 обидва порядки дали однакове до останньої цифри число, у 47 неправильний порядок був вищим, у 17 — навіть нижчим. Різниці, по суті, немає: середнє й відхилення дивляться лише на самі ознаки й не знають, де правильна відповідь.

Замір другий — відбір ознак. Тепер дані, у яких закономірності немає взагалі: 200 рядків, 2000 випадкових ознак, мітки кинуті монеткою. Чесна відповідь тут одна — 0.500, вгадування навмання. Беремо ті самі 5 фолдів і той самий класифікатор, але спершу вибираємо 5 «найкращих» ознак із 2000. Відбір, зроблений до крос-валідації, дає 0.681. Той самий відбір усередині конвеєра дає 0.512 — майже точно чесну відповідь. Завищення 0.169, і воно зʼявилось у 20 випадках із 20.

Це число варто звірити з сусідньою темою. Тема 03 ставила той самий дослід на звичайному поділі train/test і дістала 0.68 проти 0.50 — завищення на 18 відсоткових пунктів. У нас крос-валідація замість одного поділу, 2000 ознак замість 300 — а відповідь та сама з точністю до першого знака. Збігу тут немає: величина завищення визначається не схемою розбиття, а тим, скільки міток устиг побачити відбір. Крос-валідація не лікує витік і не помʼякшує його — вона просто дає йому пʼять нагод замість однієї.

Той самий гріх коштує по-різному

Наскільки завищується CV-оцінка, якщо крок передобробки навчити один раз на всій таблиці замість K разів усередині фолдів.

Вирішує одне питання: чи дивиться крок на мітки. Масштабування переставляє шкалу ознак і про правильні відповіді нічого не знає — украсти йому майже нема чого. Відбір ознак обирає стовпці за силою звʼязку з міткою, тому підглянуті мітки перетворюються на 0.181 понад чесну відповідь. Але висновок із цього не «масштабування можна й поза конвеєром»: у справжньому ланцюжку кроки стоять один за одним, і той, хто дозволив собі мовчазний виняток, наступного разу не помітить сусіда, який дивиться на мітки.
Найпідступніше тут — напрямок. Витік ніколи не псує число випадково: він завжди робить його кращим. Помилка, яка псує метрику, знаходиться за півдня — ти бачиш погане число й ідеш шукати причину. Помилка, яка метрику покращує, не знаходиться ніколи: ти бачиш гарне число й ідеш пити каву.

LOOCVКрайній випадок: K = n

Що станеться, якщо довести K до максимуму — зробити його рівним кількості обʼєктів? Кожен фолд міститиме рівно одну точку. Це Leave-One-Out Cross-Validation, LOOCV.

Плюси очевидні. Навчальна частина містить n − 1 обʼєктів — практично всю вибірку. Отже, оцінка майже не зміщена: ми міряємо якість тієї самої моделі, яку потім навчимо на всьому. Крім того, у LOOCV немає випадковості розбиття: результат детермінований, перезапуск дасть той самий відповідь до останньої цифри.

Мінуси теж очевидні. Треба навчити модель n разів. Для 100 обʼєктів це прийнятно, для 100 000 — ні. Але є важливий виняток: для лінійних моделей LOOCV рахується за один прохід завдяки формулі через діагональ матриці-проєктора — її називають капелюшною (hat matrix), бо вона «надіває дашок» на y, перетворюючи справжні відповіді на прогнози ŷ. Кому потрібен точний запис: H = X(XᵀX)⁻¹Xᵀ, де X — таблиця ознак, Xᵀ — вона ж, перевернута навколо діагоналі (транспонована), а ⁻¹ — обернена матриця. Для розуміння далі цей запис не потрібен — потрібні лише числа з діагоналі H:

CVLOO = ⅟n Σ ( (yi − ŷi) / (1 − hii) )2

Розберемо, що тут написано. yi — справжня відповідь i-го обʼєкта, ŷi — прогноз моделі, навченої на всіх обʼєктах; їхня різниця — звичайний залишок, який ти й так маєш після одного навчання. hii — число від 0 до 1 із діагоналі капелюшної матриці, і воно каже, наскільки сильно цей обʼєкт тягнув криву на себе: нуль означає «модель не помітила б його зникнення», близьке до одиниці — «крива проходить через нього тому, що він так захотів». Ділення на 1 − hii і є весь фокус: воно роздуває залишок рівно настільки, наскільки обʼєкт сам собі допоміг, — і дає той залишок, який вийшов би, якби обʼєкта в навчанні не було. Σ і 1/n далі роблять із цих чисел середнє, як у розділі 02.

Приклад на двох числах. Нехай залишок на обʼєкті — 120 грн, а hii = 0.4. Тоді внесок цього обʼєкта в LOOCV дорівнює (120 / 0.6)² = 200² = 40 000: так, ніби модель без нього промахнулась би на 200 грн, а не на 120. А для обʼєкта з hii = 0.05 поправка майже нічого не міняє: 120 / 0.95 ≈ 126. Ось чому формула не потребує жодного повторного навчання — уся інформація про «що було б без цього обʼєкта» вже лежить у діагоналі.

Тобто для звичайної й гребеневої регресії LOOCV коштує стільки ж, скільки одне навчання. Саме тому RidgeCV у scikit-learn за замовчуванням підбирає α саме через LOOCV — це безкоштовно.

Є й тонший мінус, про який сперечаються. Часто пишуть, що LOOCV має високу дисперсію оцінки: моделі навчені на майже однакових даних, тому їхні помилки сильно скорельовані, і усереднення дає мало. Це правдоподібна інтуїція, але вона не універсальна — для лінійної регресії, наприклад, показано протилежне. Практичний висновок скромніший: виграш від дуже великого K зазвичай не окуповує вартості.

Вибір KЧому на практиці 5 або 10

Тепер зберемо все докупи. Вибір K — це компроміс двох властивостей самої оцінки (не моделі!):

Kрозмір навчаннязміщення оцінкивартість
K = 250% данихвелике: міряємо гіршу модель2 навчання
K = 580% данихпомірне5 навчань
K = 1090% данихмале10 навчань
K = nмайже 100%практично нульn навчань

Логіка така. Зі зростанням K навчальна частина росте, тому оцінка дедалі точніше описує ту модель, яку ми справді збираємось використати — зміщення падає. Але кожен фолд стає меншим, окремі оцінки sᵢ шумлять сильніше, а самі моделі стають дедалі більш схожими між собою, тому усереднення знімає дедалі менше шуму. Плюс вартість росте лінійно.

Часто цей компроміс переказують фразою «криві зміщення й вартості перетинаються десь у зоні 5…10». Фраза красива, але порожня: зміщення міряється в одиницях помилки, вартість — у навчаннях, і перетинатись їм ніде. Твердження, яке справді можна перевірити, інше: обидва дефекти оцінки згасають швидко, а рахунок росте рівно. Фігура нижче рахує це на тій самій вибірці з 30 точок, з якою ми працювали весь час.

Що ти купуєш і що платиш за кожне збільшення K

Обидві криві показані в частках від свого значення при K = 2 — так їх можна покласти на одну шкалу. Сірі стовпчики — кількість навчань.

Обидві криві падають, вартість — ні. Від K = 2 до K = 10 зміщення оцінки падає з 0.022 до 0.002 (лишилось 8%), розкид — з 0.032 до 0.007 (лишилось 21%), а рахунок росте рівно впʼятеро. Після десятки обидві криві вже лежать на дні: усе, що ти доплачуєш далі, купує лічені останні відсотки точності оцінки. Ось і вся причина, чому галузь зупинилась на 5 і 10, а не пішла до K = n. Дрібні нерівності кривих — залишок випадковості: кожна точка усереднена по 400 перемішуваннях, а не по нескінченності. Зверни увагу й на ліву межу: при K = 2 дефекти найбільші саме тому, що кожна модель бачить лише половину вибірки.

Практичні орієнтири:

ПошукНавіщо це при підборі

Найчастіше крос-валідацію застосовують не для звіту, а для вибору: який степінь полінома, яка глибина дерева, яке λ регуляризації — тобто наскільки сильно ми штрафуємо модель за великі коефіцієнти (у scikit-learn цей самий важіль зветься alpha, а сама регуляризація — предмет теми 19; тут від неї потрібне лише одне: більше λ — простіша модель). Схема така: перебираємо кандидатів, для кожного рахуємо CV-оцінку, беремо переможця. Саме тому все попереднє в цій темі важливе — оцінка, за якою обирають, мусить бути чесною, інакше вибір буде зроблено за шумом.

Перебирати можна трьома способами. Сітка проходить усі комбінації зі списків, які ти задав. Випадковий пошук кидає задану кількість випадкових точок. Байєсівська оптимізація вибирає наступну точку з огляду на те, що вже перевірено. Чому випадковий пошук зазвичай виграє в сітки, скільки все це коштує і як не обдурити себе результатом — предмет окремої теми 20 · Підбір гіперпараметрів.

Тут важливе інше: як виглядає простір, у якому доводиться шукати. Подивись на теплокарту нижче — саме її форма пояснює, чому одні стратегії знаходять дно швидше за інші.

Три стратегії пошуку на одній теплокарті

Фон — справжня CV-помилка для кожної пари (степінь, λ). Точки — те, що стратегія встигла перевірити.

знайдено—
можливий мін.—
програш до оптимуму—
Що видно: світла зона має форму літери «Г». Низькі степені (нижня смуга) працюють при будь-якому малому λ — їм просто нема чого роздувати. Високі степені рятує тільки достатнє λ (вертикальна смуга), а без нього вони провалюються в темряву. Праворуч, при λ > 10, гине все: штраф придушує модель до горизонтальної прямої. Зверни увагу й на те, що дно долини дуже пласке — десятки різних пар дають майже однакову помилку, і ганятися за третім знаком після коми тут безглуздо. І одна деталь про сам бюджет: сітка мусить бути прямокутною, тож весь дозволений бюджет вона витрачає рідко — із 60 спроб бере 56 (8 степенів × 7 значень λ). Скільки спроб стратегія витратила насправді, написано в самому вердикті.

ВкладеністьЧому «найкраща CV-оцінка» бреше

А тепер найтонше місце всієї теми — те, на якому спотикаються навіть досвідчені люди.

Ти перебрав 30 варіантів гіперпараметрів, для кожного порахував 5-Fold CV, обрав найкращий і записав у звіт його CV-оцінку. Питання: чи чесне це число?

Ні. І ось чому. Кожна CV-оцінка складається з двох частин: справжньої якості моделі й випадкового шуму (памʼятаєш гістограми з розділу 3?). Коли ти береш мінімум із 30 зашумлених чисел, ти систематично обираєш не лише найкращу модель, а й найудачливіший шум. Мінімум із 30 випадкових величин у середньому менший за їхнє справжнє значення — це чиста статистика, ніякої містики.

𝔼[ minj ŝj ] < minj 𝔼[ ŝj ]

Читається так: у середньому найменша з поміряних оцінок виявляється меншою за найменшу зі справжніх. Зліва — мінімум береться першим, а вже потім усереднюємо по повторах експерименту; справа — навпаки. ŝj — поміряна CV-оцінка j-го кандидата, 𝔼 — те саме математичне сподівання з розділу 01. Нерівність строга щоразу, коли оцінки шумлять хоч трохи, і чим більше кандидатів j, тим більший розрив.

Іншими словами: ті самі дані одночасно обрали модель і оцінили її. Це та сама помилка, що й вимірювати якість на навчальній вибірці, тільки на рівень вище — перенавчання не параметрів, а гіперпараметрів.

Розвʼязок — вкладена крос-валідація. Два цикли:

зовнішній цикл · оцінка

Ділимо дані на K_out фолдів. Один відкладаємо — його не побачить ніхто, включно з процедурою підбору.

внутрішній цикл · вибір

На решті K_out − 1 фолдів запускаємо повноцінний підбір гіперпараметрів зі своєю внутрішньою крос-валідацією. Він обирає переможця, нічого не знаючи про відкладений фолд.

і тільки потім · вимірювання

Навчаємо модель з обраними гіперпараметрами на всій внутрішній частині й міряємо на відкладеному зовнішньому фолді. Повторюємо K_out разів, усереднюємо.

Ключова властивість: у вкладеній схемі вибір і оцінка ніколи не бачать одних і тих самих даних. Тому число на виході чесне.

Інтерактив нижче показує масштаб проблеми в максимально чистому вигляді. Щоб відокремити ефект вибору від усього іншого, кандидати підібрані так, що вони насправді однаково хороші: кожен додає до базової моделі рівно одну випадкову ознаку-шум, ніяк не повʼязану з ціллю. Жоден кандидат не кращий за інших — тому будь-яка «перемога» когось із них є чистим щастям на CV.

Повзунок — кількість кандидатів, серед яких іде вибір. Три криві: оптимістична оцінка простої CV, справжня помилка обраної моделі (на незалежній вибірці з 220 точок) і оцінка вкладеної CV. Усе усереднено по 30 незалежних наборах даних, щоб криві не тремтіли.

Оптимізм росте з кількістю кандидатів

30 незалежних наборів даних. Усі кандидати рівні за якістю — і все одно «переможець» виглядає кращим, ніж є.

проста CV—
справжня—
вкладена CV—
Головне: при одному кандидаті вибирати нема з чого — і рожева крива лежить вище сірої: без вибору крос-валідація навіть трохи песимістична, бо тренує моделі на 80% даних. Далі рожева невпинно повзе вниз і перетинає сіру: це і є оптимізм від вибору. Бірюзова — вкладена CV — тримається на своєму сталому невеликому запасі й нікуди не дрейфує, скільки б кандидатів ти не перебирав. Саме сталість, а не точність до відсотка, робить її чесною.
Коли вкладеність не потрібна. Якщо в тебе є повноцінна відкладена тестова вибірка, яку ти чіпаєш рівно один раз наприкінці, — вона виконує роль зовнішнього циклу, і вкладена CV не обовʼязкова. Вкладеність потрібна тоді, коли даних мало й окремий тест виділити шкода, або коли треба чесно порівняти між собою кілька алгоритмів, кожен зі своїм підбором гіперпараметрів.

ПрактикаЧекліст і типові помилки

Крос-валідація — процедура, у якій легко зробити непомітну помилку й отримати число, що приємно виглядає й нічого не означає. Ось короткий список того, що варто перевірити.

помилкащо виходитьяк правильно
масштабування до CVscaler бачив валідацію → витікPipeline: усі перетворення всередині фолда
відбір ознак до CVсильний оптимізмвідбір — крок пайплайну, не препроцесинг
SMOTE до CVсинтетичні копії витікаютьбалансування лише на навчальній частині фолда
звичайний KFold на часівитік із майбутньогоTimeSeriesSplit
випадковий KFold на групахмодель упізнає сутністьGroupKFold
звіт за best_score_оптимізм від виборувкладена CV або окремий тест
тільки .mean()різниця в шумі здається значущоюдивитись і .std()

І одна думка на завершення. Крос-валідація не робить модель кращою — вона взагалі нічого не змінює в моделі. Вона змінює тебе: дає чесну картину того, що ти маєш, і рятує від найдорожчої помилки в машинному навчанні — від упевненості, побудованої на випадково вдалому розбитті.

Число без розкиду — це не результат. Результат — це число разом із чесною відповіддю на питання «а якби дані були трохи іншими?».