Съдържание на курса
Тензори от нулата (7): детектор на измами, който се учи от данните
От Dorian Chávez · основател и главен изпълнителен директор на Hábil ·
Обновено на 21 октомври 2026 г.
Даваш му 4000 плащания с карта и отговора за всяко, но никога не му пишеш правилото. За по-малко от секунда то научава голяма част от него. А после разбираш защо да познава в 91 % от случаите може да крие, че му се изплъзва повече от 1 от 3 измами.
Тензори от нулата · урок B, сесия 7 от 8
Сценарий: героите и историята са измислени. Техническите и историческите данни и реалните цифри са с посочен източник.
Понеделник, 14 декември, 8:18 вечерта. Книжарница „Херера“, клон Centro.
Металната ролетка вече е спусната и още леко трепери, сякаш не иска да застане неподвижно. Лампата в задната стая бръмчи. Мирише на нова хартия, на претоплено кафе и на канела от един хляб, който доня Лупе е забравила до касата. Отвън минава камион и стъклото на вратата вибрира.
Матео има отворен лаптоп върху кутия с картони. Отстрани — два разпечатани листа. На единия: продажбите, които наистина са излезли от Norte — тетрадки, химикалки, някоя раница. На другия: таксите, които е изпратила банката. Някои са дошли призори. Други са високи. Трети се появяват много далеч от клона, на места, където книжарницата не е продала и кламер.
— Това от три сутринта не може да е раница — казва Матео и го заобикаля с червен химикал.
— В този час аз продавам само сън — отвръща доня Лупе. — И то евтино, защото никой не го купува.
Преди седмица умножиха бройките по цените и сметката не излезе: има такси без стока зад тях. Фер не ги е измислила; още през септември тя беше предупредила за липсите и никой не прочете имейла ѝ. Проблемът не беше в инвентара. Беше в терминала, който смени онзи мъж със синята тениска. Скрит четец може да е копирал карти в Norte. После някой ги е използвал другаде.
Но подозрението не стига на банката. Адвокат Ортега го каза по телефона без заобикалки: „Госпожо, трябва да знам кои такси са Ваши. Не предчувствие. Кои.“
И тук е възелът. Скъпата покупка не е измама: има хора, които спестяват цяла година за телевизор. Покупката през нощта също не е: има дежурни аптеки и хора със спешности. Далечното плащане може да е на някого, който е на почивка. Никоя следа сама не осъжда никого. Тежи, когато се съберат.
Матео се опитва да прегледа листа на банката ред по ред и се отказва на четиридесетия. Така няма да свърши и до януари.
На стенния календар, онзи, който подари железарията отсреща, някой е очертал с червено 23-ти. Остават девет дни.
— Няма да пиша правило за всеки случай, лельо — казва Матео. — Ще дам на компютъра хиляди примери: колко е струвало, в колко часа, колко далеч и дали търговецът е бил от друга държава. Нека той намери закономерността.
— А ако компютърът сгреши?
Матео се взира в червения кръг на календара.
— Тогава ще ни каже накъде да продължим да търсим. Но няма да решава сам.
В предишната сесия работи с цели тензори наведнъж: бройките по цените за цяла седмица, с broadcasting и с @. Днес тези два инструмента започват да работят сами: ще видиш как една програма се учи.
Това е най-трудната сесия на курса, затова вървим бавно. Ако нещо не ти е ясно от първия път, нормално е: прочети го пак с кода, който работи пред теб.
Преговор за начало
Отговори, без да гледаш сесия 6. Отговорите са в решенията, в края.
- Кога две форми могат да се „разтегнат“, за да работят заедно?
- Каква е разликата между
*и@? - Каква форма излиза от таблица
(7, 3) @вектор(3,)?
Кодът на тази сесия е в публичното хранилище на курса, в папката
sesion-07: github.com/HabilMX/curso-tensores. Първо го напиши сам, защото така се учи; хранилището е за сравнение, когато нещо не ти излезе същото. Всяка програма е придружена от изхода, който трябва да получиш.
Цели на тази сесия
| # | В края можеш да… | Доказва го |
|---|---|---|
| 1 | Назовеш по ред петте стъпки на цикъла на обучение (предвиждане, измерване на загубата, изтриване на старите наклони, изчисляване на наклона, въртене на копчетата) и кажеш кой ред от кода върши всяка | Упражнение 26 |
| 2 | Предвидиш какво става със загубата, ако смениш броя на епохите или скоростта на обучение, и го провериш, като пуснеш модела | Упражнения 26 и 27 |
| 3 | Изчислиш с тензори точността (accuracy), пълнотата (recall) и прецизността (precision) на един детектор и обясниш защо точността сама подвежда | Упражнение 28 |
| 4 | Свържеш онова, което моделът е научил, със скритото правило, генерирало данните | Упражнение 29 |
Какво ти трябва: същата среда като в сесия 2 (Python 3 с PyTorch във venv) и всичко от сесии 1 до 6: създаване на тензори, shape, dtype, индексиране, редуциране със sum и mean, reshape, broadcasting и @. Днес не инсталираш нищо.
Трябва да разпознаваш и три неща от Python: for повтаря индентираните редове; def nombre(X): именува многократно използваема рецепта; а with ...: групира редове, които се изпълняват при едно и също правило. Не е нужно още да ги владееш: копирай блоковете цели и променяй само онова, което посочва всяко упражнение.
Защо е важно
Досега ти пишеше правилото: сумирай тази ос, умножи тези две таблици. Матео има друг вид проблем. Вижда хиляди плащания и знае кои са се оказали измама, но няма кратка фраза, която да обяснява всички случаи.
Всяко плащане носи следи. Сума. Час. Разстояние. Чуждестранен търговец. Една следа сама не осъжда никого. Няколко заедно могат да изискват проверка. Работата на програмата е да намери тази комбинация в стари примери, а не да налучква историята на някой човек.
Помисли как си се научил да познаваш дали един пазар е добър. Никой не ти е дал наръчник. Ходил си много пъти. Видял си къде има опашка, кой щанд дава зрелите плодове и кой ги крие отдолу. С времето, без да можеш да го обясниш с една фраза, вече си знаел към кой щанд да не се приближаваш. Това е учене от примери.
Или помисли за баба си с боба. Нищо не мери. Опитва, липсва ѝ сол, сипва щипка, опитва пак. Никога не са ѝ дали точната формула: намерила я е, като е поправяла. Компютърът ще прави точно това, но с числа и хиляди пъти в секунда.
Тоест обръщаме нещата: вместо да пишеш правилото, даваш примери на програмата и я оставяш сама да търси полезно правило. Това е обучение на модел. Стои зад голяма част от онова, което днес наричаме изкуствен интелект.
Хубавото е, че не се появява нова магия. Обучението е чисто смятане с тензори. Данните са тензор. Копчетата, които моделът научава, са тензори. Предвиждането ползва @. А накрая Матео пак трябва да сравнява, да пита и да събира доказателства, за да прегледа някой случая.
В бележника на Матео: един модел не доказва, че едно плащане е измама. Само подрежда хиляди следи и посочва кои си струва да се проверят първо.
Стъпка 1 — Идеята в една фраза
Матео започва с малкото. Ако разбере едно копче, може да разбере и четиридесет и девет. Ако загубата спадне в малкия пример, ще знае какво да търси, когато обучават върху покупките.
Обучението на модел е това, повторено много пъти:
- Предвиждане: моделът налучква.
- Измерване: сравняваш налучкването му с верния отговор и получаваш число, което казва колко зле е минало.
- Изтриване на старите наклони: чистиш сметката от предишния кръг (в стъпка 2 ще видиш защо е нужно).
- Изчисляване на наклона: разбираш накъде да преместиш всяко копче, та да му върви малко по-малко зле.
- Въртене на копчетата: преместваш ги със съвсем малка стъпка в тази посока.
И започва отначало: for повтаря тези пет стъпки.
Като дузпите на игрището: стрелят, виждаш, че си отишъл наляво, поправяш малко надясно и стрелят пак. Никой не ти е дал формулата на идеалния удар: намерил си я, като си поправял.
Преди да видим детектора на измами, който има 49 копчета, ще направим целия цикъл с едно-единствено.
8:24 вечерта. — Пет стъпки и толкова? — Матео свива уста. — Звучи прекалено лесно. Ако беше така, всеки щеше да прави изкуствен интелект.
— Всеки прави тортили, момчето ми — казва доня Лупе, без да вдига поглед от монетите. — Да ги направиш добре е друго. Рецептата е кратка; дългото е упражнението.
Матео се смее, но остава замислен. Права е. Рецептата на цикъла се събира на салфетка. Трудното ще бъде да ѝ вярва, когато копчетата не са едно, а четиридесет и девет, и не може да види с очите си накъде се местят. Затова започва с едно. Ако го види да работи в малък мащаб, ще му повярва и в голям.
Стъпка 2 — Едно копче: цената на тетрадка
Доня Лупе е загубила ценовата листа. Единственото, което помни, е, че един клиент си взел 3 тетрадки и платил $60. Колко струва една тетрадка?
Ти я изчисляваш наум: 20. Но ще оставим PyTorch да го открие „на сляпо“, с цикъла от 5 стъпки. Създай cerro.py:
# cerro.py — una sola perilla: el precio de un cuaderno
import torch
precio = torch.tensor(0.0, requires_grad=True) # empieza en $0: ni idea
tasa = 0.05 # qué tan largo es cada paso
for paso in range(1, 6):
cobro = precio * 3 # predicción: lo que cobraría por 3 cuadernos
perdida = (cobro - 60) ** 2 # qué tan mal: la diferencia contra $60, al cuadrado
perdida.backward() # PyTorch calcula la pendiente
a_mano = 18 * precio.item() - 360 # la misma pendiente, sacada con lápiz
assert abs(precio.grad.item() - a_mano) < 0.001, "PyTorch y el lápiz deberían coincidir"
print(f"paso {paso}: precio {precio.item():6.2f} pérdida {perdida.item():8.2f} pendiente {precio.grad.item():8.2f}")
with torch.no_grad():
precio -= tasa * precio.grad # un paso cuesta abajo
precio.grad.zero_() # borrar la pendiente para el siguiente paso
assert abs(precio.item() - 20) < 0.1, "Tres cuadernos de $20 dan $60"
print("precio aprendido:", round(precio.item(), 2))
Пусни го с python3 cerro.py:
paso 1: precio 0.00 pérdida 3600.00 pendiente -360.00
paso 2: precio 18.00 pérdida 36.00 pendiente -36.00
paso 3: precio 19.80 pérdida 0.36 pendiente -3.60
paso 4: precio 19.98 pérdida 0.00 pendiente -0.36
paso 5: precio 20.00 pérdida 0.00 pendiente -0.04
precio aprendido: 20.0
За пет стъпки стигна до $20, без никой да му каже отговора. Да минем ред по ред, защото тук е цялата сесия в умален вид.
Копчето. precio е числото, което моделът може да мести. Започва от 0. requires_grad=True казва на PyTorch: „записвай всичко, което правя с това число, защото после ще те питам за неговия наклон“. Все едно да включиш диктофона преди съвещанието.
Защо „копче“? Помисли за печката. Копчето не умее да готви. Само усилва или намалява пламъка. Който решава колко да го завърти, е онзи, който опитва яденето. Тук е същото: precio не знае нищо за тетрадки. Това е число, което може да се мести малко на едната или на другата страна. Който решава колко да го премести, е цикълът.
Предвиждането. cobro = precio * 3: с цената, която си мисли сега, колко би взел за 3 тетрадки? В стъпка 1: $0.
Загубата. (cobro - 60) ** 2: разликата между предвиденото и онова, което наистина е станало, на квадрат. Квадратът прави загубата никога да не е отрицателна (да се надхвърли с $10 или да се недостигне с $10 струва еднакво) и големите грешки да тежат много повече. В стъпка 1 е 3600: мина ужасно. Загубата е оценка наопаки: колкото по-ниска, толкова по-добре. Нула е перфектно.
Като голфа, където печели който направи по-малко удари. Или като глобите по правилника: колкото по-малко, толкова по-добре си се държал. Загубата не ти казва какво си сгрешил. Казва ти само колко. Това стига, за да започнеш да поправяш.
Наклонът. Представи си, че стоиш на хълм със завързани очи и искаш да стигнеш дъното на долината. Не виждаш нищо, затова опипваш земята с крак: ако напред слиза, правиш крачка напред; ако се качва, връщаш се. Това е наклонът (в книгите — градиент): на коя страна слиза земята и колко е стръмна. Хълмът е загубата, мястото, където стоиш, е стойността на копчето, а дъното е възможно най-ниската загуба.
Направи го наистина, поне с въображение. Нощ е, токът е спрял в целия квартал и трябва да слезеш по стръмната улица пред дома си без фенер. Не виждаш ъгъла. Но краката ти усещат наклона. Където усещаш по-стръмно, правиш по-дълга крачка. Където земята почти е равна, правиш малки крачки, защото си стигнал или почти си стигнал. Никога не си видял картата на улицата. Стигна ти да усещаш земята, крачка по крачка. Това прави компютърът: не вижда цялата форма на хълма, а само усеща наклона там, където стои.
perdida.backward() е опипване на земята. PyTorch преглежда онова, което е записал диктофонът (умножи по 3, извади 60, повдигна на квадрат), и изчислява наклона сам. Това се нарича autograd: автоматични наклони. Резултатът остава в precio.grad.
Не му ли вярваш? За това е assert с a_mano: с математиката от гимназията наклонът е 18 × precio − 360, а assert проверява на всяка стъпка, че PyTorch казва същото. По желание, не го заучавай: като се диференцира (3 × precio − 60)², се получава 2 × (3 × precio − 60) × 3, което се опростява до 18 × precio − 360. Не е нужно да умееш да диференцираш; този ред само проверява, че PyTorch е направил същата сметка, за да видиш, че няма трик.
А този assert е автоматична проверка: assert условие, "съобщение" не печата нищо, ако условието е вярно. Ако е грешно, спира програмата и показва съобщението. Докато се учиш, не трий assert-ите: те са твоят светофар.
Стъпката. precio -= tasa * precio.grad. Наклонът е −360: отрицателен значи, че земята слиза, ако качиш цената. Изваждането на отрицателно е събиране: 0 − 0.05 × (−360) = 18. Цената скача на $18. tasa (learning rate, скорост на обучение) е колко е дълга всяка крачка: много къса — и се бавиш цяла вечност; много дълга — и прескачаш на другата страна на долината. Като да паркираш на заден ход на тясно място: ако въртиш волана на резки, качваш се на тротоара; ако го въртиш милиметър по милиметър, зад теб започват да ти надуват клаксона. with torch.no_grad(): изключва диктофона, докато въртиш копчето, защото нагласянето не е част от сметката.
Изтриване на наклона. PyTorch събира всеки нов наклон с този, който вече е имал. Ако не го изтриеш с precio.grad.zero_(), следващата стъпка ще ползва стария плюс новия и ще отиде надалеч.
Обърни внимание: загубата спада от 3600 на 36 и на 0.36. Близо до дъното наклонът става малък, така че стъпките се смаляват сами. Никой не му каза „спри“: хълмът му го каза.
8:36 вечерта. Доня Лупе наднича над рамото на Матео и вижда цената да стига до 20.
— Двайсет песос. Това ти го казвах и без компютър.
— Знам, лельо. Но той не го знаеше. Започна от нула, без идея, и стигна сам.
— Като братовчед ти с колелото. Падна двайсетина пъти, а на двайсет и първия вече караше сам.
Матео се смее и поглежда отново петте реда. Не е намерил скрит отговор. Поправял е един лош отговор, докато той престане да е лош. И разбира нещо, което не очакваше: първият опит няма значение. Може да започне ужасно. Важното е всеки кръг да е малко по-малко ужасен. Следващото е същото, но с 4000 покупки и 49 копчета.
Стъпка 3 — Данните: 4000 измислени покупки
Покупките ги измисляме: няма лични данни на никого, работи без интернет и, най-важното, ние знаем правилото, което решава кое е измама, така че можем да проверим дали моделът го е открил. Те са репетицията на Матео, преди пак да докосне таксите на Norte.
За всяка покупка пазим четири стойности: сума (от $0 до почти $20 000), час (от 0 до 23), разстояние до дома на клиента (от 0 до почти 500 км) и дали търговецът е чуждестранен (1 = да, 0 = не; 1 на всеки 10).
И това е скритото правило, което знаем само ти и аз. Всяка покупка запалва „червени лампи“:
- една лампа, ако сумата е над $15 000,
- една лампа, ако покупката е през нощта (преди 5),
- една лампа, ако е на повече от 400 км от дома,
- една лампа, ако търговецът е чуждестранен.
Ако една покупка запали две лампи или повече, е измама. Една не стига: да купиш скъп телевизор по обяд е нормално; да го купиш в 3 през нощта вече мирише.
Моделът никога не вижда това правило записано: получава само четирите стойности на всяка покупка и, докато учи, верния отговор (измама или не). Този файл върши две неща. Дотук ние изграждаме лабораторията с focos; от predecir(X_ent) нататък моделът получава само X_ent. focos никога не влиза в predecir, а y_ent служи само за да се измери колко зле е предвидил. При реални данни y би идвало от вече разследвани случаи.
Създай detector.py. Върви на части. Копирай блокове от 1 до 7 в един и същ файл, в реда, в който се появяват (в кода са номерирани с коментари). Ето първата:
# detector.py — un detector de fraude que aprende de los datos
import torch
torch.manual_seed(42) # semilla fija: los mismos números "al azar" cada vez
# 1) DATOS INVENTADOS: 4,000 compras con tarjeta
n = 4000
monto = torch.rand(n) * 20_000 # pesos, de 0 a 20,000
hora = torch.randint(0, 24, (n,)).float() # hora del día, de 0 a 23
distancia = torch.rand(n) * 500 # km desde la casa del cliente
extranjero = (torch.rand(n) < 0.1).float() # 1 = comercio de otro país
# 2) LA REGLA ESCONDIDA (solo nosotros la conocemos; el modelo nunca la ve)
focos = (monto > 15_000).float() + (hora < 5).float() + (distancia > 400).float() + extranjero
y = (focos >= 2).float() # la etiqueta: 1 = fraude, 0 = normal
# 3) EL TENSOR DE ENTRADA: compras × datos, todo entre 0 y 1
X = torch.stack([monto / 20_000, hora / 23, distancia / 500, extranjero], dim=1)
print("X:", tuple(X.shape), "| y:", tuple(y.shape), "| % fraude:", round(y.mean().item() * 100, 1))
Нови функции, все прости:
torch.rand(n)даваnслучайни числа между 0 и 1; по 20 000 — суми от 0 до 20 000 (broadcasting, както в сесия 6).torch.randint(0, 24, (n,))дава случайни цели числа от 0 до 23.torch.manual_seed(42)фиксира „случайността“. Компютърът не хвърля зарове: следва рецепта и със същото начално число (seed) дава същите числа всеки път, когато го пуснеш на същия компютър.(monto > 15_000)даваTrueилиFalseза всяка покупка;.float()го превръща в 1.0 или 0.0. Сборът на четири такива преброява лампите на 4000-те покупки наведнъж.torch.stack([...], dim=1)залепя четирите списъка като колони на една таблица.
Делението на всяка колона (monto / 20_000, hora / 23…) слага всичко между 0 и 1, така че сумата (хиляди) да не смачка часа (десетици) само защото е по-голямо число.
Изходът на тази част:
X: (4000, 4) | y: (4000,) | % fraude: 15.8
X има форма (4000, 4): покупки × стойности за всяка покупка. Лист с 4000 реда и 4 колони. На първата ос се казва партида (batch, колко примера), а на втората — признаци (features, какво знаем за всеки). Тази форма ще я виждаш навсякъде.
y има форма (4000,): един отговор за покупка. Наричат се етикети (labels): колоната с верните отговори на изпита. 15.8 % от покупките са измама.
За да го видиш отблизо, добави print(X[:3]) и print(y[:3]) веднага след този print. Това е временна проверка: изпълни ги веднъж тук и ги изтрий, преди да сравниш изхода си с „Пълния изход“ от стъпка 8:
tensor([[0.8823, 0.1304, 0.4309, 0.0000],
[0.9150, 0.6087, 0.3986, 0.0000],
[0.3829, 0.4348, 0.1907, 0.0000]])
tensor([1., 0., 0.])
Покупка 0 е на $17 645 (0.8823 × 20 000) в 3 през нощта (0.1304 × 23): две лампи, измама. Покупка 1 е по-скъпа, $18 300, но в 2 следобед: една лампа, нормална. Този нюанс моделът трябва да открие.
8:52 вечерта. — Готово — казва Матео, много уверен. — Всичко над петнайсет хиляди е измама. Маркирам го и толкова.
Доня Лупе спира да брои монети.
— А госпожата, която дойде през август да плати учебните пособия на четирите си деца наведнъж? И тя ли е крадла?
Матео отваря уста и пак я затваря. Поглежда покупка 1 на екрана: $18 300, в два следобед, нормална. Правилото му щеше да я обвини. Следата не живее в една колона. Нито сумата сама, нито часът сам. Странното е, когато се съберат.
— Една лястовица пролет не прави — довършва доня Лупе и се връща към монетите.
Матео зачертава правилото си. Затова няма да го пише той.
Стъпка 4 — Да учиш и да се явиш на изпит
Ако оценяваш един ученик със същите въпроси, които е учил, не знаеш дали е научил, или е зазубрил. С моделите е същото:
# 4) Estudia con 3,200 compras; el examen es con las otras 800
X_ent, y_ent = X[:3200], y[:3200]
X_exa, y_exa = X[3200:], y[3200:]
Това е срязване по първата ос, както в сесия 3. Последните 800 покупки се заключват: моделът никога не ги вижда, докато учи.
Матео нарича първите 3200 „за учене“, а другите 800 — „изпит“. Не е украса. Ако му покаже отговорите на изпита, крайното число ще звучи красиво и няма да казва нищо.
Това е разликата между да научиш урока наизуст и да разбереш темата. Ако изпитът носи точно въпросите от урока, взимаш шестица, дори да не знаеш нищо. Ако учителят смени числата, личи кой е разбрал. 800-те отделени покупки са изпитът със сменени числа.
Една честна подробност: в упражнения 26 и 27 ще сравняваш епохи и скорости на обучение, като гледаш същите тези 800. Щом избираш по тях, те вече не са чист изпит, а упражнение за вземане на решение. В истински проект се отделя трета група — финален изпит, който никой не поглежда до последния ден.
9:05 вечерта. Телефонът на доня Лупе вибрира върху тезгяха. Фер е, от Norte: „Затворих. Днес всичко беше в брой. Трима редовни клиенти си тръгнаха, без да купят, защото нямаха пари. Пращам Ви снимка на бележките от деня.“
Доня Лупе чете съобщението два пъти и го показва на Матео.
— А аз, която преди месец не исках дори да произнеса името ѝ — казва тихо. — Човек вижда призраци там, където ги няма.
Матео не отговаря. Записва снимката на бележките в нова папка. Ако моделът каже нещо за таксите на Norte, ще му трябва с какво да го сравни. Истински данни, отделени, които моделът никога не е виждал. Също като изпита.
Стъпка 5 — Копчетата и предвиждането с @
Сега копчетата. В cerro.py имаше едно. Тук има четири тензора с копчета:
# 5) LAS PERILLAS: empiezan al azar y el modelo las va girando
W1 = (torch.randn(4, 8) * 0.5).requires_grad_()
b1 = torch.zeros(8, requires_grad=True)
W2 = (torch.randn(8) * 0.5).requires_grad_()
b2 = torch.zeros((), requires_grad=True) # () = un solo número: la perilla final
def predecir(X):
oculta = torch.relu(X @ W1 + b1) # (compras, 4) @ (4, 8) -> (compras, 8)
return oculta @ W2 + b2 # (compras, 8) @ (8,) -> (compras,)
В книгите тези копчета ги наричат тегла (W, от weights) и отклонения (b, от bias). W1 има 4 × 8 = 32, b1 има 8, W2 има 8 и b2 има 1: 49 копчета. Започват на случаен принцип (torch.randn) или от нула, както цената започна от $0. Две подробности за синтаксиса: () в torch.zeros((), ...) значи „едно-единствено число“, последното копче; а долната черта в requires_grad_() включва „диктофона“ върху същия, вече създаден тензор.
Виждал ли си пулта на озвучител на празненство? Маса, пълна с копчета: басове, високи, сила на всеки микрофон, ехо. Никое копче само не прави така, че да звучи добре. Хубаво звучи комбинацията. И озвучителят не я изчислява: мести едно, слуша, мести друго, слуша пак. Теглата са това. Всяко казва колко тежи една стойност в една смес. Всяко тегло в W1 нагласява колко се брои една стойност в едно от междинните подозрения: голямо — „усили това“; близо до нула — „на това не обръщай внимание“; отрицателно — „извади го“. Само по себе си то не мери глобалната важност на стойността, нито дали е знак за измама, или за нормалност: това зависи и от W2, от отклоненията и от relu. Разликата е, че нашият озвучител има 49 копчета и мести всичките наведнъж, водено от загубата вместо от ухото.
Прочети predecir, като гледаш само формите, както в сесия 6:
X @ W1:(покупки, 4) @ (4, 8)дава(покупки, 8). Всяка покупка минава от 4 стойности към 8 междинни „подозрения“, всяко различна смес от сума, час, разстояние и чуждестранен. Едно може да се фокусира върху „скъпо и през нощта“; друго — върху „далеч и чуждестранно“. Никой не му казва кои: открива ги, докато върти копчетата.+ b1: вектор(8,), добавен към всеки ред. Broadcasting.torch.relu(...): ако едно число излезе отрицателно, го прави нула; ако излезе положително, го оставя същото. „Ако това подозрение не важи, дори не го споменавай“. Безreluдвата слоя биха се слели в една смес, една сметка от умножения и събирания, която не може да имитира прагове като правилото (над $15 000, преди 5, над 400 км) нито как се комбинират. (Ако лампите вече влизаха преброени като входове, „две или повече“ наистина би била една смес.)oculta @ W2 + b2:(покупки, 8) @ (8,)дава(покупки,), сметката от третия въпрос на преговора. Едно число за покупка: резултатът.
Резултат по-голям от нула: моделът казва „измама“; по-малък или равен на нула: „нормално“. Колкото по-далеч от нулата, толкова по-сигурен е моделът, което не е гаранция. В книгите този резултат се нарича logit (оттам името на загубата от стъпка 6). Резултат 0 е равен на „половин-половина“: 50 % вероятност за измама според модела. Затова прагът е в нулата, а кодът разрешава точното равенство като „нормално“ (> 0, не >= 0). Това се нарича предвиждане или forward („напред“): данните влизат от едната страна, а резултатът излиза от другата, като тестото в машината за тортили.
9:20 вечерта. Матео гледа
W1, сякаш му дължи пари.— А кое от осемте е това за „скъпо и през нощта“? — пита на глас, на никого.
Печата
W1сprint(W1). Излизат му трийсет и две случайни числа, нито едно с име. Малко се отчайва. Искаше да види правилото написано, красиво, в една клетка. Няма да стане. Подозренията нямат етикет: образуват се сами, докато копчетата се въртят.— Скара ли се вече с машината? — пита доня Лупе.
— Още не. Просто не ми казва какво мисли.
— Като чичо ти. Трийсет години женени, а още не знам.
Матео се разсмива и решава да не се кара с онова, което не може да види. Стига му да пази формите: четири стойности влизат, осем сигнала излизат и накрая остава един резултат за такса.
Стъпка 6 — Загубата за въпрос „да или не“
В cerro.py загубата беше разликата на квадрат. За въпроси „да или не“ се ползва друга, вече готова в PyTorch:
perdida_fn = torch.nn.BCEWithLogitsLoss()
optimizador = torch.optim.SGD([W1, b1, W2, b2], lr=1.0)
Не се плаши от името. BCEWithLogitsLoss играе ролята на нашето изваждане на квадрат, но за измама/не измама: получава резултати и етикети и връща едно-единствено число за това колко зле е минало във всички покупки заедно. Правилото ѝ е здрав разум: ако е била измама и резултатът е бил много положителен, загуба почти нула; ако е била измама и резултатът е бил много отрицателен, огромна загуба (да си много сигурен и да грешиш е най-лошото); ако резултатът е близо до нула, средна загуба.
Помисли за приятеля, който ти се кълне, че мачът е спечелен, а накрая губят с 3:0. Не е същото като приятеля, който е казал „кой знае“. И двамата са сгрешили, но на първия таксуваш облога по-скъпо. Тази загуба таксува така: колебанието струва малко; грешката при голяма увереност струва много. Така моделът се учи да не показва увереност, която няма.
Вторият ред е оптимизаторът, онзи, който върти копчетата. SGD прави същото, което направи на ръка в cerro.py, копче -= скорост * наклон, но с всичките 49 наведнъж. lr=1.0 е скоростта.
9:41 вечерта. Машината вече има данните, копчетата и начин да оценява грешките си. Остава само да я пуснем.
Матео държи пръста над клавиша и не го натиска. А ако не научи нищо? А ако загубата си остане там, където е, и той загуби нощта, и следващата, и 23-ти дойде с празни ръце?
Доня Лупе изключва кафемашината, която вече мирише на загоряло.
— Който не рискува, не печели, момчето ми. И който не натисне бутона, не разбира.
Стенният часовник показва почти десет. Остават девет дни и всеки тежи. Матео поема дъх. Всеки кръг от цикъла трябва да го приближава до някоя следа, не само до красиво число.
Стъпка 7 — Целият цикъл и епохите
Сега наистина, сърцето на сесията:
# 6) EL CICLO DE ENTRENAMIENTO: una época = una vuelta a las 3,200 compras
for epoca in range(1, 301):
puntaje = predecir(X_ent) # 1. predecir
perdida = perdida_fn(puntaje, y_ent) # 2. medir qué tan mal le fue
optimizador.zero_grad() # 3. borrar las pendientes de la vuelta anterior
perdida.backward() # 4. calcular la pendiente de cada perilla
optimizador.step() # 5. girar cada perilla un pasito cuesta abajo
if epoca in (1, 10, 50, 100, 200, 300):
print(f"época {epoca:3d} pérdida {perdida.item():.4f}")
Сравни го с cerro.py. Същият цикъл е, ред по ред:
| Стъпка | В cerro.py |
В detector.py |
|---|---|---|
| Предвиждане | cobro = precio * 3 |
puntaje = predecir(X_ent) |
| Измерване | (cobro - 60) ** 2 |
perdida_fn(puntaje, y_ent) |
| Изтриване на старите наклони | precio.grad.zero_() |
optimizador.zero_grad() |
| Изчисляване на наклоните | perdida.backward() |
perdida.backward() |
| Въртене на копчетата | precio -= tasa * precio.grad |
optimizador.step() |
Всеки кръг на for показва на модела всичките 3200 покупки за учене. Един пълен кръг през всички данни се нарича епоха: преговаряне на цялата програма веднъж. Тук му даваме 300.
Защо толкова? Защото във всеки кръг копчетата се местят само с малка стъпка. Като да научиш песен на китара: първия път, когато я свириш цялата, грешиш във всички смени на акорди. Десетия — само в трудната. Стотния вече не мислиш. Всяка епоха е цялата песен още веднъж. Никой не я научава от първия път, нито ти, нито компютърът.
Пусни онова, което имаш, и гледай загубата:
época 1 pérdida 0.6819
época 10 pérdida 0.4098
época 50 pérdida 0.2528
época 100 pérdida 0.2070
época 200 pérdida 0.1984
época 300 pérdida 0.1910
Загубата при учене спада: моделът намалява грешките в 3200-те покупки, които вече е видял. Добър знак е, но още не доказва, че работи с нови покупки: това го проверява изпитът от стъпка 8. Спада бързо в началото, а после все по-бавно, както при тетрадката: близо до дъното земята почти не се накланя. На моята машина 300-те епохи отнеха по-малко от една десета от секундата.
Защо 300, а не повече? Измерихме го. Всеки компютър закръгля десетичните знаци в малко по-различен ред; разликата е микроскопична, от порядъка на седмата десетична цифра. При дълги стъпки като
lr=1.0, след около 300 кръга тези разлики започват да растат: при 400 два компютъра със същите версии завършиха с различни броеве хванати измами. При 300 резултатът не помръдна, дори когато нарочно въведохме разлики хиляда пъти по-големи от тези между два компютъра. Затова курсът ползва 300: ако твоето число излезе различно, да знаеш, че грешката е в твоя код, а не в машината ти.
9:47 вечерта. Матео натиска клавиша.
Терминалът изплюва първия ред, преди той да успее да мигне: época 1 pérdida 0.6819. Стомахът му се свива на възел. Това число още не му казва нищо. Моделът налучква на сляпо, с копчета на случаен принцип, като куче, което чува името си на друг език.
После се появява втората: 0.4098.
Матео се навежда към екрана.
0.2528.
— Лельо. Лельо, ела.
Доня Лупе идва, бършейки ръце в престилката.
— Какво стана? Развали ли се?
— Не. Погледни. Спада.
0.2070. 0.1984. 0.1910.
Шест числа, едно под друго, всяко по-малко. Свърши. Продължи по-малко, отколкото трябва на водата за кафето да заври. Матео гледа колоната, както се гледа резултат в края на мач, който не си очаквал да спечелиш.
Никой не е написал на машината правилото „две лампи или повече“. Никой не ѝ е говорил за нощи или чуждестранни търговци. Дадоха ѝ 3200 покупки, четирите им стойности и отговора измама/не измама за всяка, и начин да мери колко зле ѝ върви. С тези примери, кръг по кръг, тя местеше своите 49 копчета, докато греши по-малко. Матео усеща нещо странно, между гордост и тръпки. Той написа няколко реда. Останалото го направи тя.
— И това добро ли е? — пита доня Лупе.
— Учи се, лельо. Гледам я как се учи.
Доня Лупе поглежда екрана, после племенника си, който има блеснали очи като в детството, когато му се получаваше пумпалът.
— Дано се научи бързо. Двайсет и третият не чака.
Матео кимва, но вече почти не я чува. Загубата е сигнал, не присъда. Казва му, че моделът греши по-малко при ученето. Остава важното: да види какво прави, когато му вземат отговорите.
Стъпка 8 — Изпитът: 91 %
Остава важното: добре ли му върви с покупки, които никога не е виждал? Добави последната част:
# 7) EL EXAMEN: 800 compras que el modelo nunca vio
with torch.no_grad():
dice_fraude = (predecir(X_exa) > 0).float() # puntaje positivo = "esto es fraude"
exactitud = (dice_fraude == y_exa).float().mean().item()
atrapados = (dice_fraude * y_exa).sum().item() # dijo fraude y sí era
falsas = (dice_fraude * (1 - y_exa)).sum().item() # dijo fraude y no era
escapados = ((1 - dice_fraude) * y_exa).sum().item() # dijo normal y era fraude
print("exactitud:", round(exactitud, 3))
print("fraudes en el examen:", int(y_exa.sum().item()))
print("atrapados:", int(atrapados), "| se escaparon:", int(escapados), "| alarmas falsas:", int(falsas))
print("recall:", round(atrapados / (atrapados + escapados), 3),
"| precisión:", round(atrapados / (atrapados + falsas), 3))
Последните две цифри, които печата (recall и precision), още нямат име за теб: игнорирай ги до стъпка 9, където се обясняват и ги четеш отново. Засега гледай точността и трите сметки.
Номерът е * елемент по елемент, от сесия 6: dice_fraude * y_exa дава 1 само там, където и двете са 1 (каза измама и е била). 1 - y_exa обръща единиците и нулите, така че dice_fraude * (1 - y_exa) дава 1 там, където каза измама и не е била. Сумираш и получаваш колко са от всеки случай.
Целият скрипт е 61 реда, 41 без коментари и празни редове. Пусни го с python3 detector.py. Пълният изход, еднакъв и двата пъти, когато го пуснах:
X: (4000, 4) | y: (4000,) | % fraude: 15.8
época 1 pérdida 0.6819
época 10 pérdida 0.4098
época 50 pérdida 0.2528
época 100 pérdida 0.2070
época 200 pérdida 0.1984
época 300 pérdida 0.1910
exactitud: 0.908
fraudes en el examen: 125
atrapados: 81 | se escaparon: 44 | alarmas falsas: 30
recall: 0.648 | precisión: 0.73
Точност: 0.908. При покупки, които никога не е виждал, позна почти 91 от 100, без никой да му каже правилото за лампите.
За seed-а: със същата версия на PyTorch и компютър като моя нормалното е да получиш точно това. Но PyTorch не гарантира еднакви числа между версии, нито между различни компютри (документация на PyTorch за възпроизводимостта): последните десетични знаци може малко да се движат. При 300 епохи измерихме, че не се движат (виж „Защо 300, а не повече?“ в стъпка 7); в упражненията с много повече кръгове може да се движат. Онова, което трябва винаги да се случва: загубата спада, а точността е около 91 %. Ако получиш нещо много различно, прегледай таблицата с грешки.
10:12 вечерта. Матео вижда 91 и вдига ръце, сякаш е вкарал гол.
— Деветдесет и едно, лельо! Позна деветдесет и един от всеки сто!
Доня Лупе не вдига ръце. Намества очилата и се приближава до екрана със спокойствието на човек, преброил хиляди изрязвания на каса.
— И тогава колко е добър?
— Ами… много добър. Деветка е.
— Деветка по какво. Какво пише онзи долен ред?
Матео го чете на глас, по-бавно: „изплъзнаха се: 44“.
Радостта му спада като спукан балон. Четирийсет и четири. От 125 измами в изпита четирийсет и четири минаха като нищо. Моделът ги е видял, прегледал и казал „нормално“.
Покупките са измислени, знае го. Но не може да не им даде лице. Представя си една от тези четирийсет и четири. Една госпожа, която е купила тетрадки в Norte през септември, платила е с карта, благодарила е и си е тръгнала. И една сутрин, на опашката за тортили, отваря банковото приложение и вижда такса от седемнайсет хиляди песос в магазин, където никога не е стъпвала. Нейната тринайсета заплата или онова, което щеше да е. После си представя момчето, което е събирало за телефон и му го изпразниха във вторник в три сутринта. И друг. И друг. Четирийсет и четири пъти.
— Не е деветка, лельо — казва и гласът му излиза мъничък. — За домашно деветка е добра оценка. За 125 измами четирийсет и четири е отворена врата.
Доня Лупе слага ръка на рамото му.
— Виждаш ли. Не е злато всичко, което блести, момчето ми. Сега разбери от какво е направено.
Каре за тревога: точността брои всички попадения заедно. Не ти казва колко измами е пропуснала. Затова една банка не може да се задоволи с това число.
91 % добре ли е? Звучи като оценка 9. Сега идва най-важното от сесията.
Стъпка 9 — Капанът: мързеливият детектор прави 84
Представи си „детектор“, който на всички покупки казва „не е измама“, без да ги поглежда.
В изпита има 800 покупки и 125 са измама. Другите 675 са нормални и мързеливият ги познава всичките: 675 от 800 е 84.4 %. Ще го провериш с тензор в упражнение 28.
Едноредова програма, която не върши нищо, прави 84; нашата — 91. Разликата е много по-малка, отколкото изглеждаше. Точността подвежда, когато важното е рядко, а в една банка измамата обикновено е рядка: почти всички покупки са на честни хора.
Помисли за металдетектора на входа на концерт. Почти никой не носи нещо опасно. Ако охранителят пусне всички без проверка, „познава“ с почти цялата публика. И въпреки това е най-лошият охранител на света, защото единственият случай, който имаше значение, му е убягнал. Да познаеш обикновеното е лесно. Трудното, и онова, което струва, е да хванеш рядкото.
10:31 вечерта. Матео записва мързеливия детектор на лист, без компютър: „на всичко — не“. Гледа го известно време. Един ред е. Смешен е. И прави 84.
— Тоест моят модел с 49 копчета бие нищоправенето с по-малко от седем точки — измърморва.
— А това малко ли е? — пита доня Лупе.
— Все едно да се хвалиш, че тичаш по-бързо от някого, който седи.
Доня Лупе избухва в смях, от който звънват чашите на рафта.
— Е, с това няма да биеш дори боклукчийския камион.
Матео не се обижда. За първи път през нощта чувства, че наистина разбира. Числото, което има значение, не е най-голямото. То е онова, което брои какво се е изплъзнало.
Затова банките гледат други две числа. Първо, четирите възможни случая на изпита:
| Наистина е била измама | Наистина е била нормална | |
|---|---|---|
| Моделът каза „измама“ | 81 хванати | 30 фалшиви тревоги |
| Моделът каза „нормално“ | 44 се изплъзнаха | 645 правилно пропуснати |
(Четирите клетки правят 800. Попаденията са 81 + 645 = 726, а 726 / 800 = 0.9075, което закръглено дава 0.908, точността отгоре.)
Recall (пълнота): от всички измами, колко хвана? Имаше 125 измами и хвана 81: 81 / 125 = 0.648. Изплъзнаха му се 44 от 125: повече от 1 на всеки 3 измами. Всяка от тези 44 е човек, който утре преглежда извлечението си и намира такса от $17 000 в магазин, където никога не е бил. Трябва да оспори таксата, банката разглежда случая и често накрая му връща парите; страха никой не му го връща. Мързеливият детектор има recall нула: не хваща нито една.
Precision (прецизност): от всички тревоги, които вдигна, колко бяха истински? Вдигна 81 + 30 = 111 тревоги и 81 бяха измама: 81 / 111 = 0.73. Горе-долу 1 на всеки 4 тревоги беше фалшива. Всяка от тези 30 е честен човек, на когото, според начина, по който работи банката, са спрели покупката на касата в магазина, с опашка отзад, или са го накарали да потвърди, че е той, преди да го пуснат. Ако това става често, клиентът сменя банката.
И двете неща тежат. Ако банката иска да хване всички измами, маркира всичко като подозрително: перфектен recall и всичките ѝ клиенти ядосани. Ако не иска да безпокои никого, не маркира нищо: нула фалшиви тревоги и всички измами минават, както при мързеливия. Истинската работа е по средата и зависи от това колко струва всяка грешка. Затова, за да се прецени система срещу измами, по-важно е колко измами хваща и колко фалшиви тревоги вдига, отколкото „точността“ сама.
Голямият урок на днес: да познаваш много не стига. Преди да повярваш на един модел, питай колко е рядко онова, което търси, и колко от тези редки случаи хваща.
11:05 вечерта. Матео чертае таблицата с четири клетки на гърба на сметка за ток. Горе вляво, 81: които хвана. Долу вляво, 44: които си отидоха. Горе вдясно, 30: честни хора с отказана карта на касата. Гледа тази клетка. Ако банката направеше това на клиентите на Norte, книжарницата щеше да загуби малкото, които още се връщат.
— Не е едно число, лельо. Две са. И се дърпат едно друго, като късо одеяло: ако си покриеш краката, раменете ти се разкриват.
Доня Лупе кимва бавно. Това го разбира отлично. Матео прибира сметката в джоба на якето. Утре ще му трябва.
👉 Ако идваш от бизнес администрацията: не е нужно да обучаваш модела, за да зададеш правилния въпрос на съвещанието. Когато някой представи „нашият модел познава в 91 %“, питай: колко прави един, който не прави нищо? Колко от случаите, които ни интересуват, хваща (recall)? Колко от тревогите му са истински (precision)? После преведи всяка грешка в песос: колко струва една измама, която се изплъзва, и колко един ядосан клиент. Тази таблица с четири клетки е бизнес инструмент и служи също толкова за решаване на кого да се даде кредит или коя застрахователна претенция да се прегледа.
Упражнения към сесия 7
Решенията са в края. Преди всяка промяна запиши на хартия какво мислиш, че ще стане. Предвиждането, преди да пуснеш, е половината от ученето. Попълни тази таблица на хартия (упражнения 26 и 27 искат тези четири изпълнения):
| Конфигурация | Моето предвиждане | Крайна загуба | Точност | Хванати | Фалшиви тревоги | Какво научих? |
|---|---|---|---|---|---|---|
300 епохи, lr=1.0 |
||||||
2000 епохи, lr=1.0 |
||||||
300 епохи, lr=0.1 |
||||||
300 епохи, lr=10 |
Упражнение 26 — Повече кръгове, по-малко кръгове. В detector.py броят на епохите е в range(1, 301). Вече го пуснах със 100 епохи (range(1, 101)): загубата завършва на 0.2070, точността на 0.902, хваща 77 измами и дава 30 фалшиви тревоги. Сега ти: пусни го с 2000 епохи. Добави 1000, 2000 към списъка с епохи, които се печатат, за да видиш загубата.
а) Преди да пуснеш: крайната загуба ще е по-голяма или по-малка от 0.1910? б) Какво стана с точността, хванатите и фалшивите тревоги? в) Посочи в кода си кой ред е всяка от петте стъпки на цикъла (предвиждане, измерване, изтриване на наклони, изчисляване на наклони, въртене на копчета).
Упражнение 27 — Дължината на стъпката. Върни се на 300 епохи. Сега смени скоростта, lr=1.0, първо на lr=0.1 (мравешки стъпки) и после на lr=10 (великански крачки). Преди да пуснеш всяка, предвиди: загубата спада повече или по-малко? Защо?
Предупреждение: при едната от двете програмата накрая хвърля ZeroDivisionError. Не е твоя грешка. Прочети редовете над грешката и обясни защо е станало. Подсказка: виж колко тревоги е дала общо.
Упражнение 28 — Изчисли мързеливия детектор. Добави това в края на detector.py и попълни празните места. Мързеливият детектор е тензор от 800 нули: „не е измама“ за всички покупки на изпита.
# Ejercicio 28: el detector flojo que siempre dice "no es fraude"
siempre_no = torch.____(800)
assert siempre_no.shape == y_exa.shape, "Tiene que haber una respuesta por cada compra del examen"
exactitud_flojo = (siempre_no == ____).float().mean().item()
atrapados_flojo = (siempre_no * ____).sum().item()
print("exactitud del flojo:", round(exactitud_flojo, 3))
print("fraudes que atrapa:", int(atrapados_flojo), "de", int(y_exa.sum().item()))
# la misma cuenta con un for, como antes de los tensores
aciertos = 0
for i in range(800):
if y_exa[i].item() == ____:
aciertos = aciertos + 1
assert round(aciertos / 800, 3) == round(exactitud_flojo, 3), "El for y el tensor deberían coincidir"
print("El for dice lo mismo:", aciertos, "aciertos de 800")
Втора част. Изчисли сега recall и precision на обучения детектор, без да гледаш последните два реда от стъпка 8. Вече имаш atrapados, falsas и escapados във файла си:
recall_modelo = ______________________________
precision_modelo = ___________________________
print("recall del modelo:", round(recall_modelo, 3))
print("precisión del modelo:", round(precision_modelo, 3))
assert round(recall_modelo, 3) == 0.648 and round(precision_modelo, 3) == 0.73, "Revisa cuál de las cuatro casillas falta en tu fracción"
С базовото изпълнение (300 епохи, lr=1.0) трябва да излязат 0.648 и 0.73. Ако си сменил епохите или скоростта, махни assert.
После отговори: какъв е recall на мързеливия детектор? Защо не можеш да изчислиш неговата precision?
Упражнение 29 — Какво е научил моделът? Правилото ползва четирите стойности. Усети ли го моделът? Попитай го, като разбъркаш една колона: ако разбъркаш часа между 800-те покупки на изпита, всяка покупка остава с часа на друга на случаен принцип. Колоната си е там, но вече не казва нищо. Ако точността падне много в това изпълнение, това е доказателство, че моделът е ползвал тази колона, за да предвижда. Само по себе си не доказва, че е открил точното правило, нито че тази колона причинява измама.
Ще го повториш за всяка от 4-те колони: копирай изпита, разбъркай само тази колона, предвиди отново и покажи точността и хванатите измами. Два нови инструмента:
X_exa.clone()прави копие, за да не развалиш оригиналния изпит.torch.randperm(800)дава числата от 0 до 799 в разбъркан ред.X_exa[torch.randperm(800), col]е колонатаcol, разбъркана.
Не забравяй да предвиждаш вътре в with torch.no_grad():, както на изпита. Решен пример с колоната monto (колона 0):
X_original = X_exa.clone()
X_revuelto = X_exa.clone()
X_revuelto[:, 0] = X_exa[torch.randperm(800), 0]
with torch.no_grad():
dice = (predecir(X_revuelto) > 0).float()
print("monto: exactitud", round((dice == y_exa).float().mean().item(), 3))
Сега попълни сам, с for, колони 1, 2 и 3 (час, разстояние и чуждестранен) и покажи и хванатите измами:
for col in range(1, 4):
X_revuelto = X_exa.________()
X_revuelto[:, col] = X_exa[torch.________(800), col]
with torch.no_grad():
dice = (________(X_revuelto) > 0).float()
# imprime la exactitud y los fraudes atrapados de esta columna
assert torch.equal(X_exa, X_original), "No debes alterar el examen original"
После отговори: коя колона най-много боли модела да загуби? Някоя не му направи впечатление? Съвпада ли с него скритото правило на лампите?
Чести грешки (и как да ги забележиш)
| Грешка | Как изглежда | Как да я забележиш | Какво да направиш |
|---|---|---|---|
Забравено requires_grad=True на копче |
В cerro.py, където е единственото копче: RuntimeError: element 0 of tensors does not require grad and does not have a grad_fn при извикване на backward(). В detector.py, ако го забравиш само при W1, не хвърля грешка: W1.grad остава None, W1 никога не помръдва, загубата заседва на 0.3366 и хваща само 13 от 125 |
В cerro.py хвърля грешка на реда с backward; в detector.py загубата спада много по-малко от нормалното, а print(W1.grad) казва None |
Включи „диктофона“ при създаването на всяко копче: requires_grad=True |
Забравено изтриване на наклоните (zero_grad) |
В cerro.py assert-ът те спасява: хвърля грешка в стъпка 2 с AssertionError: PyTorch y el lápiz deberían coincidir. Без assert-ите не хвърля грешка, а това е опасното: цената скача на 37.80, после на 41.58, загубата се качва до 4191.27 (в стъпка 4) и накрая научената цена остава 4.95 вместо 20 |
Загубата се качва и спада вместо да спада | PyTorch събира новите наклони със старите; изтривай ги на всеки кръг |
| Етикети с друга форма от резултатите | ValueError: Target size (torch.Size([3200, 1])) must be the same as input size (torch.Size([3200])) |
Отпечатай y.shape и shape на резултата: трябва да са еднакви |
Остави y с форма (4000,), без reshape(4000, 1) |
| Прекалено голяма скорост | Обучението не хвърля грешка. С lr=10 загубата заседва на 0.4365 и моделът спира да хваща измами; накрая изчисляването на прецизността хвърля ZeroDivisionError, защото не е дал нито една тревога (упражнение 27) |
Загубата спира да спада или скача | Намали скоростта; пробвай по 10 пъти (1, 0.1, 0.01…) |
Две извиквания на backward() върху една и съща загуба |
RuntimeError: Trying to backward through the graph a second time (or directly access saved tensors after they have already been freed). |
Гърми при втория backward |
Един backward на кръг; следващият кръг предвижда и мери отново |
| Да вярваш само на точността | „91 %!“ | Изчисляваш мързеливия детектор и прави 84 % | Гледай и recall, и precision |
Преговор за следващата сесия
Сесия 8 започва с тези три въпроса. Отговори, без да гледаш урока:
- Каква форма има входният тензор на детектора и какво е всяка ос?
- Какво мери загубата и защо трябва да спада?
- Защо да познаваш в 91 % от случаите може да не е достатъчно за една банка?
🔁 Следващата седмица: тензорите в банката, застрахователя и магазина. Реални случаи, с посочен източник, и предизвикателството сам да напишеш формата на тензора на всеки. Спойлер: днешният детектор има точно формата на истинските.
Решения (не ги гледай, преди да опиташ)
Преговор за начало.
- Формите се сравняват от дясно наляво, ос по ос. На всяка ос двете мерки трябва да са равни или едната да е 1 (единицата е тази, която се „разтяга“). Ако на някоя ос не е така, PyTorch дава грешка.
*умножава елемент по елемент: клетка с клетка, а формите трябва да съвпадат или да се разтеглят.@е матричното произведение: ред по колона, умножава и сумира. Подвеждащото е, че понякога и двете минават без грешка и дават различни неща.(7,). Тройката по средата се „изяжда“ (умножава се и се сумира) и остава едно число за всеки от 7-те реда. Днес го ползва:(покупки, 8) @ (8,)даде(покупки,).
Упражнение 26. С 2000 епохи (първите редове са същите като при 300; поставям от 300):
época 300 pérdida 0.1910
época 1000 pérdida 0.1641
época 2000 pérdida 0.1142
exactitud: 0.93
fraudes en el examen: 125
atrapados: 83 | se escaparon: 42 | alarmas falsas: 14
recall: 0.664 | precisión: 0.856
а) По-малка: 0.1142. Повече кръгове, повече време да слезеш по хълма. Първите епохи дават същите числа като при 300 благодарение на фиксирания seed. От 1000 нататък на твоя компютър последната цифра на загубата и някой брой могат да излязат малко различни: това са разликите от закръгляването от каретата „Защо 300, а не повече?“, които при толкова кръгове имат време да нараснат.
б) Точността се качва на 93 %, хваща 83 измами вместо 81, а фалшивите тревоги падат от 30 на 14. Внимание: повече епохи не винаги помагат. При реални данни прекалено много кръгове могат да накарат модела да запомни примерите за учене и да му върви по-зле на изпита. Затова изпитът е с отделени данни.
в) puntaje = predecir(X_ent) предвижда; perdida = perdida_fn(puntaje, y_ent) мери; optimizador.zero_grad() изтрива наклоните; perdida.backward() изчислява наклоните; optimizador.step() върти копчетата.
Упражнение 27. С lr=0.1 (махам някои епохи от средата):
época 1 pérdida 0.6819
época 50 pérdida 0.4238
época 300 pérdida 0.3194
exactitud: 0.859
fraudes en el examen: 125
atrapados: 12 | se escaparon: 113 | alarmas falsas: 0
recall: 0.096 | precisión: 1.0
Загубата спада, но много по-бавно: за 300 епохи стига до 0.3194, по-зле, отколкото скорост 1.0 постигаше за 50. Мравешки стъпки: трябват много повече кръгове. И хваща само 12 от 125. Обърни внимание на прецизността: 1.0, перфектна, защото 12-те тревоги, които е дал, са били измама. Детектор, който почти никога не звъни, почти никога не греши, когато звънне, а въпреки това пропуска 113 измами: още една причина да не го съдиш по едно число.
С lr=10 (пак съкратено):
época 1 pérdida 0.6819
época 10 pérdida 0.8124
época 50 pérdida 0.4365
época 300 pérdida 0.4365
exactitud: 0.844
fraudes en el examen: 125
atrapados: 0 | se escaparon: 125 | alarmas falsas: 0
Traceback (most recent call last):
...
"| precisión:", round(atrapados / (atrapados + falsas), 3))
~~~~~~~~~~^~~~~~~~~~~~~~~~~~~~~~
ZeroDivisionError: float division by zero
Крачките бяха толкова дълги, че в епоха 10 загубата се качи (от 0.6819 на 0.8124): прескочи на другата страна на долината. После заседна на 0.4365. И с какво завърши? Точност 0.844, нула хванати, нула тревоги: моделът завърши, като се държи като мързеливия детектор. С lr=10, в това изпълнение, обновяванията изгасиха осемте междинни сигнала (relu остави нула всичките 25 600 за покупките за учене) и моделът остана с постоянен, отрицателен резултат, един и същ за всички покупки (−1.67 на изпита). С праг в нулата никога не дава тревога. Внимание: продължава да минимизира същата загуба; не е „избрал“ точността. Че му излиза 84 %, е последица от това, че измамата е рядка.
ZeroDivisionError излиза, защото прецизността е хванати делено на тревоги, а тревоги имаше 0: 0 / 0. Не може да се измери точността на мерника на някого, който никога не е стрелял.
Упражнение 28. Празните места са zeros, y_exa, y_exa и 0. Този блок не е за копиране: показва само редовете с празни места, вече попълнени, а ... са редовете от твоя файл, които не се променят:
siempre_no = torch.zeros(800)
exactitud_flojo = (siempre_no == y_exa).float().mean().item()
atrapados_flojo = (siempre_no * y_exa).sum().item()
...
if y_exa[i].item() == 0:
Изход (последните три реда, след нормалния изход на детектора):
exactitud del flojo: 0.844
fraudes que atrapa: 0 de 125
El for dice lo mismo: 675 aciertos de 800
Recall е 0 / 125 = 0: не хваща нито една измама. Прецизността не може да се изчисли по същата причина както в упражнение 27: не дава никаква тревога, а 0 / 0 не е определено. Детектор с 84 % точност и recall нула: това е целият урок в две числа.
Упражнение 29. Едно решение:
# Ejercicio 29: ¿qué dato le importa más al modelo?
nombres = ["monto", "hora", "distancia", "extranjero"]
for col in range(4):
X_revuelto = X_exa.clone() # copia del examen
X_revuelto[:, col] = X_exa[torch.randperm(800), col] # revuelve UNA columna
with torch.no_grad():
dice = (predecir(X_revuelto) > 0).float()
exac = (dice == y_exa).float().mean().item()
atrap = (dice * y_exa).sum().item()
print(f"revolviendo {nombres[col]:10s} exactitud {exac:.3f} atrapados {int(atrap)} de 125")
Изход (в края на детектора, със същия seed):
revolviendo monto exactitud 0.848 atrapados 54 de 125
revolviendo hora exactitud 0.873 atrapados 63 de 125
revolviendo distancia exactitud 0.870 atrapados 64 de 125
revolviendo extranjero exactitud 0.849 atrapados 57 de 125
Без разбъркване познаваше 90.8 % и хващаше 81. Разбъркването на коя да е от четирите го събаря: точността пада до между 84.8 % и 87.3 %, едва над мързеливия или равна на него, и спира да хваща между 17 и 27 измами. Най-много го заболяха: сумата и чуждестранният.
Съвместимо е със скритото правило: четирите колони палят лампа, а моделът е получил етикетирани примери, произведени с тези четири колони. Това е доказателство, че ги ползва, а не доказателство, че е възстановил точното правило. Ако твоите числа са излезли различни, защото твоят randperm е разбъркал другояче, добре е: важното е и четирите да паднат.
Какво следва
В сесия 8, последната, Матео излиза от книжарницата и влиза в банката, за да види как работят същите тези тензори с милиони покупки и с хора, които проверяват решението.
Ако искаш да изпревариш, помисли: ако детекторът виждаше не една покупка, а последните 30 покупки на всеки клиент, каква форма би имал входният му тензор?
11:48 вечерта.
Задната стая е тиха. Доня Лупе е заспала на пластмасовия стол, с престилка и с торбата с монети в скута. Хлябът с канела си стои до касата, вече твърд.
Матео не пуска детектора върху таксите на Norte. Мисли го, с пръст над клавиша, и не го прави: упражнението се е учило с покупки и правило, които са измислени, и не става за решения за истински хора. Вместо това вади таблицата с четири клетки и я слага до бележките, които Фер е записала на ръка. Подрежда въпросите: кои такси нямат стока зад себе си, кои падат през нощта или далеч, какви доказателства може да поиска от банката.
Трябва да се чувства доволен и е наполовина. Има метод. Но методът не е доказателство.
Отваря имейла, за да пише на адвокат Ортега. Изписва „Споделям с Вас модел, който“ и спира. Представя си лицето на адвоката. Двайсетгодишно момче, лаптоп с одраскан стикер, програма, която познава в 91 % и оставя да избягат повече от един на всеки трима. А от другата страна — банка със собствени системи, собствени анализатори, собствени правила.
Изтрива фразата.
През прозореца минава последният патрул на нощта. Лампата бръмчи. На календара червеният кръг на 23-ти изглежда по-голям, отколкото сутринта.
Матео затваря лаптопа бавно, за да не събуди леля си. Вече знае какво има. Знае и какво му липсва: не по-високо число, а някой от другата страна, готов да провери.
Оставаха девет дни до 23-ти. А банката нямаше да повярва на модел, направен на един лаптоп.
Пиша от онова, което виждаме всеки ден в Hábil, където изграждаме системи за банки, застрахователи и компании в Мексико. Ако в работата ти или в семейния ти бизнес се сблъскаш с подобен проблем, нека поговорим: habil.mx
Предпочитате имейл? Пишете ни на hola@habil.mx