Phase 01 - Lesson 01

Intuição de Álgebra Linear

This lesson includes a graded coding exercise that runs in your browser, unlocked with lifetime access.

Todo modelo de IA é apenas matemática de matrizes vestida com um chapéu chique.

Tipo: Aprender Linguagens: Python, Julia Pré-requisitos: Fase 0 Tempo: ~60 minutos

Objetivos de Aprendizagem

  • Implementar operações de vetores e matrizes (adição, produto escalar, multiplicação de matrizes) do zero em Python
  • Explicar geometricamente o que o produto escalar, a projeção e o processo de Gram-Schmidt fazem
  • Determinar a independência linear, o posto (rank) e a base de um conjunto de vetores usando redução por linhas
  • Conectar conceitos de álgebra linear às suas aplicações em IA: embeddings, scores de atenção e LoRA

O Problema

Abra qualquer artigo de ML. Logo na primeira página, você verá vetores, matrizes, produtos escalares e transformações. Sem intuição de álgebra linear, isso é apenas um amontoado de símbolos. Com ela, você consegue ver o que uma rede neural está realmente fazendo -- movendo pontos pelo espaço.

Você não precisa ser matemático. Você precisa enxergar o que essas operações significam geometricamente e, então, programá-las você mesmo.

O Conceito

Vetores São Pontos (e Direções)

Um vetor é apenas uma lista de números. Mas esses números significam algo -- são coordenadas no espaço.

Vetor 2D [3, 2]:

x y Ponto
3 2 O vetor aponta da origem (0,0) para (3, 2) no plano

O vetor tem magnitude sqrt(3^2 + 2^2) = sqrt(13) e aponta para cima e para a direita.

Em IA, vetores representam tudo:

  • Uma palavra → um vetor de 768 números (seu "significado" no espaço de embedding)
  • Uma imagem → um vetor de milhões de valores de pixel
  • Um usuário → um vetor de preferências

Matrizes São Transformações

Uma matriz transforma um vetor em outro. Ela pode rotacionar, escalar, esticar ou projetar.

graph LR
    subgraph Before
        A["Point A"]
        B["Point B"]
    end
    subgraph Matrix["Matrix Multiplication"]
        M["M (transformation)"]
    end
    subgraph After
        A2["Point A'"]
        B2["Point B'"]
    end
    A --> M
    B --> M
    M --> A2
    M --> B2

Em IA, matrizes SÃO o modelo:

  • Pesos de redes neurais → matrizes que transformam a entrada em saída
  • Scores de atenção → matrizes que decidem no que focar
  • Embeddings → matrizes que mapeiam palavras para vetores

O Produto Escalar Mede Similaridade

O produto escalar de dois vetores te diz o quão similares eles são.

a · b = a₁×b₁ + a₂×b₂ + ... + aₙ×bₙ

Same direction:      a · b > 0  (similar)
Perpendicular:       a · b = 0  (unrelated)
Opposite direction:  a · b < 0  (dissimilar)

É literalmente assim que mecanismos de busca, sistemas de recomendação e RAG funcionam -- encontrar vetores com produtos escalares altos.

Independência Linear

Os vetores são linearmente independentes se nenhum vetor do conjunto pode ser escrito como uma combinação dos outros. Se v1, v2, v3 são independentes, eles geram um espaço 3D. Se um deles for uma combinação dos outros, eles geram apenas um plano.

Por que isso importa para IA: sua matriz de features deve ter colunas linearmente independentes. Se duas features são perfeitamente correlacionadas (linearmente dependentes), o modelo não consegue distinguir seus efeitos. Isso causa multicolinearidade na regressão -- a matriz de pesos fica instável, e pequenas mudanças na entrada produzem oscilações selvagens na saída.

Exemplo concreto:

v1 = [1, 0, 0]
v2 = [0, 1, 0]
v3 = [2, 1, 0]   # v3 = 2*v1 + v2

v1 e v2 são independentes -- nenhum é um múltiplo escalar ou combinação do outro. Mas v3 = 2*v1 + v2, então {v1, v2, v3} é um conjunto dependente. Esses três vetores estão todos no plano xy. Não importa como você os combine, você não consegue alcançar [0, 0, 1]. Você tem três vetores, mas apenas duas dimensões de liberdade.

Em um conjunto de dados: se feature_3 = 2*feature_1 + feature_2, adicionar feature_3 dá ao modelo zero informação nova. Pior ainda, isso torna as equações normais singulares -- não existe solução única para os pesos.

Base e Posto (Rank)

Uma base é um conjunto mínimo de vetores linearmente independentes que geram o espaço inteiro. O número de vetores da base é a dimensão do espaço.

