Съдържание на курса

Тензори от нулата (5): промяна на формата, без да се променят данните

От · основател и главен изпълнителен директор на 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. Отговорите са в решенията, в края.

  1. Ако сумираш papeleria с dim=1, каква форма остава и какво означава тя?
  2. Защо mean се проваля с цели числа и как го поправяш?
  3. Как проверяваш, че сумата ти с тензори е вярна?

Кодът на тази сесия е в публичното хранилище на курса, в папката 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, преди да опиташ, и напиши програма, която:

  1. Изчислява semana = papeleria.sum(dim=1).
  2. Построява правилно таблицата продукти × клонове (3 × 2).
  3. Има assert за формата и още един, който проверява, че редът за тетрадките носи [9000, 2350].
  4. Проверява с два 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 започва с тези три въпроса. Отговори на тях, без да поглеждаш урока:

  1. Кога един reshape е валиден?
  2. Какво означава -1 в reshape?
  3. Защо reshape на таблица не е същото като завъртането ѝ?

🔁 Следващата седмица: работиш с цели тензори, без for. Ще умножиш цени по количества за всички клонове и всички дни наведнъж и ще се запознаеш с капан, който, като днешния, не дава грешка. Матео най-накрая ще сложи две числа едно до друго: какво е продадено и какво е таксувано.

Същата вечер, преди да си тръгне, той писа на господин Ортега: Изпращала ли е банката техник да смени терминала в клон Norte през август?

Отговорът дойде в 9:03 на следващата сутрин. Един-единствен ред:

„Банката не е изпращала технически персонал във Вашия клон Norte от началото на годината.“

Матео увеличи снимката, която Фер беше пратила на леля му през август. Новият терминал, лъскав. А на ръба, отрязан от кадъра, мъж с гръб към камерата.

Кой беше мъжът със синята тениска?


Решения (не ги гледай, преди да опиташ)

Преговор за начало.

  1. Остава (2, 3): дните изчезват, защото са сумирани. Всяко число е сумата за седмицата на един клон от един продукт (използва я днес в стъпка 9).
  2. Защото mean приема само тензори с десетични (или комплексни) числа: с цели числа винаги хвърля грешка, дори средната стойност да излиза точна. Причината за това правило е тази от сесия 4: средната стойност почти никога не е цяло число и PyTorch не отрязва десетичните тайно. Преобразуваш в десетични преди това: papeleria.float().mean(dim=1).
  3. Като изчислиш същото с 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