Съдържание на курса
Тензори от нулата (6): операции с цели тензори
От Dorian Chávez · основател и главен изпълнителен директор на Hábil ·
Обновено на 21 октомври 2026 г.
Как се умножават продажбите за цяла седмица по цените им на един ред, правилото, което решава кога PyTorch „разтяга“ тензор, и капанът на * срещу @, който не дава никаква грешка.
Тензори от нулата · урок B, сесия 6 от 8
Сценарий: героите и историята са измислени. Техническите и историческите данни и реалните цифри са с посочен източник.
Понеделник, 7 декември, 7:40 вечерта. Книжарница „Херера“, клон Centro.
Металната ролетка е спусната до средата. Вътре мирише на нова хартия и на претоплено кафе. Доня Лупе брои монети върху тезгяха. Матео е отворил лаптопа между едно руло гланцова хартия и една кутия химикалки.
Остават шестнайсет дни до 23-ти.
От миналата седмица Матео знае две неща. Че Norte вървеше добре до август. И че през август един мъж със синя тениска „смени терминала“, без никой да го е викал. Онова, което не знае, е каква е връзката между двете.
— Вече не искам песо, лельо — казва той. — Искам бройки.
— Бройки от какво?
— От всичко. Колко тетрадки, колко химикалки, колко раници. Всеки ден. От Centro и от Norte.
Доня Лупе го поглежда над очилата.
— За това не ти трябва компютър, момчето ми. Трябва ти някой, който да брои.
— Вече са преброили. Фер ми прати данните от Norte сутринта.
Преди две седмици никой в семейството не искаше да произнесе името на Фер на глас. Centro продаваше около четири пъти повече от Norte, а тя беше управителката. Онзи следобед не вдигна телефона. Но днес прати цялото си преброяване, ред по ред, без някой да ѝ го иска два пъти.
Телефонът на доня Лупе вибрира. Имейл от банката. От господин Ортега.
„Уважаема госпожо Херера, както поискахте, изпращам Ви общата сума, таксувана от терминала във Вашия клон Norte за посочената седмица. Напомням Ви, че крайният срок е 23 декември. Оставам на разположение.“
Отдолу — едно-единствено число: $16,850.
Доня Лупе подава телефона на Матео, без да каже нищо.
— Това много ли е, или малко?
— Зависи колко сме продали.
И тук е въпросът. За да разбереш колко са продали, трябва да умножиш всяка бройка по цената ѝ. Двайсет и една клетки за всеки клон. С for Матео вече знае как. Но тази вечер иска сметка, която да вземе цялата таблица наведнъж.
Защото ако бройките по цените им дават по-малко от таксуваното от терминала, някой е таксувал нещо, което никога не е излизало от книжарницата.
В предишната сесия промени формата на тензор, без да пипаш числата му: 12 месеца в един ред станаха 4 тримесечия по 3 месеца. Днес спираш да местиш числата и започваш да смяташ с тях, с целия тензор наведнъж.
Преговор за начало
Отговори, без да поглеждаш предишната сесия. Отговорите са в решенията, в края.
- Кога един
reshapeе валиден? - Какво означава
-1вreshape? - Защо
reshapeна таблица не е същото като завъртането ѝ?
Кодът на тази сесия е в публичното хранилище на курса, в папката
sesion-06: github.com/HabilMX/curso-tensores. Първо го напиши сам, защото така се учи; хранилището е за сравнение, когато нещо не ти излезе по същия начин. Всяка програма идва с изхода, който трябва да получиш.
Цели на тази сесия
В края ще можеш да правиш тези четири неща. До всяко е упражнението, което го доказва.
| # | В края можеш… | Доказва го |
|---|---|---|
| 1 | Да извършиш операция с два тензора с еднаква форма число по число и да я провериш срещу for |
Упражнение 23 |
| 2 | Да предвидиш, преди да изпълниш, дали две форми могат да се „разтегнат“, за да участват заедно в операция, и каква форма излиза | Упражнения 22 и 25 |
| 3 | Да изчислиш с @ сума за всеки ред и да я провериш по два начина: с for и със sum(dim=1) |
Упражнение 24 |
| 4 | Да разпознаеш кога * и @ дават различни резултати, без да хвърлят грешка |
Упражнение 22 |
Какво трябва да знаеш предварително: сесии 1 до 5: да създаваш тензор, да разчиташ неговите shape и dtype, да изваждаш части от него с индекси, да сумираш със sum(dim=) и да му сменяш формата с reshape. Какво ти трябва: средата от сесия 2 с инсталиран PyTorch и един нов файл, operar.py, в който ще добавяш код стъпка по стъпка. Когато една стъпка те помоли да предизвикаш грешка, добави реда, изпълни го, разчети грешката и го изтрий, за да продължиш.
Защо е важно
Матео би могъл да направи сметката като в миналите сесии: for в друг for, клетка по клетка. Става. Но така не работи един модел на изкуствен интелект. Нито системата, която спря терминала в Norte една неделя в полунощ.
Голяма част от работата на един модел отива в две операции: умножение на тензори число по число и умножение на матрици. Когато една банка проверява покупка, моделът взема списъка с числа на покупката и го умножава по таблица с „тегла“, които са числа, научени от модела. Това е умножение на матрици. И не го прави веднъж: прави го милиони пъти в секунда. Някъде в банката точно такава сметка е отбелязала плащанията в Norte като „необичайни“.
Затова съществуват видеокартите за изкуствен интелект. Една полезна аналогия: обикновеният компютър е шепа много умели готвачи; видеокартата са хиляди помощници, които умеят само да кълцат, но кълцат всички едновременно. Умножението на матрици е „кълцане“: огромно количество умножения и събирания, които могат да се разпределят между много помощници наведнъж. AlexNet, мрежата от 2012 г., с която започна сегашната вълна на изкуствения интелект, според статията на самите ѝ автори „се обучава между пет и шест дни на два GPU GTX 580 с 3 GB“ (Krizhevsky, Sutskever и Hinton, 2012). Две видеокарти за игри.
Днес всичко върви на твоя компютър, без видеокарта. Двете операции са същите и в следващата сесия ще ти трябват, за да обучиш свой собствен модел. Тази вечер на Матео му трябват за по-прост въпрос: колко пари казват бройките?
Случаят: сега доня Лупе брои бройки
Досега числата на доня Лупе бяха в песо. Този път Матео я помоли за нещо друго: колко бройки от всеки продукт е продал Centro всеки ден. И отделно — нейния ценоразпис, който тя издиктува по памет: тетрадката по $40, химикалката по $10 и раницата по $350.
— Не са се променяли от връщането на училище — казва тя. — И няма да се променят, докато не поскъпне хартията или докато не умра, което дойде по-напред.
С бройките и цените всяко нещо, свързано с пари, излиза от една сметка. Матео започва с Centro, клона, който никой не подозира: ако сметката излезе вярна там, ще може да ѝ се довери, когато стигне до Norte.
Стъпка 1 — Бройките и цените
Напиши това в началото на operar.py. Първо списъците на Python, които вече познаваш; после тензорите, с онова, което видя в сесия 2:
import torch
lista_unidades = [
[30, 80, 2], # lunes
[40, 90, 3], # martes
[25, 70, 2], # miércoles
[35, 90, 2], # jueves
[50, 120, 3], # viernes
[30, 80, 1], # sábado
[20, 50, 1], # domingo
]
lista_precios = [40, 10, 350] # cuaderno, pluma, mochila
unidades = torch.tensor(lista_unidades)
precios = torch.tensor(lista_precios)
print(unidades.shape, unidades.dtype)
print(precios.shape, precios.dtype)
Изход:
torch.Size([7, 3]) torch.int64
torch.Size([3]) torch.int64
Разчети го като в сесия 2: unidades е таблица от 7 дни по 3 продукта; precios е списък от 3 цени, по една за продукт. И двата съдържат цели числа. Запази списъците на Python: ще ти трябват за проверката с for.
Преди да продължиш, един въпрос за размисъл: в понеделник са продадени 30 тетрадки по $40. Колко пари са влезли от тетрадки в понеделник? Ако каза $1,200, вече знаеш как се прави днешната сметка. Единственото ново е да я направиш с 21-те числа наведнъж.
Стъпка 2 — Елемент по елемент: две еднакви таблици
Сега файлът на Фер. Матео го отваря внимателно: в сесия 2 един файл от Norte имаше крив ред. Този е пълен. Същата форма като този от Centro: 7 дни, 3 продукта. Преди всичко Матео иска да види двата клона заедно, клетка до клетка. Добави във файла си:
lista_norte = [
[10, 20, 1],
[10, 25, 0],
[5, 20, 1],
[10, 15, 0],
[15, 30, 1],
[10, 20, 0],
[5, 10, 1],
]
unidades_norte = torch.tensor(lista_norte)
total = unidades + unidades_norte
print(total)
Изход:
tensor([[ 40, 100, 3],
[ 50, 115, 3],
[ 30, 90, 3],
[ 45, 105, 2],
[ 65, 150, 4],
[ 40, 100, 1],
[ 25, 60, 2]])
+ взе клетката понеделник-тетрадки от Centro (30) и я събра с клетката понеделник-тетрадки от Norte (10): 40. И така с всички 21 клетки, всяка със своята двойка. Това се нарича операция елемент по елемент: всяко число участва в операцията с онова, което е на същото място в другия тензор.
Все едно да долепиш два листа на квадратчета един върху друг срещу прозореца: всяко квадратче застава над своята двойка. Затова двата листа трябва да имат една и съща форма.
Провери срещу версията с for. Добави:
total_for = []
for d in range(7):
renglon = []
for p in range(3):
renglon.append(lista_unidades[d][p] + lista_norte[d][p])
total_for.append(renglon)
assert total.shape == (7, 3), "Deberían salir 7 días × 3 productos"
assert total.tolist() == total_for, "El tensor y el for deberían dar lo mismo"
print("Todo cuadra")
Todo cuadra
Както в сесия 5, tolist() връща тензора в списъци на Python, за да го сравниш с for. Първо формата, после стойностите.
Внимание: списъците на Python не правят това. Опитай в конзолата на Python:
print([1, 2, 3] + [10, 20, 30])
print(torch.tensor([1, 2, 3]) + torch.tensor([10, 20, 30]))
print([1, 2, 3] * 2)
print(torch.tensor([1, 2, 3]) * 2)
[1, 2, 3, 10, 20, 30]
tensor([11, 22, 33])
[1, 2, 3, 1, 2, 3]
tensor([2, 4, 6])
При списъците + залепва единия списък след другия, а * 2 го повтаря. При тензорите + събира клетка по клетка, а * 2 умножава всяко число. Ако някой ден резултатът ти излезе двойно по-дълъг, вместо двойно по-голям, значи си работил със списъци.
Матео се загледва в последната колона на Фер: 1, 0, 1, 0, 1, 0, 1. Четири раници за цялата седмица. Най-скъпото в магазина, почти неподвижно, също като в сесия 3. Записва го на лепящо листче. Още не знае за какво.
Стъпка 3 — Автоматичното разтягане: бройки по цени
Сега вече — сметката, която има значение. Бройките са таблица с форма (7, 3); цените — списък с форма (3,). Нямат една и съща форма. Въпреки това добави:
ingresos = unidades * precios
print(ingresos)
print(ingresos.shape)
tensor([[1200, 800, 700],
[1600, 900, 1050],
[1000, 700, 700],
[1400, 900, 700],
[2000, 1200, 1050],
[1200, 800, 350],
[ 800, 500, 350]])
torch.Size([7, 3])
Проработи. Понеделник: 30 тетрадки × $40 = $1,200; 80 химикалки × $10 = $800; 2 раници × $350 = $700. И същото за другите шест дни.
Какво стана? PyTorch видя, че на списъка с цени му липсва измерението на дните, и го разтегна: смята така, сякаш списъкът с цени е написан седем пъти, един под друг, по един за всеки ден.
unidades (7, 3) precios (3,) → estirado a (7, 3)
30 80 2 40 10 350
40 90 3 40 10 350
25 70 2 40 10 350
35 90 2 × 40 10 350
50 120 3 40 10 350
30 80 1 40 10 350
20 50 1 40 10 350
И щом двата листа станаха с еднакъв размер, умножи елемент по елемент, както в стъпка 2.
На английски това се нарича broadcasting; тук ще му казваме автоматично разтягане. Важното: PyTorch не копира цените седем пъти в паметта. Само прави сметката така, сякаш са копирани. При 7 дни няма значение; при 7 милиона покупки точно тази разлика не позволява на компютъра ти да забие.
Като картона с цените, който доня Лупе е залепила зад тезгяха, написан с маркер: има само един и с него се таксуват всички продажби за деня. Никой не го преписва за всеки клиент.
Провери срещу for:
ingresos_for = []
for d in range(7):
renglon = []
for p in range(3):
renglon.append(lista_unidades[d][p] * lista_precios[p])
ingresos_for.append(renglon)
assert ingresos.shape == (7, 3), "Los ingresos deberían ser 7 días × 3 productos"
assert ingresos.tolist() == ingresos_for, "El tensor y el for deberían dar lo mismo"
print("Ingresos correctos")
print(ingresos.sum())
Ingresos correctos
tensor(19900)
Погледни for спокойно: в lista_precios[p] участва само продуктът, не денят. Точно това направи разтягането: използва една и съща цена за всички дни. А сумата за седмицата, $19,900, излиза със sum, който вече познаваш от сесия 4.
$19,900 в Centro. Сметката е проверена два пъти: с тензори и с for. Сега Матео има инструмент, на който може да разчита. Но преди да го използва с Norte, иска да знае къде се чупи.
Стъпка 4 — Правилото: кога може да се разтяга?
PyTorch не разтяга каквото и да е. Следва едно правило, същото, което използва NumPy (библиотеката, от която PyTorch е взел начина си на работа), и което следва и TensorFlow. Ето го:
Формите се сравняват отдясно наляво. Два размера са съвместими, ако са равни или ако единият от тях е 1.
И една допълнителна част: ако на една форма ѝ липсват измерения отляво, PyTorch слага там единици (1) и тези единици се разтягат.
Приложи го за бройки по цени. Напиши формите, подравнени вдясно, както когато събираш на ръка в началното училище:
unidades 7 3
precios 3
↑ ↑
│ └─ 3 contra 3: iguales, compatibles
└───── 7 contra nada: a precios le falta, se le pone 1 y se estira a 7
resultado 7 3
Сега един случай, който не работи. Доня Лупе започна да продава моливи за оцветяване по $25 и ги добави в ценоразписа си, но още не ги брои в таблицата с бройките.
— Ами децата ги искат — оправдава се тя. — Вече са на картона. В преброяването още ги няма.
Добави това, изпълни го и разчети грешката:
precios_con_colores = torch.tensor([40, 10, 350, 25]) # agregó los colores
print(unidades * precios_con_colores)
Traceback (most recent call last):
...
RuntimeError: The size of tensor a (3) must match the size of tensor b (4) at non-singleton dimension 1
Разчети я дума по дума, както в сесия 5:
- The size of tensor a (3): размерът на тензор a, който е отляво на
*,unidades, е 3… - must match the size of tensor b (4): …и трябва да съвпада с размера на тензор b,
precios_con_colores, който е 4… - at non-singleton dimension 1: …в измерение 1, което не е с размер 1. (Singleton означава „от един-единствен елемент“. Ако някой от двата беше 1, можеше да се разтегне; тъй като никой не е 1, нищо не може да се направи.)
С правилото:
unidades 7 3
precios_con_colores 4
↑
3 contra 4: ni iguales ni alguno es 1 → error
Грешката ти казва нещо вярно за бизнеса ти: имаш четири цени и три колони с бройки. На коя колона се пада цената на моливите? PyTorch не гадае, и добре че е така. Изтрий тези два реда, за да продължиш.
Стъпка 5 — Скалар с всички: ДДС
Най-простия случай на разтягане вече го използваш, без да знаеш. Доня Лупе иска да види приходите си с включен ДДС от 16 % (в Мексико се нарича IVA), „за да не ми се кара счетоводителят през януари“. Тук приеми, че цените в ценоразписа ѝ са без ДДС; на истински тезгях цената, която вижда клиентът, трябва вече да включва данъците (Федерален закон за защита на потребителите на Мексико, член 7 bis). Добави:
con_iva = ingresos * 1.16
print(con_iva)
print(con_iva.shape, con_iva.dtype)
print(ingresos.sum(), con_iva.sum())
tensor([[1392., 928., 812.],
[1856., 1044., 1218.],
[1160., 812., 812.],
[1624., 1044., 812.],
[2320., 1392., 1218.],
[1392., 928., 406.],
[ 928., 580., 406.]])
torch.Size([7, 3]) torch.float32
tensor(19900) tensor(23084.)
1.16 е скалар, едно-единствено число; в сесия 1 видя, че той няма измерения и формата му е (). По правилото му липсват всички измерения, така че му се слагат единици и се разтяга до (7, 3). Затова умножава всичките 21 клетки. Седмицата с ДДС: $23,084.
Две неща, които да забележиш в изхода:
- Числата сега имат точка (
1392.), аdtypeсе смени наfloat32. Цели числа, умножени по число с десетична част, дават десетични числа. - Формата не се промени. Един скалар никога не променя формата на онова, което умножава.
Добави проверката му:
assert con_iva.shape == ingresos.shape, "El IVA no cambia la forma"
assert con_iva[0][0] == 1392, "Lunes, cuadernos: 1,200 × 1.16 = 1,392"
print("IVA correcto")
IVA correcto
Стъпка 6 — @: умножи и събери наведнъж
— А колко продадохме в петък? — пита доня Лупе. — Петъкът винаги е добрият ден.
Иска прихода за всеки ден. Вече би могъл да го направиш на две стъпки: да умножиш бройките по цените и после да събереш всеки ред със sum(dim=1). Тази комбинация, „умножи двойките и събери“, е толкова често срещана, че си има собствен символ: @. Нарича се матрично умножение (на английски matrix multiplication; в PyTorch съществува и като torch.matmul).
Добави:
por_dia = unidades @ precios
print(por_dia)
print(por_dia.shape)
tensor([2700, 3550, 2400, 3000, 4250, 2350, 1650])
torch.Size([7])
Седем суми, по една за ден, с една-единствена операция. Направи го на ръка за понеделник и ще видиш какво направи @:
lunes: 30 80 2
precios: 40 10 350
30 × 40 + 80 × 10 + 2 × 350
1200 + 800 + 700 = 2700
@ взема един ред от таблицата отляво и списъка отдясно, умножава двойките и събира всичко. Един ред — едно число. Седем реда — седем числа. Затова формата мина от (7, 3) на (7,): измерението на продуктите се „изразходва“ в сумата.
Точно това прави касиерката с касовата бележка: бройки по цена, ред по ред, и накрая сумата. @ прави бележката за всеки ден. А петъкът, добрият ден, наистина беше най-високият: $4,250.
Правилото за формите при @, с таблици и списъци като тези от тази сесия: последният размер отляво трябва да е равен на първия отдясно и този размер изчезва:
unidades @ precios
(7, 3) (3,)
└──┬──┘
3 y 3: iguales, se consumen
resultado: (7,)
Сега го провери по три начина. Това е най-важната самопроверка в сесията:
# 1) Con for, como lo harías sin tensores
por_dia_for = []
for d in range(7):
suma = 0
for p in range(3):
suma = suma + lista_unidades[d][p] * lista_precios[p]
por_dia_for.append(suma)
# 2) Multiplicar y luego sumar por renglón
por_dia_dos_pasos = (unidades * precios).sum(dim=1)
assert por_dia.shape == (7,), "Debería salir un número por día"
assert por_dia.tolist() == por_dia_for, "El @ y el for deberían dar lo mismo"
assert por_dia.tolist() == por_dia_dos_pasos.tolist(), "El @ y el sum(dim=1) deberían dar lo mismo"
print("Los tres caminos dan lo mismo")
Los tres caminos dan lo mismo
Погледни for: вътре има suma = suma + …. Това събиране е разликата спрямо for от стъпка 3, който само умножаваше. * умножава; @ умножава и събира.
Редът има значение. Опитай наобратно:
print(precios @ unidades)
Traceback (most recent call last):
...
RuntimeError: mat1 and mat2 shapes cannot be multiplied (1x3 and 7x3)
mat1 and mat2 shapes cannot be multiplied: формите на матрица 1 и матрица 2 не могат да се умножат. PyTorch третира цените като таблица 1 × 3 и я сложи срещу таблица 7 × 3: последният размер отляво (3) не е равен на първия отдясно (7). При @ онзи, който дава редовете, стои отляво.
Стъпка 7 — Най-опасният капан: * и @ с квадратни таблици
Часът е 9:15 вечерта и Матео е на път да направи грешката, която почти всеки прави веднъж.
Досега всеки път, когато използваше грешния символ, PyTorch се оплакваше. С квадратни таблици — не.
Доня Лупе иска да сравни два клона по два продукта: бройките тетрадки и химикалки в Centro и в Norte. В Norte продава малко по-скъпо, така че има таблица с цени за всеки клон. Онова, което иска, е приходът за всяка клетка: бройки по цена, всяка със своята двойка. Това е елемент по елемент, *. Но си представи, че някой напише @ по навик:
import torch
# Renglones: Centro y Norte. Columnas: cuadernos y plumas.
piezas = torch.tensor([[30, 80],
[10, 20]])
precio = torch.tensor([[40, 10], # en el Centro
[45, 12]]) # en Norte, un poco más caro
print(piezas * precio)
print(piezas @ precio)
tensor([[1200, 800],
[ 450, 240]])
tensor([[4800, 1260],
[1300, 340]])
Нито едното не хвърли грешка. И двете дадоха таблица 2 × 2. Но само първото е онова, което искаше доня Лупе: 30 тетрадки × $40 = $1,200. Второто казва, че в Centro са влезли $4,800 от тетрадки, четири пъти повече от реалното. Откъде дойде това? При две таблици @ взема ред отляво и колона отдясно и прави „умножи и събери“ като на касовата бележка: редът на Centro (30, 80) срещу колоната с цените на тетрадките (40, 45) дава 30 × 40 + 80 × 45 = 4,800. Смесил е бройките химикалки от Centro с цената на тетрадките в Norte. От бизнес гледна точка това няма никакъв смисъл.
Защо нямаше грешка? Защото при квадратните таблици двете правила се изпълняват едновременно: (2, 2) и (2, 2) са еднакви, така че * работи; и последният размер отляво (2) е равен на първия отдясно (2), така че и @ работи.
Това е най-опасният капан, защото грешка, която хвърля изключение, се оправя за пет минути; грешка, която не хвърля, стига чак до отчета за директора. Или до бюрото на господин Ортега. Матео си представя как отива в банката на 23-ти с таблица, която изглежда перфектно, а е грешна. По гърба му полазват тръпки.
Защитата е тази от цялата поредица: сравни срещу for, макар и само за един ред:
lista_piezas = [[30, 80], [10, 20]]
lista_precio = [[40, 10], [45, 12]]
ingreso_for = []
for s in range(2):
renglon = []
for p in range(2):
renglon.append(lista_piezas[s][p] * lista_precio[s][p])
ingreso_for.append(renglon)
print(ingreso_for)
[[1200, 800], [450, 240]]
Съвпада с *, не с @. А за да избереш символа, преди да пишеш, стига един-единствен въпрос:
| Ако искаш… | Използвай | Какво става с формата |
|---|---|---|
| Всяко число със своята двойка (цена × бройки от същата клетка) | * |
Остава същата (или тази от разтягането) |
| Да умножиш двойките и да ги събереш (сумата на касовата бележка) | @ |
Измерението, което се сумира, изчезва |
А в TensorFlow? Разтягането следва същото правило и дава същия резултат. @ е по-строг: с таблица и списък хвърля грешка (different ndims, различен брой измерения) и за този случай се използва друга функция, tf.linalg.matvec. Същите идеи, различни маниери.
👉 Ако идваш от бизнес администрацията: вече си правил това в електронната си таблица. Когато напишеш =B2*$F$1 и плъзнеш формулата надолу, фиксираната цена от $F$1 се използва във всички редове: това е разтягането. А @ е функцията SUMPRODUCT на Excel: умножава двойки и събира. Капанът от стъпка 7 също има своя версия в Excel: формула, която не дава #VALUE!, но сумира грешната колона. Затова, във всеки отчет, проверявай един ред на ръка, преди да го представиш.
10:40 вечерта — Онова, което не излиза
Матео прави с Norte същото, което направи с Centro: бройките на Фер по същите цени, а после сумата. Един ред. (Тази сметка я правиш ти в упражнение 23; няма да ти издавам числото предварително.)
Сравнява я с имейла на Ортега. Прави я отново. Сравнява я срещу for. Същият резултат.
— Лельо. Онова, което Norte е продал за цялата седмица, не стига дори до половината от таксуваното от терминала.
— Може почти всички да са платили с карта.
— Дори всички да бяха платили с карта. Дори никой да не беше оставил нито едно песо в брой. Терминалът е таксувал $16,850, а от Norte не е излязла стока за толкова пари.
Доня Лупе оставя монетите.
— Тогава какво са таксували?
— Нищо. Плащания, зад които няма стока.
Отвън минава камион и ролетката вибрира.
— Фер — казва доня Лупе бавно. — Тя е, която таксува в Norte.
Матео вече го беше помислил. Затова, докато for се изпълняваше, отвори имейла на книжарницата. Онзи, който никой не проверява. Онзи, за който доня Лупе казва, че „е само реклами“. Потърси „Norte“. Потърси „Фернанда“.
Ето го. Вторник, 22 септември, 6:12 вечерта. Тема: Липси: терминалът не съвпада с инвентара.
„Доня Лупе, направих отчета за седмицата и терминалът показва повече плащания от излязлото по инвентара ми. Преброих два пъти. Според терминала би трябвало да ми липсват бройки от рафта, а не ми липсват. Мисля, че нещо не е наред с новия терминал. Да съобщя ли на банката, или на Вас? Фер.“
Без отговор. Неотворен. От септември.
— Направила е същата сметка като мен — казва Матео. — Бройки по цени срещу таксуваното. С калкулатор. Преди два месеца и половина.
Доня Лупе сваля очилата си и си разтрива очите.
— А аз си мислех, че е тя.
— Мислехме със съмнения, лельо. Числата казват друго. Ако Фер крадеше, нямаше да ти пише, за да те предупреди, че не излиза.
— Утре ще ѝ се обадя. — Пауза. — И ще ѝ занеса сладки хлебчета кончас. От шоколадовите, те са ѝ любимите.
Тази вечер Матео научава онова, което никой assert нямаше да го научи: решава се с данни, не със съмнения.
Упражнения към сесия 6
Решенията са в края на тази сесия. Първо опитай сам; assert ти казва дали си улучил.
Упражнение 22 — Предвиди, преди да изпълниш.
а) За всяка двойка напиши на хартия дали могат да се разтегнат и, ако да, каква форма излиза. Използвай правилото от стъпка 4: подравни вдясно и сравни. После го изпълни:
torch.ones(...) създава тензор, пълен с единици, с формата, която посочиш. Тук го използваме само за изпробване на форми: стойностите нямат значение.
print((torch.ones(7, 3) * torch.ones(3)).shape)
print((torch.ones(2, 7, 3) * torch.ones(7, 1)).shape)
print((torch.ones(7, 1) * torch.ones(1, 3)).shape)
print((torch.ones(7, 3) * torch.tensor(2)).shape)
print((torch.ones(2, 7, 3) * torch.ones(2)).shape)
Ето как изглежда една решена: (7, 3) и (3,) → подравнявам: 3 с 3, равни; на списъка му липсва 7, разтяга се → излиза (7, 3).
б) С тази таблица изчисли на ръка, на хартия, m * m и m @ m. После го изпълни и сравни.
m = torch.tensor([[2, 1],
[0, 3]])
print(m * m)
print(m @ m)
Подсказка за @: клетката горе вдясно излиза от горния ред, (2, 1), срещу дясната колона, (1, 3): 2 × 1 + 1 × 3.
Упражнение 23 — Приходите на Norte. Попълни празните места. Използвай unidades_norte от стъпка 2 и същите precios.
ingresos_norte = ____ * ____
ingresos_norte_for = []
for d in range(7):
renglon = []
for p in range(3):
renglon.append(____)
ingresos_norte_for.append(renglon)
assert ingresos_norte.shape == (7, 3), "Norte también es 7 días × 3 productos"
assert ingresos_norte.tolist() == ingresos_norte_for, "El tensor y el for deberían dar lo mismo"
con_iva = ____
print(ingresos_norte.sum(), con_iva.sum())
print("Ejercicio 23 superado")
Преди да го изпълниш, предвиди: колко е влязло от раници във вторник в Norte? Погледни таблицата с бройките.
Упражнение 24 — Buen Fin. За Buen Fin (мексиканският сезон на намаленията) доня Лупе намалява тетрадката с $5, химикалката не я пипа, а раницата намалява с $50. Приеми, че продава същите бройки като тази седмица.
- Създай тензор
descuentoс трите намаления (като отрицателни числа) и го събери сprecios, за да получишprecios_bf. - С
@изчислиpor_dia_bf, прихода за всеки ден с цените от Buen Fin. - Напиши два
assert: този за формата и един, който сравнява твоя@с версията с*иsum(dim=1). - Изчисли колко е пропуснала да спечели всеки ден заради намаленията и колко за цялата седмица.
Предизвикателство: парите, които е пропуснала да спечели всеки ден, могат да се получат с един-единствен @, без да използваш por_dia_bf. С кой?
Упражнение 25 — Напиши правилото сам. Без PyTorch, само с Python. Напиши функция se_pueden_estirar(forma_a, forma_b), която получава два кортежа и връща True, ако могат да се разтегнат, за да участват заедно в операция, и False, ако не могат. Трябва да минава тези assert:
assert se_pueden_estirar((7, 3), (3,)) == True
assert se_pueden_estirar((7, 3), (7,)) == False
assert se_pueden_estirar((2, 7, 3), (7, 1)) == True
assert se_pueden_estirar((7, 3), (4,)) == False
assert se_pueden_estirar((7, 1), (1, 3)) == True
assert se_pueden_estirar((7, 3), ()) == True
print("Ejercicio 25 superado")
Подсказки: forma[-1] е последният размер на кортеж, а forma[-2] — предпоследният; така обхождаш отдясно наляво. Достатъчно е да сравниш толкова размера, колкото има по-късата форма.
Чести грешки (и как да ги забележиш)
| Грешка | Как изглежда | Как да я забележиш | Какво да направиш |
|---|---|---|---|
| Списък с цени с един продукт в повече (или в по-малко) | RuntimeError: The size of tensor a (3) must match the size of tensor b (4) at non-singleton dimension 1 |
Двете числа в скобите са различни и никое не е 1 | Отпечатай .shape на двата, преди операцията; поправи данните, не сметката |
Деление на таблица (7, 3) на сума за всеки ден (7,) |
RuntimeError: The size of tensor a (3) must match the size of tensor b (7) at non-singleton dimension 1 |
Правилото подравнява вдясно: сложи 7-те дни под 3-те продукта | Дай му форма на колона: por_dia.reshape(7, 1) |
Използваш @, където искаше *, с квадратни таблици |
Няма грешка. Излиза таблица с очаквания размер, с числа, които не излизат | Изчисли една клетка на ръка или сравни срещу for |
Запитай се: искам ли двойки (*), или събрани двойки (@)? |
Разменяш страните на @ |
RuntimeError: mat1 and mat2 shapes cannot be multiplied (1x3 and 7x3) |
Последният размер отляво не е равен на първия отдясно | Онзи, който дава редовете, стои отляво: unidades @ precios. Завъртането с .T не помага тук: списък (3,) си остава (3,) |
Смесваш цели и десетични числа в @ |
RuntimeError: addmv input tensors must have the same dtype, but got Float, Long, and Float |
Провери .dtype на двата |
Преобразувай този с целите числа с .float() |
| Очакваш списък на Python да работи като тензор | [1, 2, 3] * 2 дава [1, 2, 3, 1, 2, 3] |
Резултатът излиза по-дълъг, а не по-голям | Първо го преобразувай с torch.tensor(...) |
Преговор за следващата сесия
Сесия 7 започва с тези три въпроса. Отговори на тях, без да поглеждаш урока:
- Кога две форми могат да се „разтегнат“, за да участват заедно в операция?
- Каква е разликата между
*и@? - Каква форма излиза от таблица
(7, 3) @вектор(3,)?
🔁 Следващата седмица: Матео вече може да докаже, че тези плащания не са дошли от книжарницата. Онова, което не знае, е откъде са дошли. За целта ще построи детектор на измами, който се учи от данните: даваш му хиляди измислени покупки със скрито правило и моделът го открива сам, само с @ и автоматично разтягане, двете операции от днес.
Плащанията не идваха от книжарницата. Идваха отнякъде другаде. А банката виждаше само терминала.
Решения (не ги гледай, преди да опиташ)
Преговор за начало.
- Когато общият брой числа е същият преди и след: форма
(12,)се побира в(4, 3), защото 12 = 4 × 3, но не и в(5, 3). - Че PyTorch изчислява този размер вместо теб, с числата, които остават. Може да има само един
-1във всекиreshape. - Защото
reshapeпренарежда числата в реда, в който са записани, ред по ред; завъртането на таблицата, при което редовете стават колони, е.T.
Упражнение 22.
а) Реален изход:
torch.Size([7, 3])
torch.Size([2, 7, 3])
torch.Size([7, 3])
torch.Size([7, 3])
Traceback (most recent call last):
...
RuntimeError: The size of tensor a (3) must match the size of tensor b (2) at non-singleton dimension 2
(7, 3)и(3,): 3 с 3; на десния му се слага 1 и се разтяга до 7 →(7, 3).(2, 7, 3)и(7, 1): 3 с 1, разтяга се; 7 със 7; на десния му липсва 2 →(2, 7, 3).(7, 1)и(1, 3): и двата се разтягат →(7, 3).(7, 3)и скалар(): на скалара му липсват всички, разтяга се →(7, 3).(2, 7, 3)и(2,): 2 се подравнява срещу 3 отдясно, а не срещу 2 отляво. 3 срещу 2: грешка. Това е подвеждащото: 2 „прилича“ на клоновете, но правилото сравнява отдясно. Забележи, че грешката казва dimension 2: във форма с три измерения измерение 2 е последното.
б)
tensor([[4, 1],
[0, 9]])
tensor([[4, 5],
[0, 9]])
m * m: всяко число на квадрат. m @ m: горе вляво, 2 × 2 + 1 × 0 = 4; горе вдясно, 2 × 1 + 1 × 3 = 5; долу вляво, 0 × 2 + 3 × 0 = 0; долу вдясно, 0 × 1 + 3 × 3 = 9. Виж колко е коварно: двете таблици съвпадат в три от четирите клетки. Ако проверяваше само горната лява, щеше да си мислиш, че дават едно и също.
Упражнение 23. Празните места са unidades_norte и precios; lista_norte[d][p] * lista_precios[p] и ingresos_norte * 1.16. Изходът:
tensor(5400) tensor(6264.)
Ejercicio 23 superado
Раниците във вторник в Norte: 0 бройки, така че $0. Ако отпечаташ ingresos_norte, вторникът е [400, 250, 0].
Упражнение 24.
descuento = torch.tensor([-5, 0, -50])
precios_bf = precios + descuento
por_dia_bf = unidades @ precios_bf
print(precios_bf)
print(por_dia_bf)
assert por_dia_bf.shape == (7,), "Debería salir un número por día"
assert por_dia_bf.tolist() == (unidades * precios_bf).sum(dim=1).tolist(), "El @ y el sum(dim=1) deberían dar lo mismo"
dejo_de_ganar = unidades @ precios - por_dia_bf
print(dejo_de_ganar)
assert dejo_de_ganar.tolist() == (-(unidades @ descuento)).tolist()
print(dejo_de_ganar.sum())
print("Ejercicio 24 superado")
tensor([ 35, 10, 300])
tensor([2450, 3200, 2175, 2725, 3850, 2150, 1500])
tensor([250, 350, 225, 275, 400, 200, 150])
tensor(1850)
Ejercicio 24 superado
precios + descuento е елемент по елемент: два списъка с форма (3,). За седмицата е пропуснала да спечели $1,850. Предизвикателството: unidades @ descuento дава пропуснатото всеки ден със знак минус, защото намаленията са отрицателни; - отпред го прави положително. Точно това проверява последният assert.
Упражнение 25. Едно възможно решение:
def se_pueden_estirar(forma_a, forma_b):
cuantos = min(len(forma_a), len(forma_b))
for i in range(1, cuantos + 1):
a = forma_a[-i]
b = forma_b[-i]
if a != b and a != 1 and b != 1:
return False
return True
С assert-ите от условието отдолу изходът е:
Ejercicio 25 superado
Обхожда отдясно наляво с отрицателни индекси: -1, -2… Сравнява само толкова размера, колкото има по-късата форма, защото на другата ѝ липсват измерения, а те винаги могат да се разтегнат. Ако намери двойка, която не е равна и в която никой не е 1, нищо не може да се направи.
Какво следва
В сесия 7 събираш всичко досега: тензор с покупки, @ срещу таблица с тегла и разтягане, за да добавиш корекция, повторено, докато моделът се научи. Ако искаш да избързаш, направи отново стъпка 6 на ръка с вторника: ако ти излезе $3,550, без да поглеждаш, готов си.
Пиша от онова, което виждаме всеки ден в Hábil, където изграждаме системи за банки, застрахователи и компании в Мексико. Ако в работата ти или в семейния ти бизнес се сблъскаш с подобен проблем, нека поговорим: habil.mx
Предпочитате имейл? Пишете ни на hola@habil.mx