Objetivo
Dominar o NumPy, a biblioteca fundamental de computação numérica em Python. Entender arrays multidimensionais, operações vetorizadas, broadcasting e como o NumPy é a base de todas as bibliotecas de ML e IA.
1. Por que NumPy é Fundamental em IA?
Tudo em Machine Learning e Deep Learning é matemática com números. Matrizes de pixels de imagens, embeddings de palavras, pesos de redes neurais, datasets inteiros — tudo é representado como arrays numéricos.
NumPy é a biblioteca que torna essas operações:
- Rápidas: operações vetorizadas em C, muito mais rápidas que loops Python
- Convenientes: sintaxe concisa para operações complexas de álgebra linear
- Universais: PyTorch, TensorFlow, Pandas, Scikit-learn são todos construídos sobre NumPy
Um tensor do PyTorch é conceitualmente um array NumPy com suporte a GPU e gradientes automáticos. Entender NumPy é entender a base de toda IA moderna.
2. Importando NumPy
A convenção universal é importar NumPy com o alias np:
import numpy as np
print(np.__version__)
3. Criando Arrays
3.1 A partir de listas Python
# Array 1D (vetor)
vetor = np.array([1, 2, 3, 4, 5])
print(vetor)
print(type(vetor)) # <class 'numpy.ndarray'>
print(vetor.dtype) # int64
print(vetor.shape) # (5,)
print(vetor.ndim) # 1
# Array 2D (matriz)
matriz = np.array([
[1, 2, 3],
[4, 5, 6],
[7, 8, 9]
])
print(matriz.shape) # (3, 3) — 3 linhas, 3 colunas
print(matriz.ndim) # 2
# Array 3D (tensor)
tensor = np.array([
[[1, 2], [3, 4]],
[[5, 6], [7, 8]]
])
print(tensor.shape) # (2, 2, 2)
print(tensor.ndim) # 3
3.2 Tipos de dados (dtype)
# Inteiros
inteiros = np.array([1, 2, 3], dtype=np.int32)
print(inteiros.dtype) # int32
# Ponto flutuante — padrão em ML
floats = np.array([1.0, 2.5, 3.14], dtype=np.float64)
print(floats.dtype) # float64
# Float32 — mais comum em deep learning (usa menos memória)
floats32 = np.array([1.0, 2.5, 3.14], dtype=np.float32)
print(floats32.dtype) # float32
# Booleanos
booleanos = np.array([True, False, True])
print(booleanos.dtype) # bool
# Conversão de tipo
x = np.array([1, 2, 3])
y = x.astype(np.float32)
print(y) # [1. 2. 3.]
print(y.dtype) # float32
Em deep learning, float32 é o padrão porque ocupa metade da memória de float64 com precisão suficiente para treinar redes neurais.
3.3 Funções de criação
# zeros: array preenchido com zeros
print(np.zeros(5))
print(np.zeros((3, 4))) # matriz 3x4 de zeros
# ones: array preenchido com uns
print(np.ones(5))
print(np.ones((2, 3, 4))) # tensor 3D de uns
# full: array preenchido com valor específico
print(np.full((3, 3), 7)) # matriz 3x3 de setes
# eye: matriz identidade
print(np.eye(4)) # identidade 4x4
# arange: sequência de números (como range, mas retorna array)
print(np.arange(10)) # [0 1 2 3 4 5 6 7 8 9]
print(np.arange(1, 10, 2)) # [1 3 5 7 9]
print(np.arange(0, 1, 0.1)) # [0. 0.1 0.2 ... 0.9]
# linspace: N pontos igualmente espaçados entre a e b
print(np.linspace(0, 1, 5)) # [0. 0.25 0.5 0.75 1. ]
print(np.linspace(0, 10, 11)) # [0. 1. 2. ... 10.]
3.4 Arrays aleatórios
np.random.seed(42) # sempre defina seed para reprodutibilidade
# Uniformes entre 0 e 1
print(np.random.random((3, 3)))
# Normais (média 0, desvio 1) — inicialização de pesos em redes neurais
print(np.random.randn(3, 3))
# Inteiros aleatórios
print(np.random.randint(0, 10, size=(3, 4)))
# Normal com média e desvio específicos
pesos = np.random.normal(loc=0.0, scale=0.01, size=(128, 64))
print(f"Pesos inicializados: shape={pesos.shape}, média={pesos.mean():.6f}")
# Embaralhar array
dados = np.arange(10)
np.random.shuffle(dados)
print(dados)
4. Atributos Essenciais de Arrays
arr = np.array([[1.0, 2.0, 3.0],
[4.0, 5.0, 6.0]])
print(arr.shape) # (2, 3) — dimensões
print(arr.ndim) # 2 — número de dimensões
print(arr.size) # 6 — total de elementos
print(arr.dtype) # float64 — tipo dos dados
print(arr.nbytes) # 48 — bytes na memória (6 * 8 bytes)
5. Indexação e Fatiamento
5.1 Arrays 1D
arr = np.array([10, 20, 30, 40, 50, 60, 70, 80, 90])
print(arr[0]) # 10
print(arr[-1]) # 90
print(arr[2:6]) # [30 40 50 60]
print(arr[::2]) # [10 30 50 70 90]
print(arr[::-1]) # [90 80 70 ... 10]
5.2 Arrays 2D
m = np.array([
[1, 2, 3, 4],
[5, 6, 7, 8],
[9, 10, 11, 12]
])
# Elemento específico: [linha, coluna]
print(m[0, 0]) # 1
print(m[1, 2]) # 7
print(m[-1, -1]) # 12
# Linha inteira
print(m[1, :]) # [5 6 7 8]
print(m[1]) # equivalente
# Coluna inteira
print(m[:, 2]) # [3 7 11]
# Submatriz
print(m[0:2, 1:3])
# [[2 3]
# [6 7]]
# Linhas específicas
print(m[[0, 2], :])
# [[ 1 2 3 4]
# [ 9 10 11 12]]
5.3 Indexação booleana (muito usada em ML)
arr = np.array([3, -1, 4, -1, 5, -9, 2, 6])
# Máscara booleana
mascara = arr > 0
print(mascara) # [ True False True False True False True True]
# Filtrar elementos positivos
print(arr[mascara]) # [3 4 5 2 6]
print(arr[arr > 0]) # equivalente, mais conciso
# Modificar elementos com condição
arr[arr < 0] = 0
print(arr) # [3 0 4 0 5 0 2 6]
Exemplo em ML — filtrar exemplos de uma classe específica:
features = np.array([[1.2, 3.4], [2.1, 1.5], [0.8, 4.2], [3.3, 2.8]])
labels = np.array([0, 1, 0, 1])
# Selecionar apenas exemplos da classe 1
classe_1 = features[labels == 1]
print(classe_1)
# [[2.1 1.5]
# [3.3 2.8]]
6. Operações Matemáticas
6.1 Operações elemento a elemento
a = np.array([1, 2, 3, 4])
b = np.array([10, 20, 30, 40])
print(a + b) # [11 22 33 44]
print(a - b) # [-9 -18 -27 -36]
print(a * b) # [10 40 90 160]
print(a / b) # [0.1 0.1 0.1 0.1]
print(a ** 2) # [1 4 9 16]
print(np.sqrt(a)) # [1. 1.41 1.73 2.]
6.2 Operações com escalares
arr = np.array([1, 2, 3, 4, 5])
print(arr + 10) # [11 12 13 14 15]
print(arr * 2) # [2 4 6 8 10]
print(arr / 5) # [0.2 0.4 0.6 0.8 1.0]
print(arr > 3) # [False False False True True]
6.3 Funções universais (ufuncs)
Funções que operam elemento a elemento de forma vetorizada:
arr = np.array([-2, -1, 0, 1, 2])
print(np.abs(arr)) # [2 1 0 1 2]
print(np.exp(arr)) # [0.135 0.368 1. 2.718 7.389]
print(np.log(np.abs(arr) + 1)) # log de valores positivos
# Funções trigonométricas
angulos = np.linspace(0, np.pi, 5)
print(np.sin(angulos)) # [0. 0.707 1. 0.707 0.]
print(np.cos(angulos)) # [1. 0.707 0. -0.707 -1.]
# Funções de ativação implementadas com NumPy
def relu(x):
return np.maximum(0, x)
def sigmoid(x):
return 1 / (1 + np.exp(-x))
def softmax(x):
e_x = np.exp(x - np.max(x)) # subtrai max para estabilidade numérica
return e_x / e_x.sum()
entradas = np.array([-2.0, -1.0, 0.0, 1.0, 2.0])
print("ReLU: ", relu(entradas))
print("Sigmoid:", np.round(sigmoid(entradas), 4))
print("Softmax:", np.round(softmax(entradas), 4))
Saída:
ReLU: [0. 0. 0. 1. 2.]
Sigmoid: [0.1192 0.2689 0.5 0.7311 0.8808]
Softmax: [0.0117 0.0317 0.0861 0.2341 0.6364]
7. Operações de Agregação
arr = np.array([[1, 2, 3],
[4, 5, 6],
[7, 8, 9]])
# Sem eixo — opera sobre todos os elementos
print(np.sum(arr)) # 45
print(np.mean(arr)) # 5.0
print(np.min(arr)) # 1
print(np.max(arr)) # 9
print(np.std(arr)) # 2.581...
# Com eixo — opera ao longo de um eixo
# axis=0: opera ao longo das linhas (resultado por coluna)
print(np.sum(arr, axis=0)) # [12 15 18]
print(np.mean(arr, axis=0)) # [4. 5. 6.]
# axis=1: opera ao longo das colunas (resultado por linha)
print(np.sum(arr, axis=1)) # [ 6 15 24]
print(np.mean(arr, axis=1)) # [2. 5. 8.]
# argmin e argmax: índice do mínimo e máximo
print(np.argmin(arr)) # 0 (índice do menor elemento)
print(np.argmax(arr)) # 8 (índice do maior elemento)
print(np.argmax(arr, axis=1)) # [2 2 2] — índice do max em cada linha
8. Reshape e Manipulação de Forma
8.1 reshape
arr = np.arange(12)
print(arr) # [ 0 1 2 3 4 5 6 7 8 9 10 11]
print(arr.shape) # (12,)
# Reshape para 3x4
m34 = arr.reshape(3, 4)
print(m34)
print(m34.shape) # (3, 4)
# Reshape para 2x2x3
t223 = arr.reshape(2, 2, 3)
print(t223.shape) # (2, 2, 3)
# -1 infere a dimensão automaticamente
m26 = arr.reshape(2, -1) # (2, 6)
m43 = arr.reshape(-1, 3) # (4, 3)
print(m26.shape, m43.shape)
8.2 flatten e ravel
m = np.array([[1, 2, 3], [4, 5, 6]])
# flatten: retorna cópia 1D
flat = m.flatten()
print(flat) # [1 2 3 4 5 6]
# ravel: retorna view 1D (mais eficiente, sem cópia)
rav = m.ravel()
print(rav) # [1 2 3 4 5 6]
8.3 transpose
m = np.array([[1, 2, 3],
[4, 5, 6]])
print(m.shape) # (2, 3)
mt = m.T # transposta
print(mt.shape) # (3, 2)
print(mt)
# [[1 4]
# [2 5]
# [3 6]]
8.4 expand_dims e squeeze
Muito usados para ajustar dimensões em deep learning:
arr = np.array([1, 2, 3, 4, 5])
print(arr.shape) # (5,)
# Adicionar dimensão — transformar em linha ou coluna
linha = np.expand_dims(arr, axis=0)
coluna = np.expand_dims(arr, axis=1)
print(linha.shape) # (1, 5)
print(coluna.shape) # (5, 1)
# Ou com None no índice (equivalente)
print(arr[np.newaxis, :].shape) # (1, 5)
print(arr[:, np.newaxis].shape) # (5, 1)
# squeeze: remover dimensões de tamanho 1
batch = np.zeros((1, 28, 28)) # uma imagem de 28x28
imagem = np.squeeze(batch)
print(imagem.shape) # (28, 28)
9. Broadcasting
Broadcasting é uma das funcionalidades mais poderosas e importantes do NumPy. Permite operar entre arrays de formas diferentes, expandindo automaticamente as dimensões menores.
9.1 Regras de broadcasting
Duas dimensões são compatíveis se: 1. São iguais, ou 2. Uma delas é 1
O NumPy compara as formas da direita para a esquerda.
# Escalar com array — o caso mais simples
arr = np.array([1, 2, 3, 4, 5])
print(arr + 10) # [11 12 13 14 15]
# O escalar 10 é "expandido" para [10, 10, 10, 10, 10]
# Vetor com matriz
m = np.array([[1, 2, 3],
[4, 5, 6],
[7, 8, 9]])
v = np.array([10, 20, 30]) # shape (3,)
print(m + v)
# [[11 22 33]
# [14 25 36]
# [17 28 39]]
# v é somado a cada linha da matriz
9.2 Normalização com broadcasting
dados = np.array([
[1.0, 200.0, 0.5],
[2.0, 150.0, 0.8],
[3.0, 300.0, 0.2],
[1.5, 250.0, 0.6]
])
# Calcular média e desvio padrão por coluna (feature)
media = dados.mean(axis=0) # shape (3,)
desvio = dados.std(axis=0) # shape (3,)
print("Médias:", media)
print("Desvios:", desvio)
# Normalização Z-score — broadcasting aplica automaticamente
dados_norm = (dados - media) / desvio
print("\nDados normalizados:")
print(dados_norm)
print(f"\nMédia após normalização: {dados_norm.mean(axis=0)}")
print(f"Desvio após normalização: {dados_norm.std(axis=0)}")
Saída:
Médias: [ 1.875 225. 0.525]
Desvios: [ 0.726 55.9 0.216]
Dados normalizados:
[[-1.033 -0.447 -0.116]
[ 0.172 -1.342 1.273]
[ 1.378 1.342 -1.505]
[-0.516 0.447 0.348]]
Média após normalização: [0. 0. 0.]
Desvio após normalização: [1. 1. 1.]
9.3 Visualizando broadcasting
# Exemplo clássico: tabela de distâncias
pontos_a = np.array([[0], [1], [2], [3]]) # shape (4, 1)
pontos_b = np.array([[0, 1, 2, 3, 4]]) # shape (1, 5)
distancias = np.abs(pontos_a - pontos_b) # shape (4, 5) — broadcasting!
print(distancias)
# [[0 1 2 3 4]
# [1 0 1 2 3]
# [2 1 0 1 2]
# [3 2 1 0 1]]
10. Álgebra Linear com NumPy
Essencial para entender redes neurais, onde tudo é multiplicação de matrizes.
a = np.array([[1, 2], [3, 4]])
b = np.array([[5, 6], [7, 8]])
# Multiplicação elemento a elemento (NÃO é produto de matrizes)
print(a * b)
# [[ 5 12]
# [21 32]]
# Produto de matrizes (matrix multiplication)
print(np.dot(a, b))
print(a @ b) # equivalente, mais moderno
# [[19 22]
# [43 50]]
# Vetor * matriz
v = np.array([1, 2])
print(a @ v) # [5 11]
# Determinante
print(np.linalg.det(a)) # -2.0
# Inversa
print(np.linalg.inv(a))
# [[-2. 1. ]
# [ 1.5 -0.5]]
# Autovalores e autovetores
vals, vecs = np.linalg.eig(a)
print("Autovalores:", vals)
print("Autovetores:\n", vecs)
# Norma de um vetor
v = np.array([3.0, 4.0])
print(np.linalg.norm(v)) # 5.0 — norma L2
11. Concatenação e Empilhamento
a = np.array([[1, 2, 3],
[4, 5, 6]])
b = np.array([[7, 8, 9],
[10, 11, 12]])
# concatenate ao longo das linhas (axis=0 — empilha verticalmente)
print(np.concatenate([a, b], axis=0))
# [[ 1 2 3]
# [ 4 5 6]
# [ 7 8 9]
# [10 11 12]]
# concatenate ao longo das colunas (axis=1 — empilha horizontalmente)
print(np.concatenate([a, b], axis=1))
# [[ 1 2 3 7 8 9]
# [ 4 5 6 10 11 12]]
# vstack: vertical stack (alias para axis=0)
print(np.vstack([a, b]))
# hstack: horizontal stack (alias para axis=1)
print(np.hstack([a, b]))
# stack: cria nova dimensão
c = np.stack([a, b]) # shape (2, 2, 3)
print(c.shape)
12. Exemplo Completo: Implementando uma Camada Linear do Zero
Vamos implementar uma camada densa (fully connected) de rede neural usando apenas NumPy — exatamente o que o PyTorch faz por baixo dos panos.
import numpy as np
np.random.seed(42)
class CamadaLinear:
"""
Camada linear (fully connected): y = X @ W + b
Implementação manual usando NumPy para fins didáticos.
"""
def __init__(self, n_entradas: int, n_saidas: int):
# Inicialização de Xavier/Glorot — padrão em redes neurais
escala = np.sqrt(2.0 / n_entradas)
self.W = np.random.randn(n_entradas, n_saidas) * escala
self.b = np.zeros(n_saidas)
print(f"Camada linear criada: {n_entradas} → {n_saidas}")
print(f" Pesos W: shape={self.W.shape}, média={self.W.mean():.4f}")
print(f" Bias b: shape={self.b.shape}")
def forward(self, X: np.ndarray) -> np.ndarray:
"""Passagem forward: y = X @ W + b"""
return X @ self.W + self.b
def contar_parametros(self) -> int:
return self.W.size + self.b.size
def relu(x: np.ndarray) -> np.ndarray:
return np.maximum(0, x)
def softmax(x: np.ndarray) -> np.ndarray:
e_x = np.exp(x - x.max(axis=1, keepdims=True))
return e_x / e_x.sum(axis=1, keepdims=True)
# Simulando uma rede neural de 2 camadas
# Input: batch de 4 exemplos, cada um com 3 features
X = np.array([
[0.5, 1.2, -0.8],
[1.1, -0.3, 0.9],
[-0.2, 0.7, 1.5],
[0.8, 0.4, -0.1]
], dtype=np.float32)
print(f"Input: shape={X.shape}\n")
# Camada 1: 3 entradas → 4 neurônios
camada1 = CamadaLinear(3, 4)
print()
# Camada 2: 4 entradas → 2 saídas (classificação binária)
camada2 = CamadaLinear(4, 2)
print()
# Forward pass
saida1 = relu(camada1.forward(X)) # aplicando ReLU após camada 1
saida2 = softmax(camada2.forward(saida1)) # softmax na camada final
print("Forward pass:")
print(f" X: shape={X.shape}")
print(f" Saída1: shape={saida1.shape} (após ReLU)")
print(f" Saída2: shape={saida2.shape} (após Softmax)")
print()
print("Probabilidades por classe:")
for i, probs in enumerate(saida2):
classe = np.argmax(probs)
print(f" Exemplo {i}: classe 0 = {probs[0]:.4f}, "
f"classe 1 = {probs[1]:.4f} → predição: classe {classe}")
total_params = camada1.contar_parametros() + camada2.contar_parametros()
print(f"\nTotal de parâmetros: {total_params}")
Saída:
Input: shape=(4, 3)
Camada linear criada: 3 → 4
Pesos W: shape=(3, 4), média=0.0312
Bias b: shape=(4,)
Camada linear criada: 4 → 2
Pesos W: shape=(4, 2), média=-0.0521
Bias b: shape=(2,)
Forward pass:
X: shape=(4, 3)
Saída1: shape=(4, 4) (após ReLU)
Saída2: shape=(4, 2) (após Softmax)
Probabilidades por classe:
Exemplo 0: classe 0 = 0.4823, classe 1 = 0.5177 → predição: classe 1
Exemplo 1: classe 0 = 0.5341, classe 1 = 0.4659 → predição: classe 0
Exemplo 2: classe 0 = 0.4912, classe 1 = 0.5088 → predição: classe 1
Exemplo 3: classe 0 = 0.5103, classe 1 = 0.4897 → predição: classe 0
Total de parâmetros: 22
Resumo da Aula
- NumPy é a base de toda computação numérica em Python para IA
ndarrayé o objeto central: array N-dimensional tipado e eficiente- Atributos essenciais:
shape,dtype,ndim,size - Indexação booleana permite filtrar dados por condição
- Operações são vetorizadas: muito mais rápidas que loops Python
axis=0opera sobre linhas (resultado por coluna),axis=1opera sobre colunas (resultado por linha)reshape(-1, n)infere dimensões automaticamente- Broadcasting permite operar entre arrays de formas compatíveis
@é o operador de multiplicação de matrizes- Funções de ativação (ReLU, sigmoid, softmax) são facilmente implementadas com NumPy
- Normalização Z-score com broadcasting é o pré-processamento mais comum em ML
Exercícios
-
Crie um array NumPy com os números de 1 a 20. Em seguida, sem usar loops, extraia: todos os números pares, todos os múltiplos de 3, e os números que são ao mesmo tempo maiores que 5 e menores que 15.
✓ Resposta:arr = np.arange(1, 21) print("Array original:", arr) pares = arr[arr % 2 == 0] print("Pares:", pares) # [ 2 4 6 8 10 12 14 16 18 20] multiplos_3 = arr[arr % 3 == 0] print("Múltiplos de 3:", multiplos_3) # [ 3 6 9 12 15 18] intervalo = arr[(arr > 5) & (arr < 15)] print("Entre 5 e 15:", intervalo) # [ 6 7 8 9 10 11 12 13 14]Note o uso de
&em vez deandpara operações booleanas elemento a elemento em NumPy. OandPython não funciona com arrays. -
Dada a matriz abaixo, calcule sem loops: a soma de cada linha, a média de cada coluna, o elemento máximo de toda a matriz e em qual posição (linha, coluna) ele se encontra.
m = np.array([ [4, 7, 2, 1], [3, 8, 5, 9], [6, 1, 3, 2], [8, 4, 7, 5] ])✓ Resposta:m = np.array([ [4, 7, 2, 1], [3, 8, 5, 9], [6, 1, 3, 2], [8, 4, 7, 5] ]) soma_linhas = m.sum(axis=1) print("Soma por linha:", soma_linhas) # [14 25 12 24] media_colunas = m.mean(axis=0) print("Média por coluna:", media_colunas) # [5.25 5. 4.25 4.25] maximo = m.max() print("Máximo:", maximo) # 9 posicao = np.unravel_index(m.argmax(), m.shape) print(f"Posição do máximo: linha={posicao[0]}, coluna={posicao[1]}") # linha=1, coluna=3np.unravel_indexconverte o índice linear retornado porargmax()para coordenadas (linha, coluna) na matriz. -
Explique o que é broadcasting em NumPy com suas próprias palavras. Por que a operação abaixo funciona sem erro, e qual é o resultado?
a = np.array([[1, 2, 3], [4, 5, 6]]) # shape (2, 3) b = np.array([10, 20, 30]) # shape (3,) print(a + b)✓ Resposta:Broadcasting é um mecanismo que permite ao NumPy executar operações entre arrays de formas diferentes, expandindo automaticamente as dimensões menores para que as formas sejam compatíveis.
A operação
a + bfunciona porque o NumPy compara as formas da direita para a esquerda:atem shape(2, 3)ebtem shape(3,). O NumPy "expande"bpara shape(2, 3)repetindo-o duas vezes, uma para cada linha dea.O resultado é:
# b = [10, 20, 30] é somado a cada linha de a # [[1+10, 2+20, 3+30], = [[11, 22, 33], # [4+10, 5+20, 6+30]] [14, 25, 36]]Nenhuma cópia real de
bé criada na memória — o NumPy simula a expansão virtualmente, tornando a operação eficiente. -
Implemente a função de normalização min-max usando NumPy e broadcasting. A fórmula é:
x_norm = (x - x_min) / (x_max - x_min). Aplique em cada coluna (feature) independentemente para a matriz abaixo:dados = np.array([ [2.0, 100.0, 0.3], [5.0, 200.0, 0.7], [1.0, 50.0, 0.1], [8.0, 400.0, 0.9], [3.0, 150.0, 0.5] ])✓ Resposta:dados = np.array([ [2.0, 100.0, 0.3], [5.0, 200.0, 0.7], [1.0, 50.0, 0.1], [8.0, 400.0, 0.9], [3.0, 150.0, 0.5] ]) # Mínimo e máximo por coluna (feature) minimo = dados.min(axis=0) # shape (3,) maximo = dados.max(axis=0) # shape (3,) print("Mínimos por coluna:", minimo) print("Máximos por coluna:", maximo) # Normalização — broadcasting aplica automaticamente dados_norm = (dados - minimo) / (maximo - minimo) print("\nDados normalizados:") print(np.round(dados_norm, 4)) print(f"\nMínimo após norm: {dados_norm.min(axis=0)}") print(f"Máximo após norm: {dados_norm.max(axis=0)}")Saída:
Mínimos por coluna: [ 1. 50. 0.1] Máximos por coluna: [ 8. 400. 0.9] Dados normalizados: [[0.1429 0.1429 0.25 ] [0.5714 0.4286 0.75 ] [0. 0. 0. ] [1. 1. 1. ] [0.2857 0.2857 0.5 ]] Mínimo após norm: [0. 0. 0.] Máximo após norm: [1. 1. 1.] -
Explique a diferença entre
arr * bearr @ b(ounp.dot(arr, b)) quandoarrebsão matrizes 2D. Por que essa distinção é crítica em redes neurais?✓ Resposta:arr * brealiza multiplicação elemento a elemento. Cada elemento dearré multiplicado pelo elemento correspondente debna mesma posição. As formas precisam ser iguais (ou compatíveis via broadcasting). O resultado tem a mesma forma das entradas.arr @ b(ounp.dot(arr, b)) realiza o produto matricial. Para duas matrizes(m, n)e(n, p), o resultado tem shape(m, p). Cada elemento do resultado é o produto escalar de uma linha dearrcom uma coluna deb.Essa distinção é crítica em redes neurais porque a transformação linear de uma camada é
y = X @ W + b— um produto matricial. Se você usasse*por engano, estaria multiplicando pesos por features de posição correspondente, o que não implementa nenhuma transformação útil. O produto matricial é o que permite que cada neurônio da camada seguinte receba contribuições de todos os neurônios da camada anterior com pesos diferentes. -
Dado o array abaixo representando as saídas (logits) de uma rede neural para 4 exemplos com 3 classes, aplique a função softmax e determine a classe predita para cada exemplo. Explique por que subtraímos o máximo antes de calcular o exp na implementação da softmax.
logits = np.array([ [2.1, 0.5, 1.8], [0.3, 3.2, 0.8], [1.5, 1.5, 1.5], [4.0, 0.1, 0.2] ])✓ Resposta:logits = np.array([ [2.1, 0.5, 1.8], [0.3, 3.2, 0.8], [1.5, 1.5, 1.5], [4.0, 0.1, 0.2] ]) def softmax(x): e_x = np.exp(x - x.max(axis=1, keepdims=True)) return e_x / e_x.sum(axis=1, keepdims=True) probs = softmax(logits) print("Probabilidades:") print(np.round(probs, 4)) classes = np.argmax(probs, axis=1) print("\nClasses preditas:", classes)Saída:
Probabilidades: [[0.5395 0.0674 0.3931] [0.0547 0.8909 0.0544] [0.3333 0.3333 0.3333] [0.9682 0.0162 0.0156]] Classes preditas: [0 1 0 0]Subtraímos o máximo antes do
exppor estabilidade numérica. Se os logits forem grandes (ex: 1000),np.exp(1000)causa overflow e retornainf. Subtrair o máximo garante que o maior valor passado paraexpseja sempre 0, entãoexp(0) = 1. Matematicamente, o resultado do softmax é idêntico com ou sem essa subtração, pois o fator cancela no numerador e denominador. Para o terceiro exemplo, os três logits são iguais (1.5), então o softmax distribui a probabilidade igualmente: 1/3 para cada classe.