Índice do curso
Tensores do zero (6): operar tensores inteiros
Por Dorian Chávez · fundador e CEO da Hábil ·
Atualizada em 21 de outubro de 2026
Multiplicar uma semana inteira de vendas pelos preços em uma única linha, a regra que decide quando o PyTorch «estica» um tensor, e a armadilha de * versus @ que não dá nenhum erro.
Tensores do zero · lição B, sessão 6 de 8
Cenário: os personagens e a história são inventados. Os dados técnicos, históricos e os números reais trazem a fonte.
Segunda-feira, 7 de dezembro, 19h40. Papelaria Herrera, filial Centro.
A porta de enrolar está meio abaixada. Lá dentro há cheiro de papel novo e de café requentado. Dona Lupe conta moedas sobre o balcão. Mateo está com o notebook aberto entre um rolo de papel laminado e uma caixa de canetas.
Faltam dezesseis dias para o dia 23.
Desde a semana passada, Mateo sabe duas coisas. Que o Norte ia bem até agosto. E que em agosto um homem de camiseta azul «trocou a maquininha» sem que ninguém o tivesse chamado. O que ele não sabe é o que uma coisa tem a ver com a outra.
— Já não quero pesos, tia — diz ele. — Quero peças.
— Peças de quê?
— De tudo. Quantos cadernos, quantas canetas, quantas mochilas. Cada dia. Do Centro e do Norte.
Dona Lupe olha para ele por cima dos óculos.
— Para isso não precisa de computador, filho. Precisa de alguém que conte.
— Já contaram. A Fer me mandou a do Norte de manhã.
Há duas semanas, ninguém na família queria dizer o nome da Fer em voz alta. O Centro vendia umas quatro vezes o que o Norte vendia, e ela era a encarregada. Naquela tarde, ela não atendeu o telefone. Mas hoje mandou a contagem completa, linha por linha, sem que ninguém precisasse pedir duas vezes.
O celular de dona Lupe vibra. Um e-mail do banco. Do doutor Ortega.
«Prezada senhora Herrera: conforme solicitado, envio o total cobrado pela maquininha da filial Norte na semana indicada. Lembro que o prazo final é 23 de dezembro. Fico à disposição.»
Embaixo, um único número: $16,850.
Dona Lupe passa o celular para Mateo sem dizer nada.
— Isso é muito ou pouco?
— Depende de quanto a gente vendeu.
E aí está a questão. Para saber quanto venderam, é preciso multiplicar cada peça pelo preço dela. Vinte e uma células por filial. Com for, Mateo já sabe fazer. Mas esta noite ele quer uma conta que pegue a tabela inteira de uma vez só.
Porque, se as peças vezes os preços derem menos do que a maquininha cobrou, alguém cobrou algo que nunca saiu da papelaria.
Na sessão anterior, mudou-se a forma de um tensor sem tocar nos números: 12 meses em uma fila viraram 4 trimestres de 3 meses. Hoje os números param de mudar de lugar e começam as contas com eles, com o tensor inteiro de uma vez.
Revisão para abrir
A ideia é responder sem olhar a sessão anterior. As respostas estão nas soluções, no final.
- Quando um
reshapeé válido? - O que significa o
-1emreshape? - Por que fazer
reshapede uma tabela não é o mesmo que girá-la?
O código desta sessão está no repositório público do curso, na pasta
sesion-06: github.com/HabilMX/curso-tensores. Vale escrevê-lo primeiro, porque é assim que se aprende; o repositório serve para comparar quando algo não sair igual. Cada programa vem com a saída que deve produzir.
Objetivos desta sessão
Ao terminar, estas quatro coisas serão possíveis. Ao lado de cada uma está o exercício que a demonstra.
| # | Ao terminar, é possível… | Demonstrado em |
|---|---|---|
| 1 | Operar dois tensores da mesma forma número por número e conferir o resultado contra um for |
Exercício 23 |
| 2 | Prever, antes de executar, se duas formas podem ser «esticadas» para operar juntas e que forma sai | Exercícios 22 e 25 |
| 3 | Calcular com @ um total por linha e conferi-lo de duas maneiras: com um for e com sum(dim=1) |
Exercício 24 |
| 4 | Reconhecer quando * e @ dão resultados diferentes sem dar erro |
Exercício 22 |
O que é preciso saber antes: as sessões 1 a 5: criar um tensor, ler o shape e o dtype dele, extrair pedaços com índices, somar com sum(dim=) e mudar a forma com reshape. O que é preciso ter: o ambiente da sessão 2, com o PyTorch instalado, e um arquivo novo, operar.py, ao qual se vai acrescentando código passo a passo. Quando um passo pedir para provocar um erro, acrescente a linha, execute, interprete o erro e apague a linha para continuar.
Por que importa
Mateo poderia fazer a conta como nas sessões passadas: um for dentro de outro for, célula por célula. Funciona. Mas não é assim que um modelo de inteligência artificial trabalha. Nem o sistema que suspendeu a maquininha do Norte num domingo à meia-noite.
Boa parte do trabalho de um modelo vai em duas operações: multiplicar tensores número por número e multiplicar matrizes. Quando um banco revisa uma compra, o modelo pega a lista de números da compra e a multiplica por uma tabela de «pesos», que são números que o modelo aprendeu. Isso é uma multiplicação de matrizes. E ele não faz isso uma vez: faz milhões de vezes por segundo. Em algum lugar do banco, uma conta assim foi a que marcou as cobranças do Norte como «incomuns».
É por isso que existem as placas de vídeo para IA. Uma analogia que ajuda: o computador comum é um punhado de chefs muito habilidosos; a placa de vídeo são milhares de ajudantes que só sabem picar, mas picam todos ao mesmo tempo. Multiplicar matrizes é «picar»: muitíssimas multiplicações e somas que podem ser repartidas entre muitos ajudantes ao mesmo tempo. A AlexNet, a rede de 2012 com a qual começou a onda atual da IA, segundo o próprio artigo dos autores «leva entre cinco e seis dias para ser treinada em duas GPUs GTX 580 de 3 GB» (Krizhevsky, Sutskever e Hinton, 2012). Duas placas de videogame.
Hoje tudo roda no computador comum, sem placa de vídeo. As duas operações são as mesmas, e na próxima sessão elas vão ser necessárias para treinar um modelo próprio. Esta noite, Mateo precisa delas para uma pergunta mais simples: quanto dinheiro as peças dizem?
O caso: agora dona Lupe conta peças
Até agora, os números de dona Lupe eram pesos. Desta vez Mateo pediu algo diferente: quantas peças o Centro vendeu de cada produto em cada dia. E, à parte, a lista de preços, que ela dita de memória: o caderno a $40, a caneta a $10 e a mochila a $350.
— Não mudaram desde a volta às aulas — diz ela. — E não vão mudar até o papel subir ou eu morrer, o que acontecer primeiro.
Com as peças e os preços, qualquer coisa de dinheiro sai de uma conta. Mateo começa pelo Centro, a filial de que ninguém suspeita: se a conta der certo ali, vai poder confiar nela quando chegar ao Norte.
Passo 1 — As peças e os preços
Escreva isto no começo de operar.py. Primeiro as listas de Python, já conhecidas; depois os tensores, com o que se viu na sessão 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)
Saída:
torch.Size([7, 3]) torch.int64
torch.Size([3]) torch.int64
Leia como na sessão 2: unidades é uma tabela de 7 dias por 3 produtos; precios é uma lista de 3 preços, um por produto. As duas guardam inteiros. Guarde as listas de Python: elas vão servir para conferir com for.
Antes de continuar, uma pergunta para pensar: na segunda-feira foram vendidos 30 cadernos a $40. Quanto dinheiro entrou com cadernos na segunda? Se a resposta foi $1,200, a conta de hoje já é conhecida. A única novidade é fazê-la com os 21 números de uma vez.
Passo 2 — Elemento a elemento: duas tabelas iguais
Agora o arquivo da Fer. Mateo o abre com cuidado: na sessão 2, um arquivo do Norte trazia uma linha torta. Este vem completo. Mesma forma que o do Centro: 7 dias, 3 produtos. Antes de tudo, Mateo quer ver as duas filiais juntas, célula com célula. Acrescente ao arquivo:
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)
Saída:
tensor([[ 40, 100, 3],
[ 50, 115, 3],
[ 30, 90, 3],
[ 45, 105, 2],
[ 65, 150, 4],
[ 40, 100, 1],
[ 25, 60, 2]])
O + pegou a célula segunda-cadernos do Centro (30) e a somou com a célula segunda-cadernos do Norte (10): 40. E assim com as 21 células, cada uma com o par dela. A isso se chama operar elemento a elemento: cada número é operado com o que está no mesmo lugar do outro tensor.
É como sobrepor duas folhas de papel quadriculado contra a janela: cada quadradinho fica em cima do par dele. Por isso as duas folhas precisam ter a mesma forma.
Confira contra a versão com for. Acrescente:
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
Como na sessão 5, tolist() devolve o tensor a listas de Python para compará-lo com o for. Primeiro a forma, depois o valor.
Atenção: as listas de Python não fazem isso. Teste no console de 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])
Com listas, + cola uma lista atrás da outra e * 2 a repete. Com tensores, + soma célula por célula e * 2 multiplica cada número. Se um dia o resultado sair com o dobro do comprimento em vez do dobro do tamanho, eram listas que estavam sendo operadas.
Mateo fica olhando a última coluna da Fer: 1, 0, 1, 0, 1, 0, 1. Quatro mochilas na semana inteira. O mais caro da loja, quase parado, igual à sessão 3. Ele anota em um post-it. Ainda não sabe para quê.
Passo 3 — O esticamento automático: peças vezes preços
Agora sim, a conta que importa. As peças são uma tabela (7, 3); os preços, uma lista (3,). Não têm a mesma forma. Mesmo assim, acrescente isto:
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])
Funcionou. Segunda: 30 cadernos × $40 = $1,200; 80 canetas × $10 = $800; 2 mochilas × $350 = $700. E o mesmo para os outros seis dias.
O que aconteceu? O PyTorch viu que faltava à lista de preços a dimensão dos dias e a esticou: fez de conta que a lista de preços estava escrita sete vezes, uma embaixo da outra, uma por dia.
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
E, já com as duas folhas do mesmo tamanho, multiplicou elemento a elemento, como no passo 2.
Em inglês isso se chama broadcasting; em português vamos chamar de esticamento automático. O importante: o PyTorch não copia os preços sete vezes na memória. Só faz a conta como se estivessem copiados. Com 7 dias tanto faz; com 7 milhões de compras, essa diferença é o que impede o computador de travar.
É como a cartolina de preços que dona Lupe tem colada atrás do balcão, escrita com caneta hidrográfica: há uma só, e ela serve para cobrar todas as vendas do dia. Ninguém a reescreve para cada cliente.
Confira contra o 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)
Olhe o for com calma: em lista_precios[p] só aparece o produto, não o dia. É exatamente o que o esticamento fez: usar o mesmo preço para todos os dias. E o total da semana, $19,900, sai com o sum já conhecido da sessão 4.
$19,900 no Centro. A conta está comprovada duas vezes: com tensores e com for. Agora Mateo tem uma ferramenta em que pode confiar. Mas, antes de usá-la com o Norte, quer saber onde ela quebra.
Passo 4 — A regra: quando dá para esticar?
O PyTorch não estica qualquer coisa. Segue uma regra, a mesma que o NumPy usa (a biblioteca da qual o PyTorch copiou o jeito de trabalhar) e que o TensorFlow também segue. Diz assim:
As formas são comparadas da direita para a esquerda. Dois tamanhos são compatíveis se são iguais ou se um dos dois é 1.
E uma parte extra: se faltam dimensões à esquerda em uma forma, o PyTorch põe uns (1) ali, e esses uns são esticados.
Aplique-a a peças vezes preços. Escreva as formas alinhadas à direita, como na conta de somar armada no primário:
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
Agora um caso que não funciona. Dona Lupe começou a vender lápis de cor a $25 e os acrescentou à lista de preços, mas ainda não os conta na tabela de peças.
— É que as crianças pedem — defende-se ela. — Já estão na cartolina. Na contagem, ainda não.
Acrescente isto, execute e interprete o erro:
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
Leia palavra por palavra, como na sessão 5:
- The size of tensor a (3): o tamanho do tensor a, que é o da esquerda do
*,unidades, é 3… - must match the size of tensor b (4): …e tem que coincidir com o tamanho do tensor b,
precios_con_colores, que é 4… - at non-singleton dimension 1: …na dimensão 1, que não é de tamanho 1. (Singleton quer dizer «de um único elemento». Se um dos dois medisse 1, daria para esticar; como nenhum mede 1, não há o que fazer.)
Com a regra:
unidades 7 3
precios_con_colores 4
↑
3 contra 4: ni iguales ni alguno es 1 → error
O erro está dizendo algo verdadeiro sobre o negócio: há quatro preços e três colunas de peças. A que coluna cabe o preço dos lápis de cor? O PyTorch não adivinha, e ainda bem. Apague essas duas linhas para continuar.
Passo 5 — Um escalar com todos: o IVA
O caso mais simples de esticamento já vinha sendo usado sem que se soubesse. Dona Lupe quer ver as receitas com o IVA de 16 % incluído, «para o contador não brigar comigo em janeiro». Aqui, suponha que os preços da lista dela são antes do IVA; em um balcão de verdade, o preço que o cliente vê já deve trazer os impostos incluídos (Ley Federal de Protección al Consumidor, artigo 7 bis). Acrescente:
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.)
O 1.16 é um escalar, um único número; na sessão 1 se viu que ele não tem dimensões, a forma dele é (). Pela regra, faltam todas as dimensões, então se põem uns e ele é esticado para (7, 3). Por isso multiplica as 21 células. A semana com IVA: $23,084.
Duas coisas para notar na saída:
- Os números agora trazem um ponto (
1392.) e odtypemudou parafloat32. Multiplicar inteiros por um número com decimais dá decimais. - A forma não mudou. Um escalar nunca muda a forma do que multiplica.
Acrescente a conferência:
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
Passo 6 — @: multiplicar e somar de uma vez só
— E quanto a gente vendeu na sexta? — pergunta dona Lupe. — A sexta é sempre a boa.
Ela quer a receita de cada dia. Isso já daria para fazer em dois passos: multiplicar peças por preços e depois somar cada linha com sum(dim=1). Essa combinação, «multiplica pares e soma», é tão comum que tem um símbolo próprio: @. Chama-se produto de matrizes (em inglês, matrix multiplication; no PyTorch também existe como torch.matmul).
Acrescente:
por_dia = unidades @ precios
print(por_dia)
print(por_dia.shape)
tensor([2700, 3550, 2400, 3000, 4250, 2350, 1650])
torch.Size([7])
Sete totais, um por dia, em uma única operação. Faça a conta à mão para a segunda-feira e fica claro o que @ fez:
lunes: 30 80 2
precios: 40 10 350
30 × 40 + 80 × 10 + 2 × 350
1200 + 800 + 700 = 2700
@ pega uma linha da tabela da esquerda e a lista da direita, multiplica os pares e soma tudo. Uma linha, um número. Sete linhas, sete números. Por isso a forma passou de (7, 3) para (7,): a dimensão dos produtos foi «consumida» na soma.
É exatamente o que a operadora de caixa faz com o cupom: peças vezes preço, linha por linha, e no final o total. @ faz o cupom de cada dia. E a sexta, o dia bom, foi o mais alto: $4,250.
A regra das formas para @, com tabelas e listas como as desta sessão: o último tamanho da esquerda tem que ser igual ao primeiro da direita, e esse tamanho desaparece:
unidades @ precios
(7, 3) (3,)
└──┬──┘
3 y 3: iguales, se consumen
resultado: (7,)
Agora confira de três maneiras. Esta é a autocorreção mais importante da sessão:
# 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
Olhe o for: tem suma = suma + … dentro. Essa soma é a diferença em relação ao for do passo 3, que só multiplicava. * multiplica; @ multiplica e soma.
A ordem importa. Teste ao contrário:
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: as formas da matriz 1 e da matriz 2 não podem ser multiplicadas. O PyTorch tratou os preços como uma tabela de 1 × 3 e a pôs contra uma de 7 × 3: o último tamanho da esquerda (3) não é igual ao primeiro da direita (7). Com @, quem traz as linhas vai à esquerda.
Passo 7 — A armadilha mais perigosa: * e @ com tabelas quadradas
São 21h15 e Mateo está prestes a cometer o erro que quase todo mundo comete uma vez.
Até aqui, toda vez que se usou o símbolo errado, o PyTorch reclamou. Com tabelas quadradas, não.
Dona Lupe quer comparar duas filiais em dois produtos: peças de cadernos e canetas, no Centro e no Norte. No Norte ela vende um pouco mais caro, então tem uma tabela de preços por filial. O que ela quer é a receita de cada célula: peças vezes preço, cada uma com o par dela. Isso é elemento a elemento, *. Mas imagine que alguém escreve @ por costume:
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]])
Nenhum dos dois deu erro. Os dois deram uma tabela de 2 × 2. Mas só o primeiro é o que dona Lupe queria: 30 cadernos × $40 = $1,200. O segundo diz que no Centro entraram $4,800 com cadernos, quatro vezes mais que o real. De onde saiu isso? Com duas tabelas, @ pega uma linha da esquerda e uma coluna da direita, e faz o «multiplica e soma» do cupom: a linha do Centro (30, 80) contra a coluna de preços de cadernos (40, 45) dá 30 × 40 + 80 × 45 = 4,800. Misturou as peças de canetas do Centro com o preço de cadernos do Norte. Não faz nenhum sentido para o negócio.
Por que não houve erro? Porque com tabelas quadradas as duas regras se cumprem ao mesmo tempo: (2, 2) e (2, 2) são iguais, então * funciona; e o último tamanho da esquerda (2) é igual ao primeiro da direita (2), então @ também funciona.
É a armadilha mais perigosa porque um erro que quebra se conserta em cinco minutos; um que não quebra chega até o relatório do diretor. Ou até a mesa do doutor Ortega. Mateo se imagina chegando ao banco no dia 23 com uma tabela que parece perfeita e está errada. Sente um frio na espinha.
A defesa é a de toda a série: comparar contra o for, nem que seja de uma linha:
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]]
Coincide com *, não com @. E, para escolher o símbolo antes de escrever, uma única pergunta:
| Se a ideia é… | Use | O que acontece com a forma |
|---|---|---|
| Cada número com o par dele (preço × peças da mesma célula) | * |
Fica igual (ou a do esticamento) |
| Multiplicar pares e somá-los (o total do cupom) | @ |
A dimensão somada desaparece |
E no TensorFlow? O esticamento segue a mesma regra e dá o mesmo resultado. O @ é mais rígido: com uma tabela e uma lista dá erro (different ndims, número diferente de dimensões), e para esse caso se usa outra função, tf.linalg.matvec. Mesmas ideias, modos diferentes.
👉 Para quem é de Administração: isto já se faz na planilha. Ao escrever =B2*$F$1 e arrastar a fórmula para baixo, o preço fixo de $F$1 é usado em todas as linhas: isso é o esticamento. E @ é a função SOMARPRODUTO do Excel: multiplica pares e soma. A armadilha do passo 7 também tem versão no Excel: uma fórmula que não mostra #VALOR!, mas soma a coluna errada. Por isso, em qualquer relatório, vale conferir uma linha à mão antes de apresentá-lo.
22h40 — O que não fecha
Mateo faz com o Norte o mesmo que fez com o Centro: as peças da Fer pelos mesmos preços, e depois a soma. Uma linha. (Essa conta fica para o exercício 23; o número não é adiantado aqui.)
Compara com o e-mail de Ortega. Refaz. Compara contra o for. Mesmo resultado.
— Tia. O que o Norte vendeu na semana inteira não chega nem à metade do que a maquininha cobrou.
— Vai ver quase todo mundo pagou com cartão.
— Mesmo que todo mundo tivesse pagado com cartão. Mesmo que ninguém tivesse deixado um peso em dinheiro. A maquininha cobrou $16,850, e do Norte não saiu mercadoria para tanto dinheiro.
Dona Lupe larga as moedas.
— Então o que cobraram?
— Nada. Cobranças sem mercadoria por trás.
Lá fora passa um ônibus e a porta de enrolar vibra.
— A Fer — diz dona Lupe, devagar. — É ela quem cobra no Norte.
Mateo já tinha pensado nisso. Por isso, enquanto o for rodava, abriu o e-mail da papelaria. O que ninguém olha. O que dona Lupe diz que «é só propaganda». Procurou «Norte». Procurou «Fernanda».
Lá estava. Terça-feira, 22 de setembro, 18h12. Assunto: Faltas: a maquininha não bate com o estoque.
«Dona Lupe: fiz o fechamento da semana e a maquininha marca mais cobranças do que as que aparecem no meu estoque. Contei duas vezes. Pelo que a maquininha diz, deveriam estar faltando peças na prateleira, e não estão. Acho que tem algo errado com a maquininha nova. Aviso o banco ou aviso a senhora? Fer.»
Sem resposta. Sem abrir. Desde setembro.
— Ela fez a mesma conta que eu — diz Mateo. — Peças vezes preços contra o que foi cobrado. Com calculadora. Há dois meses e meio.
Dona Lupe tira os óculos e esfrega os olhos.
— E eu achando que era ela.
— A gente pensava com suspeitas, tia. Os números dizem outra coisa. Se a Fer estivesse roubando, não escreveria para avisar que não bate.
— Amanhã eu ligo para ela. — Uma pausa. — E levo conchas. Das de chocolate, que são as que ela mais gosta.
Naquela noite, Mateo aprende o que nenhum assert ia ensinar: decide-se com dados, não com suspeitas.
Exercícios da sessão 6
As soluções estão no final desta entrega. Tente primeiro; o assert diz se acertou.
Exercício 22 — Preveja antes de executar.
a) Para cada par, escreva no papel se dá para esticar e, se der, que forma sai. Use a regra do passo 4: alinhe à direita e compare. Depois execute:
torch.ones(...) cria um tensor cheio de uns com a forma indicada. Aqui ele só é usado para testar formas: os valores não importam.
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)
Exemplo de como fica um resolvido: (7, 3) e (3,) → alinho: 3 com 3, iguais; falta o 7 na lista, ela é esticada → sai (7, 3).
b) Com esta tabela, calcule à mão, no papel, m * m e m @ m. Depois execute e compare.
m = torch.tensor([[2, 1],
[0, 3]])
print(m * m)
print(m @ m)
Dica para @: a célula de cima à direita sai da linha de cima, (2, 1), contra a coluna da direita, (1, 3): 2 × 1 + 1 × 3.
Exercício 23 — As receitas do Norte. Preencha as lacunas. Use unidades_norte do passo 2 e os mesmos 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")
Antes de executar, preveja: quanto entrou com mochilas na terça no Norte? Olhe a tabela de peças.
Exercício 24 — O Buen Fin. No Buen Fin, dona Lupe baixa $5 no caderno, nada na caneta e $50 na mochila. Suponha que ela vende as mesmas peças desta semana.
- Crie um tensor
descuentocom os três descontos (como números negativos) e some-o apreciospara obterprecios_bf. - Com
@, calculepor_dia_bf, a receita de cada dia com os preços do Buen Fin. - Escreva dois
assert: o da forma e um que compare o@contra a versão com*esum(dim=1). - Calcule quanto ela deixou de ganhar em cada dia por causa dos descontos, e quanto na semana inteira.
Desafio: o dinheiro que ela deixou de ganhar em cada dia pode ser obtido com um único @, sem usar por_dia_bf. Com qual?
Exercício 25 — Escreva a regra. Sem PyTorch, só com Python. Escreva uma função se_pueden_estirar(forma_a, forma_b) que receba duas tuplas e devolva True se elas podem ser esticadas para operar juntas e False se não. Ela tem que passar nestes 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")
Dicas: forma[-1] é o último tamanho de uma tupla e forma[-2], o penúltimo; assim se percorre da direita para a esquerda. Só é preciso comparar tantos tamanhos quantos tiver a forma mais curta.
Erros comuns (e como perceber)
| Erro | Como aparece | Como perceber | O que fazer |
|---|---|---|---|
| Uma lista de preços com um produto a mais (ou a menos) | RuntimeError: The size of tensor a (3) must match the size of tensor b (4) at non-singleton dimension 1 |
Os dois números entre parênteses são diferentes e nenhum é 1 | Imprima o .shape dos dois antes de operar; corrija os dados, não a conta |
Dividir uma tabela (7, 3) por um total por dia (7,) |
RuntimeError: The size of tensor a (3) must match the size of tensor b (7) at non-singleton dimension 1 |
A regra alinha à direita: pôs os 7 dias embaixo dos 3 produtos | Dê a ele forma de coluna: por_dia.reshape(7, 1) |
Usar @ onde se queria * com tabelas quadradas |
Não há erro. Sai uma tabela do tamanho esperado com números que não batem | Calcule uma célula à mão ou compare contra o for |
A pergunta: quero pares (*) ou pares somados (@)? |
Inverter os lados de @ |
RuntimeError: mat1 and mat2 shapes cannot be multiplied (1x3 and 7x3) |
O último tamanho da esquerda não é igual ao primeiro da direita | Quem traz as linhas vai à esquerda: unidades @ precios. Girar com .T não adianta aqui: uma lista (3,) continua sendo (3,) |
Misturar inteiros e decimais em @ |
RuntimeError: addmv input tensors must have the same dtype, but got Float, Long, and Float |
Confira o .dtype dos dois |
Converta o de inteiros com .float() |
| Esperar que uma lista de Python opere como tensor | [1, 2, 3] * 2 dá [1, 2, 3, 1, 2, 3] |
O resultado sai mais comprido, não maior | Converta antes com torch.tensor(...) |
Revisão para a próxima sessão
A sessão 7 começa com estas três perguntas. Responda sem olhar a lição:
- Quando duas formas podem ser «esticadas» para operar juntas?
- Que diferença há entre
*e@? - Que forma sai de uma tabela
(7, 3) @um vetor(3,)?
🔁 Na próxima semana: Mateo já consegue provar que essas cobranças não saíram da papelaria. O que ele não sabe é de onde saíram. Para isso, vai construir um detector de fraude que aprende com os dados: recebe milhares de compras inventadas com uma regra escondida, e o modelo a descobre sozinho, só com @ e esticamento automático, as duas operações de hoje.
As cobranças não vinham da papelaria. Vinham de outro lugar. E o banco só via a maquininha.
Soluções (não olhe antes de tentar)
Revisão para abrir.
- Quando o total de números é igual antes e depois: uma forma
(12,)cabe em(4, 3)porque 12 = 4 × 3, mas não em(5, 3). - Que o PyTorch calcula esse tamanho sozinho, com os números que sobram. Só pode haver um
-1porreshape. - Porque
reshaperearruma os números na ordem em que estão guardados, linha por linha; girar a tabela, fazer as linhas virarem colunas, é.T.
Exercício 22.
a) Saída real:
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)e(3,): 3 com 3; no da direita se põe 1 e ele é esticado para 7 →(7, 3).(2, 7, 3)e(7, 1): 3 com 1, estica; 7 com 7; falta o 2 no da direita →(2, 7, 3).(7, 1)e(1, 3): os dois são esticados →(7, 3).(7, 3)e um escalar(): faltam todas ao escalar, ele é esticado →(7, 3).(2, 7, 3)e(2,): o 2 se alinha com o 3 da direita, não com o 2 da esquerda. 3 contra 2: erro. Este é o que engana: o 2 «se parece» com as filiais, mas a regra compara pela direita. Repare que o erro diz dimension 2: em uma forma de três dimensões, a dimensão 2 é a última.
b)
tensor([[4, 1],
[0, 9]])
tensor([[4, 5],
[0, 9]])
m * m: cada número ao quadrado. m @ m: em cima à esquerda, 2 × 2 + 1 × 0 = 4; em cima à direita, 2 × 1 + 1 × 3 = 5; embaixo à esquerda, 0 × 2 + 3 × 0 = 0; embaixo à direita, 0 × 1 + 3 × 3 = 9. Veja como é traiçoeiro: as duas tabelas coincidem em três de quatro células. Quem conferisse só a de cima à esquerda acharia que davam o mesmo.
Exercício 23. As lacunas são unidades_norte e precios; lista_norte[d][p] * lista_precios[p], e ingresos_norte * 1.16. A saída:
tensor(5400) tensor(6264.)
Ejercicio 23 superado
As mochilas de terça no Norte: 0 peças, então $0. Ao imprimir ingresos_norte, a terça é [400, 250, 0].
Exercício 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 é elemento a elemento: duas listas (3,). Na semana, ela deixou de ganhar $1,850. O desafio: unidades @ descuento dá o que ela deixou de ganhar em cada dia, com sinal negativo, porque os descontos são negativos; o - da frente o torna positivo. É o que o último assert confere.
Exercício 25. Uma solução possível:
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
Com os assert do enunciado embaixo, a saída é:
Ejercicio 25 superado
Percorre da direita para a esquerda com índices negativos: -1, -2… Só compara tantos tamanhos quantos tiver a forma mais curta, porque à outra faltam dimensões, e essas sempre podem ser esticadas. Se encontra um par que não é igual e em que nenhum é 1, não há mais o que fazer.
O que vem a seguir
Na sessão 7 junta-se tudo o que se viu até aqui: um tensor de compras, um @ contra uma tabela de pesos e um esticamento para somar um ajuste, repetido até o modelo aprender. Para adiantar, vale refazer o passo 6 à mão com a terça: quem chegar a $3,550 sem olhar está pronto.
Escrevo a partir do que vemos todos os dias na Hábil, onde construímos sistemas para bancos, seguradoras e empresas no México. Se no trabalho ou no negócio da família aparecer um problema assim, vamos conversar: habil.mx
Prefere e-mail? Escreva para hola@habil.mx