Fundamentos do Machine Learning com Python
Introdução ao NumPy: Trabalhando com Arrays Numéricos (Documentação NumPy)
Aprenda sobre introdução ao numpy: trabalhando com arrays numéricos (documentação numpy)
Introdução ao NumPy: Trabalhando com Arrays Numéricos
Olá, futuros cientistas de dados e engenheiros de Machine Learning! 👋 Sejam bem-vindos à nossa aula sobre NumPy, uma das bibliotecas mais fundamentais e poderosas do ecossistema Python para computação numérica.
Nesta aula, vamos desvendar o NumPy e entender por que ele é a espinha dorsal de muitas operações em Machine Learning. Prepare-se para mergulhar no mundo dos arrays multidimensionais e operações otimizadas!
1. Introdução ao NumPy: O Pilar da Computação Numérica 🚀
NumPy, que significa Numerical Python, é uma biblioteca de código aberto para a linguagem de programação Python, que oferece suporte para arrays e matrizes multidimensionais grandes, juntamente com uma vasta coleção de funções matemáticas de alto nível para operar sobre esses arrays.
Por que NumPy é Essencial para Machine Learning?
- Eficiência e Velocidade: Diferente das listas padrão do Python, os arrays NumPy (chamados
ndarray) são implementados em C, o que os torna significativamente mais rápidos e eficientes em termos de memória para armazenar e manipular grandes volumes de dados numéricos. Isso é crucial em Machine Learning, onde trabalhamos com datasets que podem ter milhões de pontos de dados. - Arrays Multidimensionais (
ndarray): O objeto central do NumPy é ondarray, que permite a criação de arrays com 1, 2, 3 ou mais dimensões. Isso é perfeito para representar vetores, matrizes, imagens (que são matrizes de pixels) e tensores (usados em Deep Learning). - Operações Vetorizadas: O NumPy permite aplicar operações matemáticas a arrays inteiros de uma só vez, sem a necessidade de loops explícitos em Python. Isso não só simplifica o código, mas também o torna muito mais rápido (um conceito conhecido como "vetorização").
- Base para Outras Bibliotecas: Bibliotecas populares como Pandas, SciPy, Scikit-learn e Matplotlib são construídas sobre o NumPy, utilizando seus arrays como estrutura de dados fundamental.
Em resumo, se você vai trabalhar com dados numéricos em Python, o NumPy é uma ferramenta indispensável!
2. Exploração Detalhada do NumPy com Exemplos 💡
Vamos agora explorar os principais conceitos e funcionalidades do NumPy.
Primeiro, precisamos importar a biblioteca. É uma convenção comum importá-la com o alias np:
import numpy as np2.1. O Objeto ndarray: O Coração do NumPy ❤️
O ndarray (N-dimensional array) é a estrutura de dados fundamental do NumPy. Ele é um contêiner homogêneo, o que significa que todos os elementos dentro dele devem ser do mesmo tipo de dado (inteiro, float, etc.).
Criação de um ndarray a partir de uma lista Python:
# Array 1D (vetor)
lista_1d = [1, 2, 3, 4, 5]
array_1d = np.array(lista_1d)
print(f"Array 1D: {array_1d}")
print(f"Tipo: {type(array_1d)}")
# Array 2D (matriz)
lista_2d = [[1, 2, 3], [4, 5, 6]]
array_2d = np.array(lista_2d)
print(f"\nArray 2D:\n{array_2d}")2.2. Métodos de Criação de Arrays 🛠️
NumPy oferece diversas maneiras convenientes de criar arrays:
a) Arrays Pré-Preenchidos
np.zeros(shape): Cria um array preenchido com zeros.np.ones(shape): Cria um array preenchido com uns.np.empty(shape): Cria um array com valores arbitrários (não inicializados), útil para alocar espaço rapidamente.np.full(shape, fill_value): Cria um array preenchido com um valor específico.
# Array de zeros 3x4
zeros_array = np.zeros((3, 4))
print(f"Array de zeros:\n{zeros_array}")
# Array de uns 2x3
ones_array = np.ones((2, 3))
print(f"\nArray de uns:\n{ones_array}")
# Array vazio (valores podem variar)
empty_array = np.empty((2, 2))
print(f"\nArray vazio:\n{empty_array}")
# Array preenchido com 7, de forma 2x2
full_array = np.full((2, 2), 7)
print(f"\nArray preenchido com 7:\n{full_array}")b) Arrays com Sequências Numéricas
np.arange(start, stop, step): Semelhante aorange()do Python, mas retorna umndarray.np.linspace(start, stop, num): Retorna números espaçados uniformemente em um intervalo especificado.
# Sequência de 0 a 9
seq_arange = np.arange(10)
print(f"Array com arange: {seq_arange}")
# Sequência de 0 a 10 com 5 elementos igualmente espaçados
seq_linspace = np.linspace(0, 10, 5)
print(f"Array com linspace: {seq_linspace}")c) Arrays Aleatórios
O submódulo np.random é essencial para gerar dados aleatórios, muito usado em simulações e inicialização de pesos em redes neurais.
np.random.rand(d0, d1, ..., dn): Gera números aleatórios de uma distribuição uniforme no intervalo[0, 1).np.random.randn(d0, d1, ..., dn): Gera números aleatórios de uma distribuição normal (gaussiana) padrão (média 0, desvio padrão 1).np.random.randint(low, high, size): Gera inteiros aleatórios no intervalo[low, high).
# Array 2x3 com números aleatórios uniformes
rand_uniform = np.random.rand(2, 3)
print(f"Array aleatório uniforme:\n{rand_uniform}")
# Array 2x2 com números aleatórios normais
rand_normal = np.random.randn(2, 2)
print(f"\nArray aleatório normal:\n{rand_normal}")
# Array 1x5 com inteiros aleatórios entre 0 e 10
rand_int = np.random.randint(0, 11, size=(1, 5)) # high é exclusivo
print(f"\nArray de inteiros aleatórios:\n{rand_int}")2.3. Atributos de um Array 📏
Os arrays NumPy possuem atributos que fornecem informações importantes sobre sua estrutura:
.shape: Uma tupla que indica as dimensões do array..ndim: O número de dimensões (eixos) do array..size: O número total de elementos no array..dtype: O tipo de dado dos elementos no array..itemsize: O tamanho em bytes de cada elemento do array..nbytes: O tamanho total em bytes do array.
arr = np.array([[1, 2, 3], [4, 5, 6]], dtype=np.float64)
print(f"Array:\n{arr}")
print(f"Shape (dimensões): {arr.shape}") # (linhas, colunas)
print(f"Número de dimensões: {arr.ndim}")
print(f"Número total de elementos: {arr.size}")
print(f"Tipo de dado dos elementos: {arr.dtype}")
print(f"Tamanho em bytes de cada elemento: {arr.itemsize} bytes")
print(f"Tamanho total do array em bytes: {arr.nbytes} bytes")2.4. Indexação e Fatiamento (Slicing) ✂️
Acessar e manipular partes de arrays é fundamental. O NumPy oferece poderosas ferramentas de indexação e fatiamento, semelhantes às listas Python, mas estendidas para múltiplas dimensões.
a) Indexação Básica
array_1d = np.array([10, 20, 30, 40, 50])
print(f"Elemento na posição 0: {array_1d[0]}")
print(f"Último elemento: {array_1d[-1]}")
array_2d = np.array([[1, 2, 3], [4, 5, 6], [7, 8, 9]])
print(f"\nArray 2D:\n{array_2d}")
# Acessando o elemento na linha 1, coluna 2 (índices começam em 0)
print(f"Elemento [1, 2]: {array_2d[1, 2]}") # Equivalente a array_2d[1][2]b) Fatiamento (Slicing)
Sintaxe: [start:stop:step] para cada dimensão.
array_1d = np.arange(10, 101, 10) # [10, 20, ..., 100]
print(f"Array 1D: {array_1d}")
print(f"Primeiros 3 elementos: {array_1d[:3]}")
print(f"Elementos do índice 2 ao 5: {array_1d[2:6]}")
print(f"Elementos do índice 2 até o final: {array_1d[2:]}")
print(f"Elementos com passo de 2: {array_1d[::2]}")
print(f"\nArray 2D:\n{array_2d}")
# Selecionar as duas primeiras linhas e as duas primeiras colunas
sub_array = array_2d[:2, :2]
print(f"Sub-array (2x2 superior esquerdo):\n{sub_array}")
# Selecionar todas as linhas, mas apenas a segunda coluna
coluna_2 = array_2d[:, 1]
print(f"Segunda coluna: {coluna_2}")
# Selecionar a primeira linha
linha_1 = array_2d[0, :]
print(f"Primeira linha: {linha_1}")c) Indexação Booleana
Permite selecionar elementos de um array que satisfaçam uma determinada condição.
data = np.array([10, 25, 5, 40, 15, 30])
print(f"Dados: {data}")
# Selecionar elementos maiores que 20
condicao = data > 20
print(f"Condição booleana: {condicao}")
elementos_maiores_que_20 = data[condicao]
print(f"Elementos maiores que 20: {elementos_maiores_que_20}")
# Combinando condições
condicao_complexa = (data > 10) & (data < 30)
elementos_entre_10_e_30 = data[condicao_complexa]
print(f"Elementos entre 10 e 30 (exclusivo): {elementos_entre_10_e_30}")d) Indexação "Fancy"
Permite selecionar elementos usando um array de índices.
arr_fancy = np.array([100, 200, 300, 400, 500])
indices = np.array([0, 2, 4]) # Queremos o 1º, 3º e 5º elemento
selected_elements = arr_fancy[indices]
print(f"Elementos selecionados por índices: {selected_elements}")
# Em 2D
matriz_fancy = np.array([[1, 2, 3], [4, 5, 6], [7, 8, 9]])
linhas_selecionadas = np.array([0, 2]) # Selecionar a primeira e a terceira linha
colunas_selecionadas = np.array([0, 2]) # Selecionar a primeira e a terceira coluna
# Selecionar elementos nas posições (0,0), (2,2)
elementos_especificos = matriz_fancy[linhas_selecionadas, colunas_selecionadas]
print(f"\nMatriz:\n{matriz_fancy}")
print(f"Elementos específicos (diagonal principal): {elementos_especificos}")2.5. Operações Básicas com Arrays ➕➖✖️➗
As operações em NumPy são geralmente elemento a elemento (element-wise) por padrão, o que as torna muito eficientes.
arr1 = np.array([1, 2, 3])
arr2 = np.array([4, 5, 6])
print(f"Array 1: {arr1}")
print(f"Array 2: {arr2}")
# Soma
print(f"Soma: {arr1 + arr2}")
# Subtração
print(f"Subtração: {arr1 - arr2}")
# Multiplicação (elemento a elemento)
print(f"Multiplicação (elemento a elemento): {arr1 * arr2}")
# Divisão
print(f"Divisão: {arr1 / arr2}")
# Potência
print(f"Potência: {arr1 ** 2}") # Eleva cada elemento de arr1 ao quadrado
# Operações com escalares
print(f"Array 1 + 10: {arr1 + 10}")
print(f"Array 2 * 3: {arr2 * 3}")Operações Matriciais
Para multiplicação de matrizes (produto escalar), usamos np.dot() ou o operador @ (Python 3.5+).
matriz_a = np.array([[1, 2], [3, 4]])
matriz_b = np.array([[5, 6], [7, 8]])
print(f"Matriz A:\n{matriz_a}")
print(f"Matriz B:\n{matriz_b}")
# Produto escalar de matrizes usando np.dot()
produto_dot = np.dot(matriz_a, matriz_b)
print(f"\nProduto escalar (np.dot):\n{produto_dot}")
# Produto escalar de matrizes usando o operador @
produto_at = matriz_a @ matriz_b
print(f"\nProduto escalar (@):\n{produto_at}")Broadcasting 📡
Broadcasting é um mecanismo poderoso que permite que o NumPy execute operações em arrays com diferentes shapes (formas). Ele tenta "esticar" o array menor para que ele tenha a mesma forma do array maior, permitindo a operação.
Regras Básicas de Broadcasting:
- Se os arrays não tiverem o mesmo número de dimensões, a forma do array com menos dimensões é prefixada com
1s até que ambos os arrays tenham o mesmo número de dimensões. - Duas dimensões são compatíveis quando:
- elas são iguais.
- uma delas é
1.
arr_broad = np.array([[1, 2, 3], [4, 5, 6]]) # Shape (2, 3)
scalar = 10 # Shape ()
vetor = np.array([100, 200, 300]) # Shape (3,)
print(f"Array:\n{arr_broad}")
print(f"Escalar: {scalar}")
print(f"Vetor: {vetor}")
# Broadcasting com escalar: o escalar é "esticado" para o shape do array
result_scalar = arr_broad + scalar
print(f"\nArray + Escalar:\n{result_scalar}")
# Broadcasting com vetor: o vetor é "esticado" ao longo das linhas
# (3,) -> (1, 3) -> (2, 3)
result_vetor = arr_broad + vetor
print(f"\nArray + Vetor:\n{result_vetor}")
# Exemplo de Broadcasting que falha (shapes incompatíveis)
# vetor_incompativel = np.array([10, 20]) # Shape (2,)
# result_fail = arr_broad + vetor_incompativel # Erro: (2,3) vs (2,)
# print(result_fail)2.6. Funções Universais (Ufuncs) e Agregações ✨
Ufuncs (Universal Functions) são funções que operam elemento a elemento em arrays NumPy. Elas são implementadas em C, tornando-as extremamente rápidas.
arr_ufunc = np.array([1, 4, 9, 16])
print(f"Array: {arr_ufunc}")
# Raiz quadrada
print(f"Raiz quadrada (np.sqrt): {np.sqrt(arr_ufunc)}")
# Exponencial
print(f"Exponencial (np.exp): {np.exp(arr_ufunc)}")
# Funções trigonométricas (ex: seno)
arr_angles = np.array([0, np.pi/2, np.pi])
print(f"\nÂngulos: {arr_angles}")
print(f"Seno (np.sin): {np.sin(arr_angles)}")Funções de Agregação (Reduções)
Ufuncs também incluem funções de agregação que calculam um único valor a partir de um array (ou ao longo de um eixo).
np.sum(): Soma de todos os elementos.np.min(): Valor mínimo.np.max(): Valor máximo.np.mean(): Média.np.std(): Desvio padrão.np.var(): Variância.
O parâmetro axis é crucial para especificar ao longo de qual dimensão a operação deve ser realizada.
axis=0: Opera ao longo das colunas (reduz as linhas).axis=1: Opera ao longo das linhas (reduz as colunas).
matrix = np.array([[1, 2, 3], [4, 5, 6]])
print(f"Matriz:\n{matrix}")
# Soma total
print(f"Soma total: {np.sum(matrix)}")
# Média total
print(f"Média total: {np.mean(matrix)}")
# Soma ao longo das colunas (axis=0): [1+4, 2+5, 3+6]
print(f"Soma por coluna (axis=0): {np.sum(matrix, axis=0)}")
# Soma ao longo das linhas (axis=1): [1+2+3, 4+5+6]
print(f"Soma por linha (axis=1): {np.sum(matrix, axis=1)}")
# Valor máximo por coluna
print(f"Máximo por coluna (axis=0): {np.max(matrix, axis=0)}")3. Exercícios e Desafios Conceituais 🧠
Para consolidar o conhecimento, tente responder a estas perguntas e resolver os pequenos desafios de código.
- Diferença Fundamental: Qual a principal vantagem de usar um
ndarraydo NumPy em vez de uma lista Python para armazenar dados numéricos em larga escala? - Criação de Array: Crie um array NumPy 4x3 preenchido com o número
5. - Atributos: Dado o array
arr = np.array([[1.5, 2.5], [3.5, 4.5]]), quais seriam os valores dearr.shape,arr.ndimearr.dtype? - Fatiamento: Como você selecionaria as duas últimas linhas e a segunda coluna do array
matriz = np.array([[10, 20, 30], [40, 50, 60], [70, 80, 90], [100, 110, 120]])? - Indexação Booleana: Crie um array de 10 números inteiros aleatórios entre 0 e 100. Em seguida, use indexação booleana para extrair todos os números pares.
- Broadcasting: Explique com suas palavras o conceito de broadcasting no NumPy e forneça um exemplo simples onde ele é aplicado.
- Agregação: Dada a matriz
M = np.array([[1, 2, 3], [4, 5, 6], [7, 8, 9]]), calcule a média de cada coluna.
4. Resumo e Próximos Passos 🚀➡️
Nesta aula, desvendamos o NumPy, a biblioteca essencial para computação numérica em Python:
ndarray: Aprendemos que o objeto central do NumPy é ondarray, uma estrutura de dados homogênea, eficiente e multidimensional.- Criação: Exploramos diversas formas de criar arrays, desde listas Python até funções como
zeros,ones,arange,linspacee geradores de números aleatórios. - Atributos: Vimos como inspecionar as características de um array usando atributos como
shape,ndim,sizeedtype. - Manipulação: Dominamos as técnicas de indexação, fatiamento, indexação booleana e indexação "fancy" para acessar e modificar elementos de arrays.
- Operações: Entendemos que as operações em NumPy são vetorizadas e elemento a elemento por padrão, e aprendemos sobre o poderoso mecanismo de broadcasting.
- Ufuncs e Agregações: Conhecemos as funções universais (ufuncs) para operações rápidas e as funções de agregação como
sum,mean,max, com o uso do parâmetroaxis.
O domínio do NumPy é um passo gigantesco para qualquer um que deseje trabalhar com dados e Machine Learning em Python. Ele fornece a base para muitas outras bibliotecas que veremos em breve.
Nos próximos módulos, exploraremos:
- Pandas: A biblioteca para manipulação e análise de dados tabulares.
- Matplotlib e Seaborn: Ferramentas para visualização de dados.
- Introdução aos Modelos de Machine Learning: Começaremos a construir nossos primeiros modelos!
Continue praticando e experimentando com o NumPy. A fluência virá com a prática! Nos vemos na próxima aula! 👋