Съдържание на курса
Тензори от нулата (5): промяна на формата, без да се променят данните
От Dorian Chávez · основател и главен изпълнителен директор на Hábil ·
Обновено на 21 октомври 2026 г.
Дванайсет месеца в един ред, четири тримесечия или две полугодия: същите числа, подредени по друг начин, и как се разчита грешката, която излиза, когато поискаш невъзможна форма.
Тензори от нулата · урок B, сесия 5 от 8
Сценарий: героите и историята са измислени. Техническите и историческите данни и реалните цифри са с посочен източник.
Понеделник, 30 ноември, 9:15 вечерта. Кухнята на доня Лупе.
Тенджерата с боба още беше топла на котлона. Върху мушамата на масата имаше зелена папка, дебела, вързана с ластик.
— Фер все още не отговаря — каза доня Лупе. — Една седмица, Матео. Една седмица.
— Може да е болна, лельо.
— Може. — Не звучеше убедена.
Тя му подаде папката. Вътре беше тетрадката на счетоводителя: колко е продал Centro всеки месец от миналата година. Дванайсет ръкописни реда, със син химикал.
— Счетоводителят ги иска по тримесечия. А господин Ортега — по полугодия, за „своя преглед“. Всеки ги иска в своята кутийка.
— Това са същите дванайсет числа.
— Ами кажи го на тях.
Матео ги въведе в лаптопа. Дванайсет числа в един ред. Не трябваше да променя нито едно. Само да ги подреди различно. И нещо не му даваше мира: ако видеше една здрава година, подредена по тримесечия, щеше да знае какво да търси в Norte. Кой месец трябва да е силен. Кой не.
— Кой месец продаваш най-много?
— Август, момчето ми. Връщането на училище. Майките чакат на опашка чак до улицата. Почти всички плащат с карта.
Матео написа август на лепящо листче и го залепи на ръба на екрана. Още не знаеше защо.
На телефона на доня Лупе светна известие. Имейл от банката. Тя го отвори, прочете го с очилата на върха на носа и му го подаде, без да каже нищо.
„Уведомяваме Ви, че първите необичайни плащания, регистрирани на терминала във Вашия клон Norte, са от втората половина на август.“
Листчето още беше там. Жълто. Август.
С тази сесия започва вторият урок, Тензорите в действие. Досега Матео се научи да гледа: да създава тензор, да изважда части от него и да го обобщава. Така откри, че Centro продава около четири пъти повече от Norte: $18,500 срещу $4,600 за седмицата. Сега тензорите започват да работят и всеки инструмент го доближава с още една крачка до онова, което се е случило в Norte: променяш формата на тензор, без да пипаш данните му, правиш сметки с цели тензори без нито един for, обучаваш модел, който открива измами, и виждаш къде се появяват те в банка, застраховател и магазин. До крайния срок на банката остават 23 дни.
| Сесия | Какво виждаш | Упражнения |
|---|---|---|
| 5 — Промяна на формата, без да се променят данните | reshape и flatten и как се разчита грешка във формата |
Упражнения 18 до 21 |
| 6 — Операции с цели тензори | Сметки с целия тензор наведнъж, без for |
Упражнения 22 до 25 |
| 7 — Детектор на измами, който се учи от данните | Обучение на малък модел с тензори | Упражнения 26 до 29 |
| 8 — Тензори в банката, застрахователя и магазина | Разчитане на реален случай и определяне на формата на тензора му | Упражнения 30 до 33, самооценка и преговор на урок B |
Както в урок A, всяка сесия е около 2 часа: един за четене и един за упражнения.
В предишната сесия сумира по лист със sum(dim=) и го сравни с for. Днес не променяш нито едно число: променяш как са подредени. Също като Матео онази вечер, с отворената тетрадка на счетоводителя до лаптопа.
Преговор за начало
Отговори, без да поглеждаш сесия 4. Отговорите са в решенията, в края.
- Ако сумираш
papeleriaсdim=1, каква форма остава и какво означава тя? - Защо
meanсе проваля с цели числа и как го поправяш? - Как проверяваш, че сумата ти с тензори е вярна?
Кодът на тази сесия е в публичното хранилище на курса, в папката
sesion-05: github.com/HabilMX/curso-tensores. Първо го напиши сам, защото така се учи; хранилището е за сравнение, когато нещо не ти излезе по същия начин. Всяка програма идва с изхода, който трябва да получиш.
Цели на тази сесия
До всяка цел е упражнението, което я доказва.
| # | В края можеш… | Доказва го |
|---|---|---|
| 1 | Да кажеш, без да изпълняваш нищо, дали една нова форма (със или без -1) е валидна за даден тензор, и да изчислиш липсващото измерение, когато има -1 |
Упражнение 18 |
| 2 | Да преподредиш тензор с reshape, като използваш -1, за да изчисли PyTorch един от размерите, и да го провериш срещу for |
Упражнения 19 и 20 |
| 3 | Да разчетеш дума по дума грешката за форма на PyTorch и да поправиш кода, който я предизвиква | Упражнение 20 |
| 4 | Да различиш преподреждането на таблица от завъртането ѝ и да хванеш с assert случая, в който някой ги е объркал |
Упражнение 21 |
| 5 | Да сплескаш тензор с flatten и да възстановиш формата му, без да загубиш стойности |
Упражнение 19, подточка в) |
Какво трябва да знаеш предварително: сесии 1 до 4: да създаваш тензор, да разчиташ неговия shape, да индексираш, да изрязваш срезове и да сумираш със sum(dim=); а в Python — да използваш for, range, списъци, append, tolist() и assert (по-долу, в стъпка 3, има напомняне). Какво ти трябва: средата от сесия 2 с PyTorch и файлът papeleria.py от сесия 4 (тензорът papeleria с форма (2, 7, 3)). Днешните файлове отиват в същата папка.
Обхват за днес: новото решение, което упражняваш, е дали reshape запазва реда, който ти трябва. flatten() е случаят, в който всичко отива в един ред. .T се появява само за контраст с reshape: напиши този ред и остави assert-ите да го проверят. Стъпка 10 и карето за TensorFlow са за четене, за да видиш къде се използва това в изкуствения интелект: изпълни foto.py, ако искаш, но нито едно упражнение не се нуждае от него и не е нужно да инсталираш TensorFlow.
Защо е важно
— А как ги подреждаш, без да ти се объркат? — попита доня Лупе.
Матео посочи кората с яйца до котлона.
Една дузина се побира в кора от 2 реда по 6 или в такава от 3 по 4: същите 12 яйца, друга кора. А ако ти поискат да ги сложиш в кора от 5 по 3, знаеш, че не може: тя има 15 гнезда, а ти имаш 12 яйца.
— Това и аз го знам — каза тя. — А ако остане празно гнездо, някой е изял яйце.
При тензорите това се случва постоянно: данните идват с една форма, а следващата стъпка ги иска с друга. Доня Лупе има продажби по месеци, а счетоводителят ѝ ги иска по тримесечия. Една снимка идва като решетка, а моделът я иска като един ред числа. Не се измисля и не се губи нито едно число: сменя се само кората. Това е reshape („промени формата“ на английски).
И една практическа причина: едни от грешките, които ще виждаш най-често с тензорите, са грешките във формата: да поискаш кора, която не пасва на яйцата ти. Днес ще се научиш да ги разчиташ, без да се плашиш. И ще се запознаеш с другата грешка, опасната: онази, която не спира програмата. Кората изглежда същата, пасва, никой не се оплаква… а яйцата вече не са онези, които бяха.
Случаят: продажбите за годината на доня Лупе
Това са дванайсетте реда от тетрадката на счетоводителя: колко е продал Centro всеки месец от миналата година, в песо. Една здрава година, без сътресения. Август е силният ѝ месец, заради връщането на училище, а декември също, заради раниците за подарък. Матео ще я използва като шаблон: така изглежда една нормална година в книжарница на доня Лупе.
Стъпка 1 — Дванайсетте месеца в един ред
Създай файл cambiar_forma.py:
import torch
# ene feb mar abr may jun jul ago sep oct nov dic
meses = [60000, 55000, 60000, 50000, 65000, 50000, 70000, 100000, 70000, 60000, 55000, 90000]
ventas = torch.tensor(meses)
print(ventas.shape)
print(ventas.sum())
Изпълни го с python3 cambiar_forma.py:
torch.Size([12])
tensor(785000)
Нищо ново: 12 числа, които дават сбор $785,000. Тази сума е твоят свидетел: ще промениш формата много пъти и тя никога не трябва да помръдне.
Стъпка 2 — Четири тримесечия по три месеца
Счетоводителят иска продажбите по тримесечия: от януари до март, от април до юни и така нататък. Искаш таблица от 4 реда (тримесечия) по 3 колони (месеци).
Добави в края на файла:
trimestres = ventas.reshape(4, 3)
print(trimestres)
print(trimestres.shape)
print(trimestres.sum())
Новите редове в изхода (над тях продължават да излизат предишните):
tensor([[ 60000, 55000, 60000],
[ 50000, 65000, 50000],
[ 70000, 100000, 70000],
[ 60000, 55000, 90000]])
torch.Size([4, 3])
tensor(785000)
Четири реда, три колони и същата сума. reshape(4, 3) означава „подреди ги в 4 по 3“.
Как решава PyTorch кое число къде отива? Чете първоначалния ред отляво надясно, като книга, и пълни новата таблица ред по ред, точно както подреждаш яйца в кора: първо първия ред, после втория.
Тъй като месеците идваха подред, всеки ред стана точно едно тримесечие. Не е късмет: редът на четене съвпада с онова, което искаш. Запомни тази идея; в стъпка 9 ще я видиш да се проваля.
Сега вече можеш да използваш всичко от урок A върху новата таблица. Добави:
print(trimestres.sum(dim=1)) # total de cada trimestre
print(trimestres[3, 2]) # cuarto trimestre, tercer mes: diciembre
print(ventas.shape) # ¿se descompuso la original?
Нов изход:
tensor([175000, 165000, 240000, 205000])
tensor(90000)
torch.Size([12])
- Третото тримесечие (от юли до септември) е най-силното: $240,000, заради връщането на училище.
trimestres[3, 2]е декември, $90,000: ред 3 (четвърто тримесечие), колона 2 (трети месец).ventasпродължава да е с форма(12,).reshapeне променя формата на първоначалния тензор: дава ти нов, с новата форма. Затова го запазваш в променлива, катоtrimestres. Внимание: не винаги е отделно копие; често двата споделят едни и същи числа отвътре. Днес това няма значение, защото не променяш нито едно.
Доня Лупе надникна през рамото му.
— А как знаеш, че машината не си ги е измислила?
— Не знам, лельо. Затова ще го проверя.
— Това ми харесва. И на компютъра не бива да се вярва от първия път.
Стъпка 3 — Проверка срещу for
Не вярвай на PyTorch просто така. Изчисли сумите с for и сравни.
Напомняне за Python, което ще използваш: range(4) дава 0, 1, 2 и 3; lista.append(x) добавя x в края на списък; tensor.tolist() превръща тензор в обикновен списък, за да го сравниш с друг списък.
И един инструмент, който ще използваш в цялата поредица. assert е автоматична проверка: ако условието е вярно, не отпечатва нищо и програмата продължава; ако е невярно, програмата спира и показва съобщението в кавичките. Тук работи като твой контрол за грешки: не е нужно да вярваш на резултата, можеш да го провериш. Ако искаш да видиш как изглежда един провал, опитай това в конзола на Python:
# Prueba opcional para reconocer un fallo:
assert 2 == 3, "2 no es igual a 3"
# Sale: AssertionError: 2 no es igual a 3
Сега вече добави:
a_mano = []
for t in range(4):
total = 0
for m in range(3):
total = total + meses[t * 3 + m]
a_mano.append(total)
assert trimestres.shape == (4, 3), "Deberían ser 4 trimestres × 3 meses"
assert trimestres.sum(dim=1).tolist() == a_mano, "Los totales por trimestre no cuadran con el for"
print("Trimestres comprobados")
Нов изход:
Trimestres comprobados
Обърни внимание на meses[t * 3 + m]: това е сметката, която reshape прави отвътре. Декември (тримесечие 3, месец 2) е на позиция 3 × 3 + 2 = 11 в първоначалния ред. reshape ти спестява тази сметка. И както винаги: първо assert за формата, после този за стойностите.
Стъпка 4 — Две полугодия, и дори три измерения
Господин Ортега иска от доня Лупе продажбите ѝ по полугодия: две половини от по шест месеца. Добави:
semestres = ventas.reshape(2, 6)
print(semestres)
print(semestres.sum(dim=1))
anio = ventas.reshape(2, 2, 3) # semestre × trimestre × mes
print(anio)
print(anio[1, 0, 2])
Нов изход:
tensor([[ 60000, 55000, 60000, 50000, 65000, 50000],
[ 70000, 100000, 70000, 60000, 55000, 90000]])
tensor([340000, 445000])
tensor([[[ 60000, 55000, 60000],
[ 50000, 65000, 50000]],
[[ 70000, 100000, 70000],
[ 60000, 55000, 90000]]])
tensor(70000)
Първо полугодие — $340,000; второ — $445,000. Заедно дават обичайните $785,000.
Формата (2, 2, 3) превръща годината в куб: книгата с листове от сесия 1, по един лист за всяко полугодие и във всеки от тях таблица тримесечия по месеци. anio[1, 0, 2] е второто полугодие, неговото първо тримесечие, неговият трети месец: септември, $70,000. Дванайсет числа, четири форми и нищо не се загуби.
Матео се загледа във второто полугодие. Там беше най-голямото число за цялата година, само, по средата на своето тримесечие: август, $100,000. В една здрава година август е месецът, в който най-много хора плащат с карта.
Погледна листчето. Погледна имейла от банката: втората половина на август.
Ако някой искаше да скрие фалшиви плащания, кой месец би избрал? Месеца, в който никой не обръща внимание, защото всички плащат.
Стъпка 5 — Правилото: умножи формата
reshapeс положителни размери е валиден, когато новата форма, умножена, дава същия общ брой числа като първоначалната. За-1има отделно правило, в стъпка 6.
Провери го, преди да изпълниш:
| Форма, която искаш | Умножена | Става ли за 12 месеца? |
|---|---|---|
(4, 3) |
4 × 3 = 12 | Да |
(2, 2, 3) |
2 × 2 × 3 = 12 | Да |
(5, 3) |
5 × 3 = 15 | Не: липсват ти 3 яйца |
(5, 2) |
5 × 2 = 10 | Не: остават ти 2 яйца в повече |
reshape никога не допълва с нули и не изхвърля излишното. Ако не пасва, отказва.
Стъпка 6 — -1, който смята сам
Понякога знаеш колко искаш по едно измерение и те мързи да делиш за другото. Слагаш -1 на мястото, което не искаш да смяташ. Добави:
print(ventas.reshape(-1, 3).shape)
print(ventas.reshape(6, -1).shape)
print(ventas.reshape(2, -1, 3).shape)
print(ventas.reshape(-1).shape)
Нов изход:
torch.Size([4, 3])
torch.Size([6, 2])
torch.Size([2, 2, 3])
torch.Size([12])
-1 означава „ти изчисли колко трябва да е тук, за да пасне“. При reshape(-1, 3) PyTorch мисли: „12 числа в колони по 3: това са 4 реда“. При reshape(2, -1, 3): „2 × нещо × 3 = 12; нещото е 2“.
А reshape(-1), самичко, означава „сложи всичко в едно-единствено измерение“, каквато и форма да има: списък от числа, като (12,) от началото.
Правилото с -1: умножи размерите, които си написал; това произведение трябва да дели точно общия брой числа (без нищо да остава). -1 е равно на общия брой, разделен на това произведение. При reshape(2, -1, 3): 2 × 3 = 6, а 12 ÷ 6 = 2.
- Може да има само един. С
ventas.reshape(-1, -1)излизаRuntimeError: only one dimension can be inferred(„може да се изведе само едно измерение“). - Не прави чудеса. При
reshape(-1, 5)няма число, което по 5 да дава 12, и хвърля грешка.-1смята; не измисля яйца. - Това е единственото отрицателно число, което важи. С
ventas.reshape(-2, -6)излизаRuntimeError: invalid shape dimension -2 at index 0 of shape [-2, -6], макар -2 × -6 да е 12.
Стъпка 7 — Грешката във формата, разчетена дума по дума
Нека предизвикаме грешката нарочно. Един съученик искаше „5 групи по 3 месеца“. Добави в края на файла:
quintos = ventas.reshape(5, 3)
Изпълни го (съкратих пътя с ...; на твоя компютър е различен):
Traceback (most recent call last):
File ".../cambiar_forma.py", line 42, in <module>
quintos = ventas.reshape(5, 3)
^^^^^^^^^^^^^^^^^^^^
RuntimeError: shape '[5, 3]' is invalid for input of size 12
Съобщението за грешка се чете отдолу нагоре: последният ред казва какво се е случило; горните — къде. Последният, дума по дума:
| Част | Какво означава |
|---|---|
RuntimeError |
„Грешка по време на изпълнение“: кодът е написан правилно, но се е провалил, докато се изпълнява. |
shape '[5, 3]' |
„Формата [5, 3]“. Това е формата, която ти поиска. |
is invalid |
„не е валидна“. |
for input of size 12 |
„за вход с размер 12“. Това е, което ти имаш: 12 числа. |
Заедно: „Формата 5 × 3 не става за нещо, което има 12 числа.“ 5 × 3 = 15, а 15 не е 12. Грешката не ти се кара: дава ти какво си поискал и какво имаш.
Горните редове казват къде: в твоя файл, ред 42 (на теб може да ти излезе друго число, ако си оставил повече или по-малко празни редове), и копират виновния ред с ^^^^ под частта, която е хвърлила грешка.
Ако си искал групи по 3 месеца, поправката е reshape(4, 3) или reshape(-1, 3). Ако наистина ти трябваха 5 групи, проблемът не е в кода: 12 не се разделя на 5 равни части. Изтрий този ред, преди да продължиш.
Рецептата за всяка грешка във формата: потърси в последния ред двата размера (какво си поискал и какво имаш), умножи и се запитай какво всъщност си искал.
— Колко възпитано — каза доня Лупе, когато Матео ѝ прочете грешката. — Казва ти какво си поискал и какво имаш. Да беше и банката такава.
Матео се засмя. После спря да се смее. Какво си поискал и какво имаш. Какво е продадено и какво е таксувано. Ако някой сложеше тези две числа заедно, едно до друго…
Записа го на друго листче. Това щеше да почака.
Стъпка 8 — Сплескване на книжарницата с flatten
Да се върнем към книжарницата (2, 7, 3). Понякога ти трябват 42-те ѝ числа в един-единствен ред, за да ги запазиш или да ги дадеш на модел. Това е сплескване, като да разглобиш картонена кутия: същият картон, без обем.
В PyTorch се казва flatten („сплескай“). Преди да създадеш файла, провери дали papeleria.py може да се импортира. Отвори терминала в папката, където си го запазил, и изпълни:
python3 -c "from papeleria import papeleria; print(papeleria.shape)"
Трябва да отпечата torch.Size([2, 7, 3]). Ако каже ModuleNotFoundError, сложи papeleria.py в същата тази папка и отвори терминала там. Ако се импортира, но казва, че не намира papeleria, върни се към файла от сесия 4 и провери дали това е запазената променлива.
Сега вече създай aplanar.py:
from papeleria import papeleria
print(papeleria.shape)
plana = papeleria.flatten()
print(plana.shape)
print(plana[:6]) # los primeros 6 números
print(plana[21:24]) # del 22 al 24
print(plana.sum(), papeleria.sum())
de_vuelta = plana.reshape(2, 7, 3)
assert de_vuelta.shape == (2, 7, 3), "Debería regresar a 2 sucursales × 7 días × 3 productos"
assert de_vuelta.tolist() == papeleria.tolist(), "Al regresar debería quedar idéntica"
print("Ida y vuelta sin perder nada")
Запази файла и го изпълни от същата папка с python3 aplanar.py. Ако се появи грешка, не продължавай: първо сравни последния ред с очаквания изход.
Изход:
torch.Size([2, 7, 3])
torch.Size([42])
tensor([1200, 800, 500, 1500, 900, 700])
tensor([300, 200, 100])
tensor(23100) tensor(23100)
Ida y vuelta sin perder nada
- 2 × 7 × 3 = 42 числа, които дават същите $23,100.
- Редът на четене е обичайният, от външната кутия към вътрешната: целият лист Centro, ден по ден и продукт по продукт, и после целият Norte. Първите шест са Centro понеделник и вторник. Първите 21 са от Centro (7 × 3), така че позиции 21 до 23 са Norte понеделник: 300, 200, 100.
- Може да се върнеш обратно, стига да помниш каква форма е имал.
flatten() прави същото като reshape(-1), но името му казва какво прави.
Стъпка 9 — Капанът: reshape не е завъртане на таблицата
Най-важното от сесията, защото тук грешката не спира програмата.
papeleria.sum(dim=1) е таблицата за седмицата: 2 клона × 3 продукта. Доня Лупе я иска наобратно за един отчет: продуктите в редовете, клоновете в колоните, 3 × 2. reshape(3, 2)?
Създай girar.py:
from papeleria import papeleria
semana = papeleria.sum(dim=1) # sucursal × producto
print(semana)
print(semana.shape)
mal = semana.reshape(3, 2)
print(mal)
Изпълни го с python3 girar.py. Изход:
tensor([[9000, 5800, 3700],
[2350, 1400, 850]])
torch.Size([2, 3])
tensor([[9000, 5800],
[3700, 2350],
[1400, 850]])
PyTorch не се оплака: 3 × 2 = 2 × 3. Но го прочети като отчет:
- Редът за тетрадките трябваше да носи Centro 9,000 и Norte 2,350. Носи 9,000 и 5,800: тетрадки и химикалки от Centro.
- Този за химикалките трябваше да носи 5,800 и 1,400. Носи 3,700 и 2,350: раници от Centro и тетрадки от Norte.
Каша. reshape направи единственото, което умее: да чете подред (9000, 5800, 3700, 2350, 1400, 850) и да пълни редове по 2. reshape сменя кората; не знае кое яйце на кого е.
Онова, което искаше, е да завъртиш таблицата: редовете да станат колони. В PyTorch това е .T (от транспониране, математическото име). Пише се semana.T, без скоби: не се извиква като функция, а описва завъртяната таблица. Добави:
bien = semana.T
print(bien)
print(bien.shape)
Нов изход:
tensor([[9000, 2350],
[5800, 1400],
[3700, 850]])
torch.Size([3, 2])
Сега вече е вярно: тетрадки 9,000 и 2,350; химикалки 5,800 и 1,400; раници 3,700 и 850. Същата форма (3, 2) като кашата: същият размер, различно съдържание. Затова assert за формата никога не идва сам. Повече за .T засега не ти трябва: то е онова, което наистина завърта.
А защо с тримесечията проработи? Защото там не завърташе нищо: режеше един ред на парчета по 3, в същия ред. Въпросът преди всеки reshape: искам ли да нарежа на парчета, в същия ред на четене? Ако да — reshape. Ако искаш редовете да станат колони, reshape ти дава каша, без да те предупреди.
Същият размер, различно съдържание. Матео го каза на глас.
— Кое? — попита доня Лупе, която вече миеше тенджерата.
— Нещо, което отвън изглежда същото, може отвътре да е друго. Като кашата: минава проверката на формата и никой не забелязва.
Доня Лупе спря водата. Избърса си ръцете в престилката, бавно.
— Терминала в Norte го смениха през август.
Матео вдигна поглед.
— Кой?
— Един техник. Фер ми прати снимка на телефона: „Дойде човекът от банката, новият терминал е сложен“. Досущ като другия. А на ръба на снимката — един господин със синя тениска, с гръб към камерата. Помислих, че го е пратила банката.
— Ти ли го повика?
— Не.
— А Фер?
Доня Лупе не отговори. На масата листчето все така казваше август.
Стъпка 10 — Защо изкуственият интелект постоянно сменя форми (за четене; изпълнението е по избор)
Снимката се сплесква. Черно-бяла снимка от 28 × 28 точки е таблица, а много прости модели очакват по един ред числа за всеки пример.
Пакетът (batch) се преподрежда. Моделите виждат снимките на пакети, например по 32: куб (32, 28, 28), който моделът иска като 32 реда, по един за снимка.
Ако искаш да го видиш да работи, създай foto.py и го изпълни с python3 foto.py. torch.zeros създава тензор с формата, която поискаш, пълен с нули, за да не пишеш хиляди числа:
import torch
foto = torch.zeros(28, 28) # una foto de 28 × 28 puntos, toda negra
print(foto.shape, foto.numel())
print(foto.flatten().shape)
lote = torch.zeros(32, 28, 28) # 32 fotos
print(lote.reshape(32, -1).shape)
print(lote.flatten().shape)
Изход:
torch.Size([28, 28]) 784
torch.Size([784])
torch.Size([32, 784])
torch.Size([25088])
- Сплесканата снимка: 784 числа в един ред (28 × 28).
lote.reshape(32, -1): 32 реда, по един за снимка;-1изчислява 784.- Внимавай с
lote.flatten(): сплесква всичко, включително разделението между снимките. Остават 25,088 числа (32 × 784) и формата вече не казва къде започва всяка снимка: знаеш го само ако помниш, че бяха 32 по 784. При пакети искашreshape(32, -1), което пази това разделение във формата.
Каре: същото в TensorFlow. Само за сравнение; не го изпълнявай и не инсталирай TensorFlow за тази сесия: всички цели и упражнения се решават с PyTorch. Идеята е същата:
tf.reshape(ventas, (4, 3))вместоventas.reshape(4, 3), със същия-1. Съобщението за грешка съдържа:Input to reshape is a tensor with 12 values, but the requested shape has 15. TensorFlow умножава вместо теб (5 × 3 = 15); PyTorch ти дава формата, а ти умножаваш.
👉 Ако идваш от бизнес администрацията: това прави счетоводителят на доня Лупе в края на всяко тримесечие и това правиш ти, когато превръщаш месечен отчет в тримесечен: същите числа, групирани различно, а сумата за годината не помръдва. Това е твоята контролна проверка, като assert от стъпка 3. А капанът от стъпка 9 живее в Excel: ако преподредиш таблица ръчно, вместо да използваш „Специално поставяне → Транспониране“, числата попадат в чужди клетки без никакво предупреждение. След като преподредиш, провери една стойност, която знаеш наизуст.
Упражнения към сесия 5
Решенията са в края. Първо опитай сам. assert-ите проверяват частите с код; за предвижданията и обясненията сравни отговора си с очаквания изход и с решението в края.
Упражнение 18 — Предвиди, преди да изпълниш. С 12-те месеца на ventas напиши на хартия дали всяка форма е валидна и каква крайна форма би дала:
(3, 4) (6, 2) (5, 2) (2, -1) (-1, 5) (2, 3, 2) (12, 1)
После ги провери една по една в конзолата на Python, с вече създадена ventas. Ако в cambiar_forma.py още имаш реда quintos = ventas.reshape(5, 3) от стъпка 7, изтрий го (иначе файлът спира на тази грешка) и отвори конзолата с:
python3 -i cambiar_forma.py
Когато се появи >>>, пиши по една форма. Ако >>> не се появи, файлът е спрял на друга грешка: оправи я, преди да правиш упражнението. За да излезеш, напиши exit(). Изглежда така (не копирай >>>: слага го Python, ти пишеш само онова, което следва):
>>> ventas.reshape(3, 4).shape
Ето как изглежда една решена: (4, 3) → 4 × 3 = 12 → валидна → torch.Size([4, 3]). При невалидните провери дали грешката казва двата размера, които си очаквал.
Упражнение 19 — Дългата таблица. Много таблици пазят данните в дълъг формат: един ред за клон и ден, една колона за продукт. Подточки а) и б): попълни празните места и отговори на въпроса по-долу.
from papeleria import papeleria
larga = papeleria.reshape(____, 3)
assert larga.shape == (14, 3), "Deberían ser 14 renglones (sucursal y día) × 3 productos"
a_mano = []
for s in range(2):
for d in range(7):
a_mano.append(papeleria[s, d].____())
assert larga.tolist() == a_mano, "La tabla larga no coincide con la armada a mano"
print("Ejercicio 19 superado")
След това, без да изпълняваш нищо, кажи кой ред на larga заема Norte сряда. Провери с print(larga[9]), препиши трите числа, които излязоха, и добави:
assert larga[9].tolist() == papeleria[1, 2].tolist(), "Ese renglón debería ser Norte miércoles"
Подточка в) — Сплескване. Без да поглеждаш стъпка 8, попълни празните места, за да сплескаш книжарницата и да я върнеш към формата ѝ:
plana = papeleria.____()
de_vuelta = plana.____(2, 7, 3)
assert plana.shape == (42,), "Deberían quedar 42 números en una fila"
assert de_vuelta.tolist() == papeleria.tolist(), "La ida y vuelta debe conservar cada dato"
print("Ejercicio 19c superado")
Упражнение 20 — Разчети грешката и я поправи. Дневните продажби на Centro през февруари с 28 дни, който е започнал в понеделник: точно 4 седмици. Един съученик искаше да ги подреди по седмици:
import torch
dias_febrero = [
2500, 3100, 2200, 2800, 3900, 2400, 1600,
2600, 3000, 2300, 2700, 4000, 2500, 1500,
2400, 3200, 2100, 2900, 3800, 2300, 1700,
2700, 3300, 2400, 3000, 4200, 2600, 1800,
]
febrero = torch.tensor(dias_febrero)
por_semana = febrero.reshape(7, 5)
а) Изпълни го, препиши последния ред на грешката и обясни тези четири части: RuntimeError, shape '[7, 5]', is invalid и input of size 28. След това обясни защо 7 × 5 не може да побере 28 дни.
б) Поправи го, така че por_semana да стане 4 седмици × 7 дни (от понеделник до неделя). Напиши assert за формата и сравни сумите по седмици с for върху списъка dias_febrero, както в стъпка 3 (ден d от седмица s е в dias_febrero[s * 7 + d]).
в) Друг съученик казва: „аз го поправих с reshape(7, 4), което също дава 28“. Не хвърля грешка. Защо е грешно за онова, което искаха? За да го видиш, добави:
mal = febrero.reshape(7, 4)
print(mal[0])
Сравни този изход със седемте дни от понеделник до неделя на първата седмица.
Упражнение 21 — Хвани капана. Покрий стъпка 9, преди да опиташ, и напиши програма, която:
- Изчислява
semana = papeleria.sum(dim=1). - Построява правилно таблицата продукти × клонове (3 × 2).
- Има
assertза формата и още един, който проверява, че редът за тетрадките носи[9000, 2350]. - Проверява с два
for, чеgirada[p, s]е равно наsemana[s, p]за всички.
След това, за да изпробваш капана, замени само реда със завъртяната таблица (в решението е girada = semana.T) с:
girada = semana.reshape(3, 2)
Не променяй assert-ите, нито двата for. Изпълни го. Кой assert хвърля грешка? Защо PyTorch не те предупреди по-рано?
Чести грешки (и как да ги забележиш)
| Грешка | Как изглежда | Как да я забележиш | Какво да направиш |
|---|---|---|---|
| Искаш форма, която не пасва | RuntimeError: shape '[5, 3]' is invalid for input of size 12 |
Програмата хвърля грешка на реда с reshape |
Умножи формата, която си поискал, и я сравни с размера, който казва грешката |
Мислиш, че reshape е променил оригинала |
След ventas.reshape(4, 3) ventas.shape си остава (12,) |
ventas[3, 2] хвърля грешка |
Запази резултата: trimestres = ventas.reshape(4, 3) |
Използваш два -1 |
RuntimeError: only one dimension can be inferred |
Хвърля грешка, макар общият брой да пасва | Остави един-единствен -1 и напиши останалите размери |
Използваш reshape, за да завъртиш таблица |
Числа в чужди редове, без никаква грешка | Минава assert за формата и се проваля този за стойностите |
За завъртане — .T; reshape само реже в реда на четене |
Преговор за следващата сесия
Сесия 6 започва с тези три въпроса. Отговори на тях, без да поглеждаш урока:
- Кога един
reshapeе валиден? - Какво означава
-1вreshape? - Защо
reshapeна таблица не е същото като завъртането ѝ?
🔁 Следващата седмица: работиш с цели тензори, без for. Ще умножиш цени по количества за всички клонове и всички дни наведнъж и ще се запознаеш с капан, който, като днешния, не дава грешка. Матео най-накрая ще сложи две числа едно до друго: какво е продадено и какво е таксувано.
Същата вечер, преди да си тръгне, той писа на господин Ортега: Изпращала ли е банката техник да смени терминала в клон Norte през август?
Отговорът дойде в 9:03 на следващата сутрин. Един-единствен ред:
„Банката не е изпращала технически персонал във Вашия клон Norte от началото на годината.“
Матео увеличи снимката, която Фер беше пратила на леля му през август. Новият терминал, лъскав. А на ръба, отрязан от кадъра, мъж с гръб към камерата.
Кой беше мъжът със синята тениска?
Решения (не ги гледай, преди да опиташ)
Преговор за начало.
- Остава
(2, 3): дните изчезват, защото са сумирани. Всяко число е сумата за седмицата на един клон от един продукт (използва я днес в стъпка 9). - Защото
meanприема само тензори с десетични (или комплексни) числа: с цели числа винаги хвърля грешка, дори средната стойност да излиза точна. Причината за това правило е тази от сесия 4: средната стойност почти никога не е цяло число и PyTorch не отрязва десетичните тайно. Преобразуваш в десетични преди това:papeleria.float().mean(dim=1). - Като изчислиш същото с
for, за който вече знаеш, че работи, и сравниш сassert: първо формата, после стойностите.
Упражнение 18.
| Форма | Умножена | Резултат |
|---|---|---|
(3, 4) |
12 | torch.Size([3, 4]) |
(6, 2) |
12 | torch.Size([6, 2]) |
(5, 2) |
10 | RuntimeError: shape '[5, 2]' is invalid for input of size 12 |
(2, -1) |
2 × 6 | torch.Size([2, 6]) |
(-1, 5) |
никое число × 5 не дава 12 | RuntimeError: shape '[-1, 5]' is invalid for input of size 12 |
(2, 3, 2) |
12 | torch.Size([2, 3, 2]) |
(12, 1) |
12 | torch.Size([12, 1]) |
Последната е любопитна: 12 реда с по едно-единствено число, една колона. За разлика от (12,), която има едно-единствено измерение, (12, 1) има две. Изглежда безполезна, но често ще я срещаш в примери с изкуствен интелект.
Упражнение 19. Празните места са -1 (или 14) и tolist. Този блок не е за копиране: показва само двата реда с вече попълнени празни места, а ... са редовете от файла ти, които не се променят:
larga = papeleria.reshape(-1, 3)
...
a_mano.append(papeleria[s, d].tolist())
Norte сряда е ред 9: първите 7 реда (0 до 6) са Centro, Norte понеделник е 7, вторник — 8, а сряда — 9. print(larga[9]) дава:
tensor([350, 200, 100])
Сметката е същата като в стъпка 3: клон × 7 + ден = 1 × 7 + 2 = 9.
Подточка в): празните места са flatten и reshape:
plana = papeleria.flatten()
de_vuelta = plana.reshape(2, 7, 3)
Изход: Ejercicio 19c superado.
Упражнение 20.
а) Последният ред е:
RuntimeError: shape '[7, 5]' is invalid for input of size 28
RuntimeError е грешка по време на изпълнение: кодът е написан правилно, но се е провалил, докато се изпълнява. shape '[7, 5]' е формата, която съученикът е поискал. is invalid казва, че не става. input of size 28 е това, което има: дните на февруари. 7 × 5 = 35, а 35 не е 28, така че 28 дни не се побират в 7 реда по 5.
б) Смени последния ред с por_semana = febrero.reshape(4, 7) (или reshape(-1, 7): „по 7, ти изчисли колко седмици“). for е този от стъпка 3, с 4 и 7 вместо 4 и 3, върху списъка dias_febrero:
por_semana = febrero.reshape(4, 7)
a_mano = []
for s in range(4):
total = 0
for d in range(7):
total = total + dias_febrero[s * 7 + d]
a_mano.append(total)
assert por_semana.shape == (4, 7), "Deberían ser 4 semanas × 7 días"
assert por_semana.sum(dim=1).tolist() == a_mano, "Los totales por semana no cuadran con el for"
print(por_semana.sum(dim=1))
print("Ejercicio 20 superado")
Изход:
tensor([18500, 18600, 18400, 20000])
Ejercicio 20 superado
в) reshape(7, 4) реже на парчета по 4 дни. Първият му ред е tensor([2500, 3100, 2200, 2800]): от понеделник до четвъртък на първата седмица. Още една каша, която не хвърля грешка (следващият ред започва от петъка). Това, че общият брой пасва, казва само, че reshape е възможен, а не че е този, който си искал.
Упражнение 21.
from papeleria import papeleria
semana = papeleria.sum(dim=1) # sucursal × producto
girada = semana.T # producto × sucursal
assert girada.shape == (3, 2), "Deberían ser 3 productos × 2 sucursales"
assert girada[0].tolist() == [9000, 2350], "El renglón de cuadernos debería traer Centro y Norte"
for p in range(3):
for s in range(2):
assert girada[p, s] == semana[s, p], "Girar es cambiar renglones por columnas"
print("Ejercicio 21 superado")
Изход:
Ejercicio 21 superado
С semana.reshape(3, 2) минава assert за формата и се проваля този за тетрадките: първият ред носи [9000, 5800]. PyTorch не предупреди, защото за него няма грешка: поиска 6 числа в 3 × 2 и това ти даде. Дали данните имат смисъл, проверяваш само ти, с assert за стойностите.
Какво следва
Източници на техническите данни: официалната документация на PyTorch за reshape, flatten и mean, Tensor.numel, torch.zeros и атрибута Tensor.T (в страницата за тензорите), и на TensorFlow за tf.reshape.
В сесия 6 спираш да смяташ число по число. Ако искаш да избързаш, напиши с for как би умножил по 2 всяко от 42-те числа на книжарницата. Следващата седмица ще го направиш на един ред.
Пиша от онова, което виждаме всеки ден в Hábil, където изграждаме системи за банки, застрахователи и компании в Мексико. Ако в работата ти или в семейния ти бизнес се сблъскаш с подобен проблем, нека поговорим: habil.mx
Предпочитате имейл? Пишете ни на hola@habil.mx