Pular para o conteúdo

    Capítulo 23, Intermediário

    Álgebra linear

    Resolver sistemas de equações, medir distâncias, ajustar uma reta aos dados: a álgebra linear está por baixo de quase tudo em ciência de dados, e o NumPy a entrega pronta.

    Código deste capítulo: intermediario/cap23_algebra_linear.py

    Multiplicação de matrizes

    Há duas multiplicações, e confundi-las é um erro clássico. O * multiplica elemento a elemento. O @ (ou np.matmul) é a multiplicação de matrizes, linhas por colunas:

    intermediario/cap23_algebra_linear.pylinhas 10 a 17
    import numpy as np
    
    A = np.array([[2.0, 1.0], [1.0, 3.0]])
    b = np.array([3.0, 5.0])
    print(A @ A)
    print(A.T @ b)
    print(A * A)
    print(np.array_equal(A * A, A @ A))
    
    Saída
    [[ 5.  5.]
     [ 5. 10.]]
    [11. 18.]
    [[4. 1.]
     [1. 9.]]
    False
    

    Para vetores, o @ é o produto escalar. Com ele e com a norma (o comprimento do vetor) se calcula o cosseno do ângulo entre dois vetores, uma medida de similaridade usada em busca e recomendação:

    intermediario/cap23_algebra_linear.pylinhas 19 a 21
    u = np.array([3.0, 4.0])
    v = np.array([1.0, 0.0])
    print(u @ v, np.linalg.norm(u), (u @ v) / (np.linalg.norm(u) * np.linalg.norm(v)))
    
    Saída
    3.0 5.0 0.6
    

    Resolver um sistema de equações

    O sistema 2x + y = 3 e x + 3y = 5 se escreve como A @ x = b. O np.linalg.solve o resolve, e conferir é simples: multiplicar de volta e comparar:

    intermediario/cap23_algebra_linear.pylinhas 26 a 27
    x = np.linalg.solve(A, b)
    print(x, np.allclose(A @ x, b))
    
    Saída
    [0.8 1.4] True
    

    Não calcule a inversa para resolver sistemas

    Muita gente escreve inv(A) @ b. Evite: calcular a inversa é mais lento e menos preciso do que o solve. Use o solve sempre que o objetivo for resolver um sistema, e deixe a inversa para quando você precisa dela de verdade.

    Determinante, inversa e autovalores

    intermediario/cap23_algebra_linear.pylinhas 32 a 35
    print(round(np.linalg.det(A), 6))
    print(np.round(np.linalg.inv(A), 3))
    valores, vetores = np.linalg.eigh(A)
    print(np.round(valores, 3))
    
    Saída
    5.0
    [[ 0.6 -0.2]
     [-0.2  0.4]]
    [1.382 3.618]
    

    O eigh é a versão para matrizes simétricas, mais rápida e mais estável do que a geral (eig). Os autovalores descrevem, de forma resumida, "quanto a matriz estica" cada direção, e estão por trás de técnicas como a análise de componentes principais.

    Ajustar uma reta: mínimos quadrados

    Dado um conjunto de pontos com ruído, o np.linalg.lstsq encontra a reta que minimiza o erro quadrático. Basta montar a matriz com a coluna de x e uma coluna de uns (para o intercepto):

    intermediario/cap23_algebra_linear.pylinhas 40 a 45
    rng = np.random.default_rng(1)
    xs = np.linspace(0, 10, 50)
    ys = 3 * xs + 2 + rng.normal(0, 0.5, 50)
    X = np.column_stack([xs, np.ones_like(xs)])
    (coef, intercepto), *_ = np.linalg.lstsq(X, ys, rcond=None)
    print(abs(coef - 3) < 0.1, abs(intercepto - 2) < 0.5)
    
    Saída
    True True
    

    Eu gerei os dados com inclinação 3 e intercepto 2, mais ruído, e o ajuste os recupera com boa precisão. É o mesmo cálculo que o capítulo 40 compara com o scikit-learn.

    Lotes de matrizes

    O @ funciona em lotes: ele multiplica as duas últimas dimensões e trata as anteriores como lote (com broadcasting). É assim que uma rede neural processa vários exemplos de uma vez:

    intermediario/cap23_algebra_linear.pylinhas 50 a 52
    lote_a = np.ones((10, 3, 4))
    lote_b = np.ones((10, 4, 2))
    print((lote_a @ lote_b).shape)
    
    Saída
    (10, 3, 2)
    

    Exercício 1

    Projetar um vetor sobre outro

    Escreva projetar(u, v), a projeção de u na direção de v: (u · v) / (v · v) * v.