Agora que você já sabe o que é um modelo de IA generativa,
nós vamos dar um mergulho mais profundo e começar a ver
como podemos implementar esses modelos na prática.
Para isso, precisamos conhecer a base desses modelos.
A base para modelos de IA generativas
são redes neurais artificiais,
e a base para redes neurais artificiais é álgebra linear.
Então, nesse laboratório, veremos uma revisão sobre a álgebra linear
utilizando a biblioteca NumPy do Python.
Álgebra linear, basicamente, é como podemos fazer operações
com vetores e matrizes.
Então, para começar esse laboratório,
a gente vai começar a relembrar um pouco sobre vetores
e ver como podemos representar esses vetores no Python.
Para isso, vamos utilizar a biblioteca NumPy do Python.
Essa biblioteca é especializada em cálculo numérico e álgebra linear,
então é a principal biblioteca do Python para esse fim.
Então, vamos começar o código mportando essa biblioteca
para dentro do código.
Então "import numpy as np".
Bom, para definir um vetor no NumPy,
eu posso utilizar o módulo "array" do NumPy.
Então, se eu criar um "vetor_a = np.array"
e passar uma lista de valores para ele,
ele vai criar um vetor para mim.
Se eu quiser criar um segundo vetor, "b",
eu também posso utilizar o mesmo código.
Então, "np.array", vou passar uma lista de valores
e será criado um array, ou um vetor, e associado à variável "vetor_b".
Nesse código aqui, vamos criar dois vetores, "A" e "B",
e vamos printar esses vetores.
Então, aqui nas respostas, vetor A e vetor B,
da forma como a gente criou anteriormente,
e as dimensões do vetor A,
por exemplo, o vetor B tem a mesma dimensão do vetor A.
Então, o que é a dimensão de um vetor?
São quantos elementos tem nesse vetor.
Então, esse vetor tem três elementos, perfeito?
Se eu quiser fazer a soma desses dois vetores,
eu posso simplesmente vir aqui e criar uma variável "soma_vetores",
que vai ser igual a "vetor_a + vetor_b".
E, aqui, eu vou printar a soma desses vetores.
Então, quando nós somamos dois vetores, qual a operação é feita?
Basicamente, uma soma, elemento a elemento,
então cada elemento do vetor A
vai ser somado com o mesmo elemento do vetor B,
e eu vou ter esse resultado aqui na soma.
Se eu quiser fazer uma subtração, da mesma forma, "vetor_a - vetor_b",
e o resultado é uma subtração elemento a elemento.
Então, eu posso, por exemplo, criar uma variável "vetor_multiplicado",
que vai ser igual ao vetor_a vezes um valor escalar, por exemplo, "2".
Essa multiplicação também será feita elemento a elemento,
então cada elemento do vetor A será multiplicado por 2,
e aqui a gente tem, no nosso código,
a resposta desse vetor multiplicado.
Então, 1 vezes 2, 2 vezes 2, 3 vezes 2,
e a resposta aqui na variável vetor_multiplicado.
Uma outra operação muito importante
que eu posso fazer com dois vetores,
se chama "produto escalar".
O produto escalar entre dois vetores
é uma operação onde eu vou multiplicar esses dois vetores
e o resultado será um número escalar.
O que esse número representa?
Esse número representa um certo grau de similaridade
entre esses dois vetores, ou seja,
se esses vetores estiverem apontando
para uma mesma direção, esse produto será maior.
Se esses produtos estiverem perpendiculares um com o outro,
ou seja, se tiver um ângulo de 90 graus entre eles,
esse produto escalar vai retornar algo próximo de 0.
E se eles estiverem apontando para direções opostas,
isso significa que esse produto escalar terá um valor negativo.
Então, basicamente, o produto escalar
é uma forma de você multiplicar dois vetores
e obter um escalar como resposta,
e esse escalar tem um certo significado,
indica, mais ou menos, se esses dois vetores
estão apontando para direções parecidas.
Como eu calculo isso?
Por trás dos panos, eu estou fazendo
linhas de um vetor versus colunas do outro.
Então, se for necessário, por exemplo, nesse caso,
a gente tem vetores de mesmas dimensões,
o segundo vetor vai ser transposto, nesse caso.
Então, se eu fizer, aqui, o produto escalar do vetor A com o vetor B,
eu vou ter um número, 32.
Esse número indica, de certa forma,
se esses dois vetores estão apontando
para direções similares ou opostas.
Outro conceito muito importante
quando a gente fala de álgebra linear
é o conceito de matrizes.
Então, diferentemente dos vetores, que têm apenas uma dimensão,
as matrizes podem ter duas dimensões.
Então, as matrizes vão ter linhas e colunas.
Então, serão parecidas com uma tabelinha,
que teria linhas e colunas.
Então, eu posso criar, aqui no Python, uma matriz X,
usando o np.array,
e, em vez de passar apenas uma lista de valores,
eu vou passar uma lista de listas de valores,
onde cada lista vai representar uma linha dessa matriz.
Essa matriz tem duas linhas, essa linha e essa linha,
e duas colunas, essa coluna e essa coluna.
Então, é uma matriz que tem dimensões 2 por 2.
A matriz Y também é uma matriz 2 por 2,
porque ela tem duas linhas e duas colunas.
Se a gente rodar esse código, a gente vai ver essas duas matrizes
dispostas, aqui, da forma que a gente criou elas, perfeito?
As operações que a gente pode fazer com as matrizes
são muito similares às operações que a gente pode fazer
com os vetores.
Então, eu posso olhar para o shape dessa matriz,
vai me dar as dimensões dela,
então quais são as dimensões dessa matriz?
No vetor, a gente tinha apenas uma dimensão,
então a gente criou um vetor de três posições.
Aqui, eu tenho duas dimensões, eu tenho linhas e colunas,
então cada uma das nossas matrizes tem duas linhas e duas colunas.
Isso, a gente consegue saber usando o atributo "shape" da nossa matriz.
Da mesma forma como vetores,
como a gente fez para os nossos vetores,
a gente pode somar matrizes.
Então, quando eu somo a matriz X com a matriz Y,
eu estou fazendo a mesma operação que a gente fez para os vetores,
então eu estou somando, elemento a elemento,
os valores das duas matrizes.
Então, se a gente fizer a soma e printar essa soma,
a gente vai ter o resultado, que é a soma das duas matrizes,
elemento a elemento.
Da mesma forma, se a gente fizer a subtração
entre essas duas matrizes, eu também vou ter o resultado
como sendo uma matriz resultante de mesma dimensão, 2 por 2,
onde cada valor é a subtração, elemento a elemento,
dessas duas matrizes.
Por fim, eu posso multiplicar essa matriz, ou alguma matriz,
por um valor escalar, e aí eu também terei uma resposta
que vai ser a multiplicação desse escalar, por exemplo, o valor "3",
por cada elemento dessa matriz.
Então, o resultado também é uma matriz 2 por 2,
e essa matriz 2 por 2, cada elemento dela
é o resultado da multiplicação desse escalar pelo valor da matriz.
Da mesma forma que fizemos o produto escalar para vetores,
podemos, também, aplicar para matrizes,
porém, com uma pequena diferença.
Nos vetores, temos apenas uma dimensão,
nas matrizes, a gente tem duas dimensões.
Então, se eu criar uma matriz, por exemplo,
chamada "matriz_a_mult",
como uma matriz que vai ter duas linhas e três colunas,
eu utilizaria esse código aqui para criá-la.
Poderia criar, também, uma "matriz_b_mult",
essa matriz ao invés de ter duas linhas por três colunas,
vai ter três linhas por duas colunas.
Então, se a gente rodar esse código aqui,
a gente pode ver, aqui, as nossas duas matrizes.
Uma tem dimensões dois por três, uma tem dimensões três por dois.
Para fazer o produto escalar dessas duas matrizes,
eu posso utilizar o mesmo código do NumPy,
a mesma função, o mesmo método,
"np.dot", "matriz_a_mult" e "matriz_b_mult",
e eu vou ter o resultado dessa multiplicação.
Note que o resultado dessa multiplicação
é uma matriz de 2 por 2.
Então, existe uma regrinha
para a gente fazer multiplicação matricial através do produto escalar
e essa multiplicação ser possível.
Qual seria essa regra?
As dimensões da minha matriz A, a quantidade de colunas dela
tem que ser igual à quantidade de linhas da matriz B.
Então, sempre que eu estiver fazendo o produto escalar
entre duas matrizes, a quantidade de colunas da primeira
tem que ser igual à quantidade de linhas da segunda.
E o resultado sempre será a quantidade de linhas da primeira
por a quantidade de colunas da segunda.
Então, essas duas matrizes, eu posso multiplicar, por quê?
Porque elas obedecem o primeiro quesito,
a quantidade de colunas da matriz A
é igual à quantidade de linhas da matriz B.
E o resultado dessa conta desse produto escalar
vai dar a quantidade de linhas da matriz A
pela quantidade de colunas da matriz B, que vai dar 2 por 2.
Então, se a gente pudesse fixar um pouco essa regra,
você poderia pensar assim: quando eu for multiplicar duas matrizes,
fazer o produto escalar delas, os valores de dentro, ou seja,
a quantidade de colunas da primeira e de linhas da segunda
tem que ser igual, e o resultado vai dar os dois de fora, ou seja,
a quantidade de linhas da primeira
e a quantidade de colunas da segunda.
Então, seguindo aqui, vamos passar, agora,
por outras operações relevantes no nosso contexto de álgebra linear.
Um conceito mega importante, uma operação mega importante
é inverter uma matriz.
Para inverter uma matriz, qualquer matriz tem que ser quadrada,
como a que a gente está criando aqui,
"matriz_quadrada = np.array", uma matriz de 2 por 2.
Então, o que é uma matriz quadrada?
Mesma quantidade de linhas é a mesma quantidade de colunas.
E ela precisa ser não singular.
Ser não singular significa que o determinante dela não pode ser 0.
Então, basicamente, inverter uma matriz, no Python,
a gente pode fazer utilizando o módulo do NumPy de linear algebra,
então "np.linalg",
e do linalg, eu vou usar o método "inv", de inverter.
Então, vou criar uma variável matriz inversa
e vou atribuir para ela a inversa,
o cálculo da inversa dessa matriz quadrada que a gente criou.
Quando a gente inverte uma matriz, o que significa?
É como se eu estivesse fazendo um cálculo análogo
a 1 sobre um determinado número, esse é o inverso dela.
Então, 1 sobre essa matriz
é a mesma coisa que a inversa dessa matriz, perfeito?
Então, se a gente rodar esse código,
a gente vai ver, aqui, a matriz quadrada que a gente tinha criado
e, aqui, a inversa dessa matriz quadrada.
Ela é uma matriz quadrada, então ela pode ser invertível
e ela é não singular, então ela pode ser invertível também,
tem que obedecer esses dois critérios aí, perfeito?
E aí, a gente pode, depois, no nosso código
verificar essa inversão dessa matriz.
Como eu poderia fazer isso?
Se eu fizer o produto escalar da matriz original com a sua inversa,
eu tenho que ter como resultado a matriz identidade, ou seja,
a matriz identidade no contexto de álgebra
é como se fosse o valor 1.
Então, é como se eu multiplicasse a matriz por 1 sobre a matriz,
eu vou ter 1 como resposta, é isso que quer dizer esse código aqui.
Seguindo no nosso código,
como a gente falou de determinante da matriz,
a gente pode verificar, aqui,
como calcular o determinante de uma matriz.
Então, vou criar, aqui, uma matriz
para a gente calcular o determinante com base nela
e eu vou utilizar o mesmo módulo de linear algebra do NumPy,
só que eu vou usar, agora, o método "det", de determinante.
Então, qual é o determinante dessa matriz?
Se a gente rodar esse código,
a gente vai ver que o determinante dessa matriz é 4.99.
Se eu tiver uma matriz singular,
o que significa uma matriz singular?
Significa que é uma matriz cujo determinante é 0.
Se o determinante dela é 0, eu não consigo inverter essa matriz.
Então, essa matriz singular que a gente criou
mais embaixo do nosso código é uma matriz que não teria inversa,
não seria possível calcular a inversa dessa matriz, perfeito?
Outra operação extremamente importante é a norma de um vetor.
Então, a norma de um vetor é como a gente calcula,
de certa forma, o tamanho desse vetor.
Então, a norma desse vetor...
Existem várias formas de se calcular a norma de um vetor.
A mais conhecida e a mais utilizada é a norma L2,
que é também conhecida como a norma Euclidiana.
Então, ela é dada por essa continha aqui,
então vai ser a raiz quadrada da soma dos valores dessa matriz,
desse vetor, ao quadrado, então a raiz quadrada da soma
dos quadrados dos valores dessa matriz, perfeito?
Então, se a gente tiver aqui uma matriz 3 e -4,
a norma Euclidiana dessa matriz vai ser 5.
Como eu chego nesse cálculo?
Fazendo a raiz quadrada da soma dos quadrados
dos valores desse vetor, perfeito?
Bom, então, para calcular a norma Euclidiana,
eu posso usar o módulo de álgebra linear do NumPy,
utilizando o método "norm", certo?
E eu vou ter a norma Euclidiana.
Existem, também, outras formas de normas que a gente pode utilizar,
por exemplo, a norma de Frobenius.
Não é uma norma muito utilizada,
mas apenas para a gente conseguir visualizar que existe um parâmetro,
eu posso parametrizar o tipo de norma que eu quero
na função, no método "norm" do NumPy.
Então, por padrão, essa biblioteca já utiliza a norma Euclidiana,
mas eu poderia definir outra dentre as normas possíveis,
e, para saber as normas possíveis,
a gente pode olhar na documentação dessa biblioteca, perfeito?
Outro cálculo muito interessante, muito importante para criar IAs
e para a álgebra linear como um todo,
é você calcular distâncias entre vetores.
Então, eu posso ter dois vetores
e eu quero calcular o quão distantes esses vetores são.
Para fazer a distância entre vetores,
eu posso, simplesmente, calcular a diferença entre eles.
Então, se eu calcular a diferença entre eles,
eu vou ter um menos o outro,
eu vou ter a distância de cada vetor em cada dimensão.
Quando eu quero criar, utilizar uma distância Euclidiana,
o que é a distância Euclidiana?
É uma distância baseada na norma Euclidiana,
então vai ser a norma L2,
a norma Euclidiana da diferença entre os dois vetores,
então eu vou fazer a raiz quadrada das diferenças elevado ao quadrado,
essa que é a conta.
No Python, eu poderia, simplesmente, fazer a norma
das diferenças dos vetores que a gente calculou aqui.
E, aqui, eu coloco a ordem 2, a ordem já é presumida que é o 2,
por default, e aí, a gente tem, aqui,
o que a gente chama de distância Euclidiana entre dois vetores,
o quão distantes esses dois vetores estão, certo?
Outra métrica bastante interessante para se calcular entre dois vetores
é a distância de Manhattan.
Então, a única diferença entre a distância Euclidiana
e a distância de Manhattan é a ordem dessa norma.
Então, uma a gente chama de norma L2,
a distância Euclidiana,
a distância de Manhattan, a gente chama de L1,
tudo por causa da norma, da ordem da norma.
O que isso muda na prática?
Na norma euclidiana,
isso muda que a gente usa o quadrado das diferenças.
Na norma de Manhattan, na distância de Manhattan,
norma L1, a gente usa o módulo,
como se estivesse usando uma ordem 1,
elevado a 1, esse número, então a gente usa a norma dos valores.
Então, se a gente verificar aqui, a gente vai ter diferentes normas
e cada norma dessa vai ser mais útil ou menos útil
dependendo do caso de uso que a gente tiver.
Outras operações úteis aqui no contexto de álgebra linear:
a gente pode transpor uma matriz, então, frequentemente,
a gente vai ter que calcular a transposta de uma matriz.
O que é a transposta de uma matriz?
É simplesmente eu transformar o que era linha em coluna
e o que era coluna em linha.
Então, se eu criar uma matriz 3 por 2 com esses valores,
com esses elementos,
a transposta dessa matriz original vai ser, simplesmente,
essa matriz transposta, ou seja, linhas vão virar colunas
e colunas vão virar linhas, então, no final, a dimensão dessa matriz
vai de 3 por 2 para 2 por 3.
Então, vamos rodar esse código para visualizar.
Eu tinha uma matriz original 3 por 2,
a matriz transposta vai ser 2 por 3.
Outro conceito interessante é você criar uma matriz identidade.
Então, a matriz identidade é uma matriz similar a um número 1,
então aquela matriz que, se você multiplicar uma matriz por ela,
é como se você estivesse multiplicando um número por 1,
então vai dar ela mesma.
Então, a matriz identidade no Python, utilizando NumPy,
a gente usa a função "eye".
Se eu colocar aqui, como entrada, 2,
eu vou ter uma matriz identidade 2 por 2,
se eu colocar 3, eu vou ter uma matriz identidade 3 por 3.
Então, matriz identidade sempre são matrizes quadradas, ou seja,
mesmo número de linhas e de colunas.
Então, se a gente rodar aqui,
a gente vai ter a matriz identidade, ela é 2 por 2,
vou ter a matriz transposta,
e, se eu multiplicar uma matriz por sua identidade,
se eu fizer o produto escalar entre eles,
eu vou ter como resultado a mesma matriz original, certo?
Olha, "[1. 2.] [3. 4.]", resposta "[1. 2.] [3. 4.]", perfeito?
Agora, algumas funções auxiliares que o NumPy oferece para a gente:
então o NumPy oferece, por exemplo,
uma funçãozinha que é interessante
para o que a gente vai fazer durante as aulas,
que se chama "linspace".
O que esse linspace faz?
Ele simplesmente cria pontos linearmente, igualmente espaçados,
vamos dizer assim.
Então, o que essa funçãozinha aqui está fazendo?
Eu vou criar 5 pontos entre 0 e 10,
e esses pontos vão ser linearmente espaçados,
então 0, 2.5, 5, 7.5, 10.
Tenho 5 pontos e a distância entre eles é igual.
Outras funções auxiliares:
eu posso criar uma matriz só com zeros no Python,
utilizando a função "np.zeros".
E, aqui, eu posso passar como entrada,
como parâmetro para essa função,
a dimensão da matriz que eu quero criar.
Então, eu vou criar uma matriz de zeros
com dimensões de 2 linhas e 3 colunas.
Da mesma forma, eu posso criar uma matriz de números 1s,
utilizando "np.ones".
Vou passar aqui, eu quero criar uma matriz de 3 linhas e 2 colunas
só com números uns.
Então, está aqui a nossa matriz de zeros e a nossa matriz de uns.
E a gente pode, também, concatenar esses vetores,
ou seja, eu posso juntar dois vetores, ou juntar duas matrizes.
Então, aqui, por exemplo, eu tenho uma matriz chamada "array1"
e essa matriz é "np.array", primeira linha, segunda linha,
cada uma com duas colunas.
E eu posso ter um vetor, uma matriz que, na verdade,
aqui, vai ter uma linha só, uma linha e duas colunas.
Então, se eu concatenar esses dois arrays aqui, esses dois vetores,
essas duas matrizes, aqui no caso,
eu vou ter, na verdade, no final, uma matriz,
esse array concatenado vai adicionar essa linha
nesse vetor original que tinha só duas linhas.
Então, vou ter um vetor de 3 linhas e 2 colunas,
uma matriz, no caso, de 3 linhas e 2 colunas.
Esse parâmetro aqui, "axis", significa:
quando é 0, eu vou adicionar nas linhas,
quando são colunas, eu vou adicionar nas colunas,
então um eu vou adicionar embaixo, o outro eu vou adicionar ao lado,
é como se fosse isso.
Então, aqui, eu tenho um exemplo tanto adicionando linhas
a uma matriz original,
quanto adicionando colunas a uma matriz original.
Então, se a gente rodar esse exemplo,
eu tenho a matriz 1, 2 por 2,
e a matriz 2, que é uma linha por duas colunas.
Quando eu concateno elas utilizando o eixo 0, o axis 0,
eu vou adicionar uma linha a mais.
No outro exemplo, no debaixo,
eu tenho uma matriz de duas linhas e uma coluna,
e eu vou concatenar um array nas colunas,
então eu vou adicionar duas colunas diferentes.
Então, eu continuo com o 7 e 8, vou adicionar o 1, 3, 2, 4 aqui,
como colunas novas nessa matriz original.
E, por último, para fechar o nosso lab,
eu também posso utilizar uma função de reshape nos meus dados,
nos meus arrays, nos meus vetores e matrizes.
O que o reshape vai fazer?
Eu consigo alterar o formato dessa matriz ou desse array.
Então, imagina que eu tenho uma matriz, um array, na verdade,
que eu tenho 6 elementos nesse array.
Eu posso fazer um reshape nesse dado, nessa matriz,
e transformá-lo em uma matriz de duas linhas e três colunas.
O único ponto importante para a gente pensar é:
no final, essa conta tem que fechar,
então, se eu tenho 6 colunas, 6 números, 6 elementos nesse vetor,
e eu quero transformá-lo em uma matriz,
as dimensões, aqui, têm que multiplicar
e dar o mesmo resultado que a quantidade de elementos
desse vetor original.
Então, se eu tenho 6 elementos, 2 vezes 3 tem que dar 6.
Eu poderia, também, fazer 3 por 2, ou 6 por 1,
mas eu não poderia fazer 2 por 4, porque daria erro.
Então, se a gente rodar aqui, tem o array original,
e esse array, quando eu fizer o reshape de 2 por 3 dele,
ele vai reajustar esses elementos
em uma matriz de duas linhas e três colunas.
Se eu colocasse 4 aqui, daria erro.
Por que daria erro?
Porque eu só tenho 6 elementos.
Para fazer um reshape de 2 e 4, eu teria que ter 8 elementos.
Então o único ponto de atenção seria esse.
Então agora você já sabe muito sobre a generativa e já deu seus
primeiros passos sobre álgebra linear para conseguir você mesmo
implementar os seus próprios modelos de a generativa.