A base padrão para o espaço 3D é {[1,0,0], [0,1,0], [0,0,1]}. Mas quaisquer três vetores independentes em 3D formam uma base válida. A escolha da base é uma escolha de sistema de coordenadas.

O posto (rank) de uma matriz = número de colunas linearmente independentes = número de linhas linearmente independentes. Se posto < min(linhas, colunas), a matriz tem posto deficiente. Isso significa:

  • O sistema tem infinitas soluções (ou nenhuma)
  • Informação é perdida na transformação
  • A matriz não pode ser invertida
Situação Posto O que significa para ML
Posto completo (posto = min(m, n)) Máximo possível Existe solução única de mínimos quadrados. O modelo está bem condicionado.
Posto deficiente (posto < min(m, n)) Abaixo do máximo As features são redundantes. Infinitas soluções de pesos. Regularização necessária.
Posto 1 1 Toda coluna é uma cópia escalada de um vetor. Todos os dados estão em uma linha.
Quase posto deficiente (valores singulares pequenos) Numericamente baixo A matriz é mal condicionada. Um pequeno ruído na entrada causa grandes mudanças na saída. Use truncamento de SVD ou regressão ridge.

Projeção

Projetar o vetor a sobre o vetor b dá a componente de a na direção de b:

proj_b(a) = (a dot b / b dot b) * b

O resíduo (a - proj_b(a)) é perpendicular a b. Essa decomposição ortogonal é a base do ajuste por mínimos quadrados.

A projeção está em todo lugar em ML:

  • A regressão linear minimiza a distância das observações ao espaço coluna -- a solução É uma projeção
  • O PCA projeta os dados sobre as direções de máxima variância
  • A atenção em transformers calcula projeções de queries sobre keys
graph LR
    subgraph Projection["Projection of a onto b"]
        direction TB
        O["Origin"] --> |"b (direction)"| B["b"]
        O --> |"a (original)"| A["a"]
        O --> |"proj_b(a)"| P["projection"]
        A -.-> |"residual (perpendicular)"| P
    end

Exemplo: a = [3, 4], b = [1, 0]

proj_b(a) = (31 + 40) / (11 + 00) * [1, 0] = 3 * [1, 0] = [3, 0]

A projeção descarta a componente y. Essa é a redução de dimensionalidade na sua forma mais simples -- jogar fora as direções com as quais você não se importa.

Processo de Gram-Schmidt

Converter qualquer conjunto de vetores independentes em uma base ortonormal. Ortonormal significa que todo vetor tem comprimento 1 e todo par é perpendicular.

O algoritmo:

  1. Pegue o primeiro vetor, normalize-o
  2. Pegue o segundo vetor, subtraia sua projeção sobre o primeiro, normalize
  3. Pegue o terceiro vetor, subtraia suas projeções sobre todos os vetores anteriores, normalize
  4. Repita para os vetores restantes
Input:  v1, v2, v3, ... (linearly independent)

u1 = v1 / |v1|

w2 = v2 - (v2 dot u1) * u1
u2 = w2 / |w2|

w3 = v3 - (v3 dot u1) * u1 - (v3 dot u2) * u2
u3 = w3 / |w3|

Output: u1, u2, u3, ... (orthonormal basis)

É assim que a decomposição QR funciona internamente. Q é a base ortonormal, R captura os coeficientes de projeção. A decomposição QR é usada em:

  • Resolução de sistemas lineares (mais estável que a eliminação gaussiana)
  • Cômputo de autovalores (algoritmo QR)
  • Regressão por mínimos quadrados (o método numérico padrão)

Construa

Passo 1: Vetores do zero (Python)

class Vector:
    def __init__(self, components):
        self.components = list(components)
        self.dim = len(self.components)

    def __add__(self, other):
        return Vector([a + b for a, b in zip(self.components, other.components)])

    def __sub__(self, other):
        return Vector([a - b for a, b in zip(self.components, other.components)])

    def dot(self, other):
        return sum(a * b for a, b in zip(self.components, other.components))

    def magnitude(self):
        return sum(x**2 for x in self.components) ** 0.5

    def normalize(self):
        mag = self.magnitude()
        return Vector([x / mag for x in self.components])

    def cosine_similarity(self, other):
        return self.dot(other) / (self.magnitude() * other.magnitude())

    def __repr__(self):
        return f"Vector({self.components})"


a = Vector([1, 2, 3])
b = Vector([4, 5, 6])

print(f"a + b = {a + b}")
print(f"a · b = {a.dot(b)}")
print(f"|a| = {a.magnitude():.4f}")
print(f"cosine similarity = {a.cosine_similarity(b):.4f}")

Passo 2: Matrizes do zero (Python)

class Matrix:
    def __init__(self, rows):
        self.rows = [list(row) for row in rows]
        self.shape = (len(self.rows), len(self.rows[0]))

    def __matmul__(self, other):
        if isinstance(other, Vector):
            return Vector([
                sum(self.rows[i][j] * other.components[j] for j in range(self.shape[1]))
                for i in range(self.shape[0])
            ])
        rows = []
        for i in range(self.shape[0]):
            row = []
            for j in range(other.shape[1]):
                row.append(sum(
                    self.rows[i][k] * other.rows[k][j]
                    for k in range(self.shape[1])
                ))
            rows.append(row)
        return Matrix(rows)

    def transpose(self):
        return Matrix([
            [self.rows[j][i] for j in range(self.shape[0])]
            for i in range(self.shape[1])
        ])

    def __repr__(self):
        return f"Matrix({self.rows})"


rotation_90 = Matrix([[0, -1], [1, 0]])
point = Vector([3, 1])

rotated = rotation_90 @ point
print(f"Original: {point}")
print(f"Rotated 90°: {rotated}")

Passo 3: Por que isso importa para IA

import random

random.seed(42)
weights = Matrix([[random.gauss(0, 0.1) for _ in range(3)] for _ in range(2)])
input_vector = Vector([1.0, 0.5, -0.3])

output = weights @ input_vector
print(f"Input (3D): {input_vector}")
print(f"Output (2D): {output}")
print("This is what a neural network layer does -- matrix multiplication.")

Passo 4: Versão em Julia

a = [1.0, 2.0, 3.0]
b = [4.0, 5.0, 6.0]

println("a + b = ", a + b)
println("a · b = ", a ⋅ b)       # Julia supports unicode operators
println("|a| = ", √(a ⋅ a))
println("cosine = ", (a ⋅ b) / (√(a ⋅ a) * √(b ⋅ b)))

# Matrix-vector multiplication
W = [0.1 -0.2 0.3; 0.4 0.5 -0.1]
x = [1.0, 0.5, -0.3]
println("Wx = ", W * x)
println("This is a neural network layer.")

Passo 5: Independência linear e projeção do zero (Python)

def is_linearly_independent(vectors):
    n = len(vectors)
    dim = len(vectors[0].components)
    mat = Matrix([v.components[:] for v in vectors])
    rows = [row[:] for row in mat.rows]
    rank = 0
    for col in range(dim):
        pivot = None
        for row in range(rank, len(rows)):
            if abs(rows[row][col]) > 1e-10:
                pivot = row
                break
        if pivot is None:
            continue
        rows[rank], rows[pivot] = rows[pivot], rows[rank]
        scale = rows[rank][col]
        rows[rank] = [x / scale for x in rows[rank]]
        for row in range(len(rows)):
            if row != rank and abs(rows[row][col]) > 1e-10:
                factor = rows[row][col]
                rows[row] = [rows[row][j] - factor * rows[rank][j] for j in range(dim)]
        rank += 1
    return rank == n


def project(a, b):
    scalar = a.dot(b) / b.dot(b)
    return Vector([scalar * x for x in b.components])


def gram_schmidt(vectors):
    orthonormal = []
    for v in vectors:
        w = v
        for u in orthonormal:
            proj = project(w, u)
            w = w - proj
        if w.magnitude() < 1e-10:
            continue
        orthonormal.append(w.normalize())
    return orthonormal


v1 = Vector([1, 0, 0])
v2 = Vector([1, 1, 0])
v3 = Vector([1, 1, 1])
basis = gram_schmidt([v1, v2, v3])
for i, u in enumerate(basis):
    print(f"u{i+1} = {u}")
    print(f"  |u{i+1}| = {u.magnitude():.6f}")

print(f"u1 · u2 = {basis[0].dot(basis[1]):.6f}")
print(f"u1 · u3 = {basis[0].dot(basis[2]):.6f}")
print(f"u2 · u3 = {basis[1].dot(basis[2]):.6f}")

Use

Agora a mesma coisa com NumPy -- o que você vai realmente usar na prática:

import numpy as np

a = np.array([1, 2, 3], dtype=float)
b = np.array([4, 5, 6], dtype=float)

print(f"a + b = {a + b}")
print(f"a · b = {np.dot(a, b)}")
print(f"|a| = {np.linalg.norm(a):.4f}")
print(f"cosine = {np.dot(a, b) / (np.linalg.norm(a) * np.linalg.norm(b)):.4f}")

W = np.random.randn(2, 3) * 0.1
x = np.array([1.0, 0.5, -0.3])
print(f"Wx = {W @ x}")

Posto, Projeção e QR com NumPy

import numpy as np

A = np.array([[1, 2], [2, 4]])
print(f"Rank: {np.linalg.matrix_rank(A)}")

a = np.array([3, 4])
b = np.array([1, 0])
proj = (np.dot(a, b) / np.dot(b, b)) * b
print(f"Projection of {a} onto {b}: {proj}")

Q, R = np.linalg.qr(np.random.randn(3, 3))
print(f"Q is orthogonal: {np.allclose(Q @ Q.T, np.eye(3))}")
print(f"R is upper triangular: {np.allclose(R, np.triu(R))}")

PyTorch -- Tensores São Vetores com Autodiff

import torch

x = torch.randn(3, requires_grad=True)
y = torch.tensor([1.0, 0.0, 0.0])

similarity = torch.dot(x, y)
similarity.backward()

print(f"x = {x.data}")
print(f"y = {y.data}")
print(f"dot product = {similarity.item():.4f}")
print(f"d(dot)/dx = {x.grad}")

O gradiente do produto escalar em relação a x é simplesmente y. O PyTorch calculou isso automaticamente. Toda operação em uma rede neural é construída a partir de operações como esta -- multiplicações de matrizes, produtos escalares, projeções -- e o autodiff rastreia os gradientes através de todas elas.

Você acabou de construir do zero o que o NumPy faz em uma linha. Agora você sabe o que está acontecendo por baixo dos panos.

Entregue

Esta lição produz:

  • outputs/prompt-linear-algebra-tutor.md -- um prompt para assistentes de IA ensinarem álgebra linear por meio de intuição geométrica

Conexões

Tudo nesta lição se conecta a partes específicas da IA moderna:

Conceito Onde aparece
Produto escalar Scores de atenção em transformers, similaridade de cosseno em RAG
Multiplicação de matrizes Toda camada de rede neural, toda transformação linear
Independência linear Seleção de features, evitar multicolinearidade
Posto Determinar se um sistema tem solução, LoRA (low-rank adaptation)
Projeção Regressão linear (projetar no espaço coluna), PCA
Gram-Schmidt / QR Resolvedores numéricos, cômputo de autovalores
Base ortonormal Computação numérica estável, transformações de whitening

O LoRA merece menção especial. Ele faz fine-tuning de grandes modelos de linguagem decompondo as atualizações de pesos em matrizes de posto baixo. Em vez de atualizar uma matriz de pesos 4096x4096 (16M de parâmetros), o LoRA atualiza duas matrizes de tamanho 4096x16 e 16x4096 (131K de parâmetros). A restrição de posto 16 significa que o LoRA assume que a atualização de pesos vive em um subespaço de 16 dimensões do espaço completo de 4096 dimensões. Isso é álgebra linear fazendo trabalho de verdade.

Exercícios

  1. Implemente Vector.angle_between(other) que retorna o ângulo em graus entre dois vetores
  2. Crie uma matriz de escala 2D que dobra a coordenada x e triplica a coordenada y, depois aplique-a ao vetor [1, 1]
  3. Dados 5 vetores aleatórios semelhantes a palavras (dimensão 50), encontre os dois mais similares usando a similaridade de cosseno
  4. Verifique que a saída do Gram-Schmidt é realmente ortonormal: confira que todo par tem produto escalar 0 e todo vetor tem magnitude 1
  5. Crie uma matriz 3x3 com posto 2. Verifique usando o método rank(). Depois explique qual objeto geométrico as colunas geram.
  6. Projete o vetor [1, 2, 3] sobre [1, 1, 1]. O que o resultado representa geometricamente?

Termos-chave

Termo O que as pessoas dizem O que realmente significa
Vetor "Uma flecha" Uma lista de números representando um ponto ou direção em um espaço n-dimensional
Matriz "Uma tabela de números" Uma transformação que mapeia vetores de um espaço para outro
Produto escalar "Multiplicar e somar" Uma medida de quão alinhados dois vetores estão -- o núcleo da busca por similaridade
Embedding "Alguma mágica de IA" Um vetor que representa o significado de algo (palavra, imagem, usuário)
Independência linear "Eles não se sobrepõem" Nenhum vetor do conjunto pode ser escrito como combinação dos outros
Posto (Rank) "Quantas dimensões" O número de colunas (ou linhas) linearmente independentes em uma matriz
Projeção "A sombra" A componente de um vetor na direção de outro
Base "Os eixos coordenados" Um conjunto mínimo de vetores independentes que geram o espaço
Ortonormal "Vetores unitários perpendiculares" Vetores mutuamente perpendiculares e cada um com comprimento 1
0 lifetime access. Curriculum based on AI Engineering from Scratch by Rohit Ghumare (MIT, used under attribution).