Noções de Python, TensorFlow e PyTorch
Plano de Estudo Aprofundado: Python, TensorFlow e PyTorch para o
Concurso de Auditor Tributário
Introdução: Seu Plano Estratégico de Estudos para Python e Machine
Learning
A jornada para a aprovação em um concurso de alto nível como o de Auditor Tributário
exige dedicação, estratégia e o domínio de competências que definem o profissional do
futuro. A inclusão de Noções de Python, TensorFlow e PyTorch no edital reflete uma
transformação global na administração pública, que se torna cada vez mais orientada por
dados. Dominar essas ferramentas não é apenas um requisito para a prova, mas um
investimento estratégico em uma carreira de destaque.
Este plano de seis semanas foi meticulosamente desenhado para conduzi-lo, de forma
progressiva e sólida, desde os fundamentos da programação em Python até a construção
de modelos de inteligência artificial com os frameworks mais poderosos do mercado. A
estrutura foi pensada para maximizar a retenção de conhecimento, conectando teoria e
prática a cada passo.
A progressão lógica do plano é a seguinte:
Semanas 1-2: Construindo uma base sólida e inabalável em Python. O foco aqui é
a maestria das estruturas de dados e dos paradigmas de programação que são pré-
requisitos para qualquer trabalho sério com dados.
Semanas 3-4: Dominando a construção de modelos com a eficiência do
TensorFlow e Keras. Nesta fase, o objetivo é aprender a construir, treinar e avaliar
modelos preditivos de forma rápida e intuitiva, utilizando a API de alto nível Keras.
Semanas 5-6: Aprofundando o conhecimento com a flexibilidade e o poder do
PyTorch. A etapa final visa consolidar o aprendizado, apresentando uma abordagem
alternativa e mais explícita para a construção de modelos, o que solidifica a
compreensão dos mecanismos internos do treinamento de redes neurais.
O objetivo deste guia não é apenas ensinar a sintaxe, mas sim o "porquê" por trás de cada
linha de código. Compreender os conceitos fundamentais que governam essas ferramentas
é o que diferencia a aplicação mecânica de um código da capacidade de resolver
problemas complexos de forma eficaz e criativa — uma habilidade indispensável para um
Auditor Tributário na era digital.
Semana 1: Fundamentos de Python - Estruturas de Dados Essenciais
O objetivo desta primeira semana é construir uma base conceitual robusta sobre as
principais estruturas de dados de Python. A distinção entre tipos de dados mutáveis e
imutáveis é um dos pilares da linguagem, e compreendê-la profundamente é essencial para
escrever código eficiente, seguro e livre de erros inesperados.
Dias 1-2: Listas - A Coleção Mutável e Ordenada
Teoria:
As listas são a estrutura de dados sequencial mais versátil e comumente utilizada em
Python. Elas são definidas como coleções de itens que são ordenadas e mutáveis.
Ordenadas: A ordem em que os itens são inseridos na lista é preservada. Cada item
possui um índice, começando em 0 para o primeiro item, que define sua posição.
Mutáveis: Esta é a característica mais importante das listas. "Mutável" significa que o
conteúdo da lista pode ser alterado após sua criação. É possível adicionar, remover ou
modificar elementos sem a necessidade de criar uma nova lista.
As operações fundamentais em listas incluem:
Indexação: Acessar um único elemento pelo seu índice (ex: minha_lista ).
Fatiamento (Slicing): Extrair uma sub-lista especificando um intervalo de índices (ex:
minha_lista[1:3] ).
Adição de elementos: Usando o método .append() para adicionar um item ao final da
lista.
Remoção de elementos: Usando .pop() para remover um item por seu índice ou
.remove() para remover pela primeira ocorrência de um valor.
A mutabilidade das listas as torna ideais para situações em que a coleção de dados precisa
ser modificada dinamicamente durante a execução do programa, como armazenar uma
série de transações financeiras ou registros de contribuintes que podem ser adicionados ou
removidos.
Código de Exemplo Comentado:
Python
# Criando uma lista de valores de tributos (em milhares de R$)
tributos_arrecadados =
print(f"Lista inicial: {tributos_arrecadados}")
# Acessando o primeiro elemento (índice 0)
primeiro_valor = tributos_arrecadados
print(f"Primeiro valor arrecadado: {primeiro_valor}")
# Modificando um valor na lista (mutabilidade em ação)
# Suponha uma correção no terceiro valor (índice 2)
tributos_arrecadados = 1050
print(f"Lista após correção: {tributos_arrecadados}")
# Adicionando um novo valor ao final da lista
tributos_arrecadados.append(1800)
print(f"Lista após nova arrecadação: {tributos_arrecadados}")
# Removendo o último valor adicionado
valor_removido = tributos_arrecadados.pop()
print(f"Valor removido: {valor_removido}")
print(f"Lista final: {tributos_arrecadados}")
# Iterando sobre a lista para calcular o total
total_arrecadado = 0
for valor in tributos_arrecadados:
total_arrecadado += valor
print(f"Total arrecadado: {total_arrecadado}")
Dias 3-4: Tuplas - A Sequência Imutável e a Integridade dos Dados
Teoria:
As tuplas são, em muitos aspectos, semelhantes às listas: são coleções ordenadas de itens.
No entanto, possuem uma diferença fundamental e definidora: são imutáveis. Uma vez que
uma tupla é criada, seu conteúdo não pode ser alterado. Tentar modificar, adicionar ou
remover um elemento de uma tupla resultará em um erro do tipo TypeError.
Essa imutabilidade não é uma limitação, mas uma característica poderosa com implicações
importantes:
1. Integridade dos Dados: Garante que uma coleção de dados permaneça constante ao
longo do programa, prevenindo modificações acidentais. É ideal para representar dados
que não devem mudar, como coordenadas geográficas (latitude, longitude) ou registros
fixos de dados.1
2. Performance: Como o Python sabe que a tupla não mudará de tamanho, ele pode
realizar otimizações de memória e performance. Tuplas geralmente consomem menos
memória e são ligeiramente mais rápidas de serem processadas do que as listas.
3. Uso como Chaves de Dicionário: Dicionários exigem que suas chaves sejam de um
tipo imutável. Como as tuplas são imutáveis (desde que todos os seus elementos
também o sejam), elas podem ser usadas como chaves, enquanto as listas não
podem.4
Uma particularidade sintática importante é a criação de tuplas com um único elemento. Para
diferenciá-la de uma expressão matemática entre parênteses, é necessário incluir uma
vírgula final: tupla_de_um_item = (item,) .
Código de Exemplo Comentado:
Python
# Criando uma tupla para armazenar dados fixos de um contribuinte (CPF, Ano
de Referência)
registro_fiscal = ('123.456.789-00', 2023)
print(f"Registro fiscal: {registro_fiscal}")
# Desempacotamento de tupla (tuple unpacking)
cpf, ano = registro_fiscal
print(f"CPF: {cpf}, Ano: {ano}")
# Tentando modificar a tupla (o que causará um erro)
try:
registro_fiscal = 2024
except TypeError as e:
print(f"\nErro ao tentar modificar a tupla: {e}")
print("Isso demonstra a imutabilidade da tupla.")
# Tupla com um único elemento
aliquota_fixa = (15.0,) # A vírgula é essencial
print(f"\nTupla de um elemento: {aliquota_fixa}, Tipo:
{type(aliquota_fixa)}")
# Usando uma tupla como chave de dicionário
dados_por_registro = {
('123.456.789-00', 2023): {'valor_declarado': 75000, 'imposto_pago':
5000},
('987.654.321-11', 2023): {'valor_declarado': 120000, 'imposto_pago':
15000}
}
print(f"\nDados do primeiro registro: {dados_por_registro[('123.456.789-00',
2023)]}")
Dias 5-6: Dicionários - Mapeamentos Chave-Valor Otimizados
Teoria:
Diferentemente de listas e tuplas, que são sequências indexadas por números inteiros, os
dicionários são coleções não ordenadas (em versões de Python anteriores à 3.7) de pares
chave-valor. Eles são otimizados para uma busca extremamente rápida de valores quando
a chave é conhecida. Em vez de percorrer a coleção para encontrar um item, o dicionário
usa um mecanismo interno chamado hashing para calcular quase que instantaneamente
onde o valor associado a uma chave está armazenado.1
As características principais dos dicionários são:
Mapeamento: Cada item é um par, onde uma chave única mapeia para um valor.
Chaves Imutáveis: As chaves devem ser de um tipo de dado imutável (como strings,
números ou tuplas) para que o mecanismo de hashing funcione corretamente.4
Recuperação Rápida: O tempo para encontrar um valor a partir de sua chave é, em
média, constante, independentemente do tamanho do dicionário. Isso os torna ideais
para armazenar dados que precisam ser acessados frequentemente por um
identificador único, como um cadastro de contribuintes onde o CPF é a chave.
Métodos comuns incluem .keys() para obter as chaves, .values() para obter os valores,
e .items() para obter os pares chave-valor. O método .get(chave, valor_padrao) é
uma forma segura de acessar uma chave, pois retorna um valor padrão (como None ) em
vez de um erro se a chave não existir.
Código de Exemplo Comentado:
Python
# Criando um dicionário para armazenar informações de um contribuinte
contribuinte = {
'cpf': '123.456.789-00',
'nome': 'João da Silva',
'renda_anual': 85000.00,
'possui_pendencias': False
}
print(f"Dicionário do contribuinte: {contribuinte}")
# Acessando um valor pela sua chave
nome_contribuinte = contribuinte['nome']
print(f"Nome: {nome_contribuinte}")
# Adicionando um novo par chave-valor
contribuinte['cidade'] = 'Brasília'
print(f"Dicionário atualizado: {contribuinte}")
# Usando o mé[Link]() para acesso seguro
endereco = [Link]('endereco', 'Não informado')
print(f"Endereço: {endereco}")
# Iterando sobre as chaves e valores do dicionário
print("\nDetalhes do Contribuinte:")
for chave, valor in [Link]():
print(f" - {[Link]()}: {valor}")
Dia 7: Revisão e Exercício Prático
A escolha entre lista, tupla e dicionário não é apenas uma questão de sintaxe, mas uma
decisão de design que afeta a performance, a segurança e a clareza do código. Um
programador experiente seleciona a estrutura que melhor expressa a sua intenção para
com os dados. Usar uma tupla para um conjunto de alíquotas fixas, por exemplo, comunica
a outros desenvolvedores (e a si mesmo no futuro) que esses valores são constantes e não
devem ser alterados. Essa prática, conhecida como "código auto-documentado", é um
princípio fundamental da engenharia de software que previne uma classe inteira de bugs
relacionados a modificações inesperadas de estado.
A tabela a seguir resume as diferenças cruciais para auxiliar na revisão e na tomada de
decisão.
Característica Lista Tupla Dicionário
Sintaxe `` (1, 2, 3) {'a': 1, 'b': 2}
Mutabilidade Mutável (pode ser Imutável (não pode Mutável (pode ser
alterada) ser alterada) alterado)
Ordenação Ordenada (ordem Ordenada (ordem Ordenada (desde Python
de inserção de inserção mantida) 3.7)
mantida)
Indexação Por inteiros (ex: Por inteiros (ex: Por chaves (ex:
lista ) tupla ) dicionario['a'] )
Uso Típico Coleções Coleções Mapeamento de dados
homogêneas e heterogêneas e com identificadores únicos
dinâmicas fixas (registros)
Performance Menos eficiente Mais eficiente em Extremamente rápido
para criação e memória e para busca por chave
acesso velocidade
Hashable Não (por ser Sim (se os Chaves devem ser
mutável) elementos forem hashable; valores não
imutáveis)
Exercício Prático da Semana 1: Gestão de Dados de Contribuintes
Objetivo: Aplicar o conhecimento sobre listas, tuplas e dicionários para criar uma estrutura
de dados que modele um sistema simplificado de gestão de contribuintes.
Enunciado:
Crie um script Python que realize as seguintes tarefas:
1. Defina uma tupla chamada CATEGORIAS_TRIBUTARIAS contendo as strings: 'IRPF' ,
'IPTU' , 'IPVA' . Esta estrutura deve garantir que as categorias não possam ser
alteradas durante a execução do programa.
2. Crie uma lista vazia chamada lista_contribuintes .
3. Crie três dicionários, cada um representando um contribuinte. Cada dicionário deve
conter as seguintes chaves:
'cpf' : uma string com o CPF.
'nome' : uma string com o nome.
'tributos_pagos' : um dicionário interno onde as chaves são itens da tupla
CATEGORIAS_TRIBUTARIAS e os valores são os montantes pagos. Nem todos os
contribuintes precisam ter pago todas as categorias.
4. Adicione os três dicionários de contribuintes à lista_contribuintes .
5. Escreva um laço for que itere sobre a lista_contribuintes e imprima um resumo
para cada um, mostrando o nome, o CPF e o total de tributos pagos (a soma dos
valores no dicionário 'tributos_pagos' ).
Este exercício força a utilização de cada estrutura de dados para o seu propósito ideal: a
tupla para dados constantes, o dicionário para dados estruturados e nomeados, e a lista
para uma coleção dinâmica desses registros.
Semana 2: Fundamentos de Python - Funções e Programação
Orientada a Objetos (POO)
Após dominar as estruturas de dados, o próximo passo é aprender a organizar o código de
forma lógica e reutilizável. Esta semana foca em funções, para modularizar o código, e na
Programação Orientada a Objetos (POO), um paradigma poderoso para gerenciar a
complexidade de sistemas maiores.
Dias 1-2: Funções - Reutilização e Modularidade
Teoria:
Uma função é um bloco de código organizado e reutilizável que executa uma tarefa
específica. Funções são a base da programação procedural e permitem quebrar problemas
complexos em partes menores e mais gerenciáveis. O princípio fundamental por trás do uso
de funções é "Don't Repeat Yourself" (DRY - Não se Repita). Se uma determinada
sequência de operações precisa ser executada várias vezes em um programa, encapsulá-la
em uma função evita a duplicação de código, tornando o programa mais fácil de ler, depurar
e manter.
Os componentes essenciais de uma função em Python são:
Palavra-chave def : Inicia a definição da função.
Nome da função: Um nome descritivo que indica o que a função faz.
Parâmetros (entre parênteses): Variáveis que a função aceita como entrada.
Corpo da função: O bloco de código indentado que executa a tarefa.
Instrução return (opcional): Envia um resultado de volta para o local onde a função
foi chamada. Se omitido, a função retorna None .
Código de Exemplo Comentado:
Python
# Uma função simples para calcular o imposto com base em uma renda e uma
alíquota
def calcular_imposto_simples(renda, aliquota_percentual):
"""Calcula o valor de um imposto simples."""
imposto = renda * (aliquota_percentual / 100)
return imposto
# Usando a função
renda_contribuinte_A = 50000
imposto_devido_A = calcular_imposto_simples(renda_contribuinte_A, 15)
print(f"Para uma renda de R${renda_contribuinte_A:.2f}, o imposto devido é
R${imposto_devido_A:.2f}")
# Uma função com um parâmetro de valor padrão
def calcular_multa(valor_principal, taxa_multa=2.0):
"""Calcula a multa por atraso. A taxa padrão é de 2%."""
multa = valor_principal * (taxa_multa / 100)
return multa
# Chamando a função com a taxa padrão
multa1 = calcular_multa(1000)
print(f"\nMulta com taxa padrão sobre R$1000: R${multa1:.2f}")
# Chamando a função e especificando uma taxa diferente
multa2 = calcular_multa(1000, taxa_multa=5.0)
print(f"Multa com taxa de 5% sobre R$1000: R${multa2:.2f}")
# Uma função que retorna múltiplos valores (na forma de uma tupla)
def analisar_divida(valor_principal):
"""Analisa uma dívida, calculando multa e juros."""
multa = calcular_multa(valor_principal)
juros = valor_principal * 0.01 # Juros fixos de 1% para o exemplo
total_a_pagar = valor_principal + multa + juros
return total_a_pagar, multa, juros
# Desempacotando o resultado da função
total, multa_calculada, juros_calculados = analisar_divida(2500)
print(f"\nAnálise da dívida de R$2500:")
print(f" - Multa: R${multa_calculada:.2f}")
print(f" - Juros: R${juros_calculados:.2f}")
print(f" - Total a pagar: R${total:.2f}")
Dias 3-6: Programação Orientada a Objetos (POO)
Teoria:
A Programação Orientada a Objetos é um paradigma de programação que estrutura um
programa ao agrupar propriedades (dados) e comportamentos (funções) relacionados em
entidades individuais chamadas objetos.8 Em vez de pensar em um programa como uma
sequência de procedimentos, a POO nos permite modelar entidades do mundo real (ou
conceituais), como um "Contribuinte", um "Veículo" ou uma "Nota Fiscal".
Essa abordagem é uma estratégia poderosa para gerenciar a complexidade. Os quatro
pilares da POO (Encapsulamento, Herança, Abstração e Polimorfismo) são ferramentas
práticas para escrever código mais organizado, manutenível e escalável.8 Para o escopo
deste concurso, o foco principal será nos conceitos fundamentais de classes, objetos,
atributos e métodos.
Classes e Objetos: Uma classe é um "molde" ou "planta baixa" que define as
características e ações que um tipo de objeto terá. Um objeto (ou instância) é uma
criação concreta a partir dessa classe, com seus próprios dados. A partir da classe
Veiculo , pode-se criar múltiplos objetos: meu_carro , onibus_escolar , etc., cada um
com suas próprias propriedades (cor, modelo).
O Método Construtor __init__ e self : Quando um novo objeto é criado a partir de
uma classe, um método especial chamado __init__ é executado automaticamente.
Este método é o construtor, responsável por inicializar o estado do objeto, ou seja,
definir seus atributos iniciais.11 O primeiro parâmetro de __init__ (e de todos os
métodos de instância) é, por convenção, chamado de self . self é uma referência ao
próprio objeto, permitindo que os métodos acessem e modifiquem os atributos daquela
instância específica.
Atributos de Instância vs. Atributos de Classe:
Atributos de Instância: São dados únicos para cada objeto. São definidos dentro
do método __init__ usando self.nome_do_atributo = valor . Por exemplo,
[Link] e [Link] são atributos de instância de um veículo.
Atributos de Classe: São dados compartilhados por todas as instâncias daquela
classe. São definidos diretamente no corpo da classe, fora de qualquer método.
Por exemplo, se todos os veículos de uma categoria tivessem 4 rodas,
numero_de_rodas = 4 poderia ser um atributo de classe.
Código de Exemplo Comentado:
Python
# Definindo a classe 'Veiculo' como um molde
class Veiculo:
# Atributo de classe: compartilhado por todos os objetos Veiculo
imposto_anual_base = 1500.00
# Método construtor (__init__) para inicializar os atributos de
instância
def __init__(self, placa, modelo, ano, cor):
# Atributos de instância: únicos para cada objeto
[Link] = placa
[Link] = modelo
[Link] = ano
[Link] = cor
self.velocidade_atual = 0
print(f"Veículo {[Link]} ({[Link]}) criado.")
# Um método de instância (comportamento)
def acelerar(self, incremento):
self.velocidade_atual += incremento
print(f"O veículo {[Link]} acelerou para
{self.velocidade_atual} km/h.")
# Outro método de instância
def frear(self, decremento):
self.velocidade_atual -= decremento
if self.velocidade_atual < 0:
self.velocidade_atual = 0
print(f"O veículo {[Link]} freou para {self.velocidade_atual}
km/h.")
# Método para calcular o imposto específico deste veículo
def calcular_ipva(self, fator_antiguidade):
# Acessa tanto o atributo de classe quanto o de instância (ano)
ipva = self.imposto_anual_base / (fator_antiguidade * (2024 -
[Link] + 1))
return ipva
# Criando dois objetos (instâncias) da classe Veiculo
carro1 = Veiculo('ABC-1234', 'Fusca', 1980, 'Azul')
carro2 = Veiculo('XYZ-5678', 'Civic', 2022, 'Preto')
# Usando os métodos dos objetos
[Link](50)
[Link](100)
[Link](20)
# Acessando atributos de instância
print(f"\nCor do carro 1: {[Link]}")
print(f"Cor do carro 2: {[Link]}")
# Acessando o atributo de classe (pode ser pela classe ou pela instância)
print(f"Imposto base (acessado pela classe):
R${Veiculo.imposto_anual_base:.2f}")
print(f"Imposto base (acessado pelo objeto carro1):
R${carro1.imposto_anual_base:.2f}")
# Calculando o IPVA para cada carro
ipva_carro1 = carro1.calcular_ipva(fator_antiguidade=1.5)
ipva_carro2 = carro2.calcular_ipva(fator_antiguidade=1.5)
print(f"IPVA do Fusca: R${ipva_carro1:.2f}")
print(f"IPVA do Civic: R${ipva_carro2:.2f}")
Dia 7: Revisão e Exercício Prático
O paradigma da orientação a objetos é fundamental para o desenvolvimento de software
robusto. Ao encapsular a lógica de negócio dentro de uma classe (por exemplo, as regras
de cálculo de imposto dentro da classe Contribuinte ), o código se torna mais seguro e
fácil de manter. O restante do sistema não precisa conhecer os detalhes da implementação;
ele apenas interage com a interface pública do objeto (seus métodos). Isso é o princípio da
Abstração. Se a legislação tributária mudar, as alterações podem ser contidas dentro do
método calcular_imposto_devido() , sem a necessidade de modificar outras partes do
programa que utilizam objetos Contribuinte . Essa contenção do "efeito cascata" de
mudanças é um dos maiores benefícios da POO, especialmente em sistemas
governamentais que estão em constante evolução.
Exercício Prático da Semana 2: Modelando um Contribuinte
Objetivo: Aplicar os conceitos de POO para criar um modelo de dados relevante para o
domínio de um auditor fiscal.
Enunciado:
Crie uma classe Python chamada Contribuinte.
1. Atributo de Classe: A classe deve ter um atributo de classe chamado
ALIQUOTA_PADRAO_IRPF com o valor de 27.5 .
2. Construtor ( __init__ ): O construtor deve aceitar o nome e o cpf do contribuinte
como argumentos e inicializar os seguintes atributos de instância:
[Link]
[Link]
[Link] : uma lista vazia para armazenar os valores dos rendimentos.
self.despesas_dedutiveis : uma lista vazia para armazenar os valores das
despesas dedutíveis.
3. Métodos: A classe deve ter os seguintes métodos de instância:
adicionar_rendimento(self, valor) : Adiciona um valor de rendimento à lista
[Link] .
adicionar_despesa(self, valor) : Adiciona um valor de despesa à lista
self.despesas_dedutiveis .
calcular_base_calculo(self) : Calcula e retorna a base de cálculo do IRPF, que é
a soma de todos os rendimentos menos a soma de todas as despesas dedutíveis.
calcular_imposto_devido(self) : Calcula e retorna o valor do imposto devido. O
cálculo deve ser base_de_calculo * (ALIQUOTA_PADRAO_IRPF / 100) . Este
método deve chamar o método calcular_base_calculo() internamente.
4. Teste: Após definir a classe, crie uma instância de Contribuinte , adicione alguns
rendimentos e despesas, e depois imprima um relatório mostrando o nome, CPF, a base
de cálculo e o imposto devido.
Semana 3: Introdução ao TensorFlow e o Mundo dos Tensores
Com uma base sólida em Python, esta semana marca a transição para o universo do
Machine Learning. O foco será o TensorFlow, um dos frameworks mais completos e
utilizados no mundo, e sua unidade fundamental de dados: o tensor.
Dias 1-2: O Ecossistema TensorFlow
Teoria:
TensorFlow é uma plataforma de código aberto de ponta a ponta para Machine Learning,
desenvolvida pela equipe do Google Brain. Embora seja frequentemente associado a redes
neurais complexas, sua arquitetura é projetada para abranger todo o ciclo de vida de um
projeto de ML, desde a preparação dos dados até o treinamento de modelos e sua
implantação em produção.
Para um iniciante, os componentes mais importantes do ecossistema são:
TensorFlow Core: O motor de baixo nível que realiza a computação numérica de forma
altamente eficiente, especialmente em hardware especializado como GPUs (Unidades
de Processamento Gráfico) e TPUs (Unidades de Processamento Tensorial).
Keras: Uma API de alto nível, integrada ao TensorFlow ( [Link] ), que permite
construir e treinar modelos de Machine Learning com uma sintaxe intuitiva e modular.
Keras simplifica drasticamente o processo, tornando-o acessível a um público mais
amplo. A maior parte do trabalho nas próximas semanas será feita com Keras.
[Link] : Um conjunto de ferramentas para criar pipelines de dados eficientes e
escaláveis. Ele permite carregar e pré-processar grandes volumes de dados de forma
otimizada, garantindo que o hardware de treinamento (GPU/TPU) não fique ocioso
esperando por dados.
TensorBoard: Uma suíte de visualização que permite monitorar o processo de
treinamento, visualizar a arquitetura do modelo, analisar métricas de performance e
muito more.
Dias 3-5: Tensores - A Base da Computação Numérica
Teoria:
A unidade central de dados no TensorFlow é o tensor. Um tensor pode ser entendido como
uma generalização de vetores e matrizes para um número arbitrário de dimensões.
Formalmente, é um array multidimensional com um tipo de dado uniforme (dtype).
Para facilitar a compreensão, pode-se fazer uma analogia direta com estruturas de dados
conhecidas:
Um tensor de rank 0 (ou 0 dimensões) é um escalar (um único número).
Um tensor de rank 1 (ou 1 dimensão) é um vetor (uma lista de números).
Um tensor de rank 2 (ou 2 dimensões) é uma matriz (uma tabela de números).
Um tensor de rank 3 pode ser visto como um cubo de números, e assim por diante.
Os atributos mais importantes de um tensor são 22:
shape : Uma tupla de inteiros que descreve o tamanho do tensor em cada uma de suas
dimensões (eixos). Por exemplo, uma matriz de 3 linhas e 4 colunas tem um shape (3,
4) .
rank : O número de dimensões do tensor. É o comprimento da tupla shape .
dtype : O tipo de dado dos elementos no tensor (ex: tf.float32 , tf.int32 ).
Embora os tensores sejam muito semelhantes aos arrays da biblioteca NumPy, eles
possuem duas vantagens cruciais que os tornam a base para o deep learning:
1. Aceleração por Hardware: Tensores podem ser alocados e processados em GPUs ou
TPUs, o que acelera massivamente as operações matemáticas (especialmente as de
álgebra linear) em comparação com a execução em CPUs.
2. Diferenciação Automática: O TensorFlow pode rastrear todas as operações realizadas
em tensores para calcular gradientes automaticamente, um processo fundamental para
o treinamento de modelos de machine learning (veremos isso em mais detalhes).
É importante notar que, embora o TensorFlow moderno opere em "modo eager" (execução
imediata), onde as operações são avaliadas assim que são definidas (como no Python
padrão), o conceito de um grafo computacional ainda é central para a otimização e
exportação de modelos. Cada operação em tensores pode ser vista como um nó em um
grafo, e os tensores são as arestas que fluem entre esses nós. Essa estrutura permite que o
TensorFlow analise, otimize e execute a computação de forma altamente eficiente.
Código de Exemplo Comentado:
Python
import tensorflow as tf
import numpy as np
# --- Criação de Tensores ---
# A partir de uma lista Python
tensor_rank2 = [Link]([, ], dtype=tf.float32)
print("Tensor criado a partir de lista:")
print(tensor_rank2)
# A partir de um array NumPy
numpy_array = [Link]([, ])
tensor_from_numpy = tf.convert_to_tensor(numpy_array)
print("\nTensor criado a partir de NumPy array:")
print(tensor_from_numpy)
# Tensores com valores específicos
tensor_zeros = [Link](shape=(2, 3)) # Matriz 2x3 de zeros
tensor_ones = [Link](shape=(3, 2)) # Matriz 3x2 de uns
print("\nTensor de zeros:")
print(tensor_zeros)
# --- Atributos do Tensor ---
print("\nAtributos do tensor_rank2:")
print(f" - Shape: {tensor_rank2.shape}")
print(f" - Rank: {[Link](tensor_rank2)}")
print(f" - Dtype: {tensor_rank2.dtype}")
# --- Operações Matemáticas ---
a = [Link]([, ])
b = [Link]([, ])
# Adição elemento a elemento
soma = [Link](a, b) # ou simplesmente a + b
print("\nSoma de tensores:")
print(soma)
# Multiplicação de matrizes
produto_matricial = [Link](a, b) # ou a @ b
print("\nProduto matricial:")
print(produto_matricial)
# --- Indexação e Fatiamento (Slicing) ---
# As regras são as mesmas de Python e NumPy
tensor_grande = [Link](,
,
)
print("\nTensor para fatiamento:")
print(tensor_grande)
# Pegando o elemento na segunda linha (índice 1) e terceira coluna (índice
2)
elemento = tensor_grande
print(f"\nElemento : {[Link]()}") #.numpy() para converter para
valor NumPy
# Pegando a primeira linha inteira
primeira_linha = tensor_grande[0, :]
print(f"Primeira linha: {primeira_linha.numpy()}")
# Pegando as duas primeiras colunas
duas_colunas = tensor_grande[:, 0:2]
print("\nDuas primeiras colunas:")
print(duas_colunas.numpy())
Dias 6-7: Revisão e Exercício Prático
Exercício Prático da Semana 3: Manipulação de Dados Fiscais com Tensores
Objetivo: Familiarizar-se com a criação de tensores e a execução de operações básicas,
aplicando-as a um cenário de dados fiscais simplificado.
Enunciado:
Considere o seguinte conjunto de dados simulados, representando a renda anual e as
despesas dedutíveis (em milhares de R$) de cinco contribuintes, armazenado em um array
NumPy:
Python
import numpy as np
dados_fiscais_np = [Link]([80.0, 20.0], # Contribuinte 1: R$80k de
renda, R$20k de despesas
[120.0, 35.0], # Contribuinte 2
[65.0, 15.0], # Contribuinte 3
[250.0, 80.0], # Contribuinte 4
[95.0, 22.0] # Contribuinte 5
])
Realize as seguintes tarefas usando TensorFlow:
1. Converta o array NumPy dados_fiscais_np em um [Link] chamado
dados_fiscais_tf . Verifique seu shape e dtype .
2. Calcule a renda média de todos os contribuintes. (Dica: primeiro, selecione a coluna de
rendas e depois use tf.reduce_mean ).
3. Calcule o total de despesas dedutíveis de todos os contribuintes. (Dica: use
tf.reduce_sum ).
4. Crie um novo tensor chamado renda_liquida que contenha a renda líquida (renda -
despesa) para cada contribuinte. Isso deve ser feito com uma única operação de
subtração de tensores.
5. Selecione os dados dos contribuintes que tiveram uma renda anual superior a
R$100.000. (Dica: use tf.boolean_mask ).
Semana 4: Construindo Modelos Preditivos com TensorFlow e Keras
Nesta semana, o conhecimento sobre tensores será aplicado para construir os primeiros
modelos de Machine Learning. Utilizando a API Keras, o processo de criação de modelos
de regressão linear e logística se torna notavelmente estruturado e acessível. A semelhança
no fluxo de trabalho para diferentes tipos de modelos demonstra o poder da abstração no
design de software, um conceito que se materializa na API Keras.
Dias 1-3: Regressão Linear - Prevendo Valores Contínuos
Teoria:
A regressão é uma tarefa de aprendizado supervisionado cujo objetivo é prever um valor de
saída contínuo. A Regressão Linear é o modelo mais fundamental para essa tarefa. Seu
objetivo é encontrar a melhor relação linear (uma linha, em duas dimensões, ou um
hiperplano, em mais dimensões) que descreva a relação entre as variáveis de entrada
(features) e a variável de saída (target). A equação que o modelo aprende é a familiar
equação da reta:
y=mx+b, onde y é a previsão, x é a entrada, m é o peso (inclinação) e b é o bias
(intercepto).
Para "aprender" os melhores valores para m e b, o modelo passa por um processo de
treinamento que envolve três componentes principais:
1. Modelo: A arquitetura que define como a saída é calculada a partir da entrada. Em
Keras, isso é tipicamente um [Link] com uma camada Dense
( [Link] ).
2. Função de Perda (Loss Function): Uma função que mede o quão "erradas" estão as
previsões do modelo em comparação com os valores reais. Para a regressão linear, a
métrica mais comum é o Erro Quadrático Médio (Mean Squared Error - MSE), que
calcula a média dos quadrados das diferenças entre os valores previstos e os reais.
3. Otimizador (Optimizer): Um algoritmo que ajusta os parâmetros do modelo (os pesos
m e o bias b) na direção que minimiza a função de perda. O algoritmo mais fundamental
é o Gradiente Descendente (Gradient Descent), que "desce a colina" da função de
perda para encontrar seu ponto mínimo.
O fluxo de trabalho em Keras para treinar um modelo é elegantemente simples e consiste
em três etapas principais:
1. Definir o Modelo: Construir a sequência de camadas que compõem o modelo.
2. Compilar o Modelo: Configurar o processo de treinamento especificando o optimizer ,
a loss e, opcionalmente, outras métricas a serem monitoradas.
3. Treinar o Modelo: Chamar o método [Link]() , fornecendo os dados de
treinamento. O Keras então cuida de todo o processo iterativo de apresentar os dados
ao modelo, calcular a perda, usar o otimizador para ajustar os pesos e repetir esse ciclo
por um número definido de épocas (iterações sobre todo o conjunto de dados).
Código de Exemplo Comentado: Prevendo Valor do Imóvel
Python
import tensorflow as tf
import numpy as np
import [Link] as plt
# --- 1. Gerar dados sintéticos ---
# Vamos simular que o valor de um imóvel (em R$ mil) é baseado em seu
tamanho (em m²)
# Valor = 50 + 2 * Tamanho + ruído
tamanho_m2 = [Link](50, 200, 100)
valor_real_milhar = 50 + 2 * tamanho_m2 + [Link](100) * 20 #
Adiciona ruído
# --- 2. Definir o Modelo com Keras ---
# Usamos um modelo sequencial, que é uma pilha linear de camadas.
# A camada 'Dense' é uma camada totalmente conectada, que implementa a
equação y = mx + b.
# 'units=1' significa que a camada terá uma saída (o valor previsto do
imóvel).
# 'input_shape=' informa que cada amostra de entrada tem 1 feature (o
tamanho em m²).
model = [Link]()
])
# --- 3. Compilar o Modelo ---
# 'adam' é um otimizador popular, uma variação eficiente do Gradiente
Descendente.
# 'mean_squared_error' (mse) é a função de perda padrão para regressão
linear.
[Link](optimizer='adam', loss='mean_squared_error')
# --- 4. Treinar o Modelo ---
# 'epochs=50' significa que o modelo verá todo o conjunto de dados 50 vezes.
# O treinamento é o processo de encontrar os melhores 'm' e 'b'.
history = [Link](tamanho_m2, valor_real_milhar, epochs=50, verbose=0)
print("Treinamento concluído.")
# --- 5. Avaliar e Usar o Modelo ---
# Fazer uma previsão para um novo imóvel de 120 m²
tamanho_novo = [Link]([[120.0]])
valor_previsto = [Link](tamanho_novo)
print(f"Previsão para um imóvel de 120 m²: R$ {valor_previsto:.2f} mil")
# Visualizar os resultados
[Link](tamanho_m2, valor_real_milhar, label='Dados Reais')
[Link](tamanho_m2, [Link](tamanho_m2), color='red', label='Linha de
Regressão (Modelo)')
[Link]('Tamanho (m²)')
[Link]('Valor (R$ mil)')
[Link]()
[Link]()
Dias 4-6: Regressão Logística - Classificação Binária
Teoria:
Apesar do nome, a Regressão Logística não é um algoritmo de regressão, mas sim de
classificação.29 Seu uso mais comum é para problemas de classificação binária, onde a
saída é uma de duas categorias (ex: 'sim'/'não', 1/0, 'fraude'/'não fraude').
O modelo funciona de forma muito parecida com a regressão linear, mas com uma adição
crucial: a saída da equação linear (mx+b) é passada por uma função de ativação
Sigmoide. A função Sigmoide comprime qualquer valor de entrada em um intervalo entre 0
e 1.29 Esse valor de saída pode ser interpretado como a probabilidade de a amostra
pertencer à classe positiva (classe 1). Se a probabilidade for > 0.5, a previsão é a classe 1;
caso contrário, é a classe 0.
Para treinar este modelo, a função de perda precisa ser adequada para problemas de
probabilidade. O MSE não é ideal aqui. Em vez disso, usa-se a Entropia Cruzada Binária
(Binary Cross-Entropy), que mede a "distância" entre duas distribuições de probabilidade
(a prevista pelo modelo e a real).
Notavelmente, o fluxo de trabalho em Keras permanece quase idêntico. As únicas
mudanças necessárias são:
1. Na camada de saída Dense , adicionar activation='sigmoid' .
2. Na compilação do modelo, usar loss='binary_crossentropy' .
Código de Exemplo Comentado: Classificação de Fraude
Python
import tensorflow as tf
import numpy as np
import [Link] as plt
# --- 1. Gerar dados sintéticos ---
# Simular que transações fraudulentas (1) tendem a ter valores mais altos
# e ocorrer em horários incomuns (ex: madrugada, representada como > 20)
num_amostras = 200
# Transações normais
valor_normal = [Link](loc=500, scale=200, size=num_amostras//2)
horario_normal = [Link](loc=14, scale=4, size=num_amostras//2)
# Transações fraudulentas
valor_fraude = [Link](loc=2000, scale=500, size=num_amostras//2)
horario_fraude = [Link](loc=3, scale=2, size=num_amostras//2)
features = [Link]([
np.column_stack([valor_normal, horario_normal]),
np.column_stack([valor_fraude, horario_fraude])
])
labels = [Link]([[Link](num_amostras//2), [Link](num_amostras//2)])
# --- 2. Definir o Modelo com Keras ---
# 'input_shape=' pois temos duas features: valor e horário.
# 'activation='sigmoid'' na camada de saída é o que define este como um
modelo de regressão logística.
model = [Link](, activation='sigmoid')
])
# --- 3. Compilar o Modelo ---
# 'loss='binary_crossentropy'' é a função de perda correta para
classificação binária.
# 'metrics=['accuracy']' para monitorar a acurácia durante o treinamento.
[Link](optimizer='adam', loss='binary_crossentropy', metrics=
['accuracy'])
# --- 4. Treinar o Modelo ---
history = [Link](features, labels, epochs=100, verbose=0)
print("Treinamento concluído.")
loss, accuracy = [Link](features, labels, verbose=0)
print(f"Acurácia final no conjunto de dados: {accuracy*100:.2f}%")
# --- 5. Usar o Modelo ---
# Prever uma nova transação: valor alto, horário da madrugada
transacao_suspeita = [Link]([])
probabilidade_fraude = [Link](transacao_suspeita)
print(f"\nProbabilidade de fraude para a transação suspeita:
{probabilidade_fraude*100:.2f}%")
# Prever uma nova transação: valor baixo, horário comercial
transacao_normal = [Link]([])
probabilidade_fraude_normal = [Link](transacao_normal)
print(f"Probabilidade de fraude para a transação normal:
{probabilidade_fraude_normal*100:.2f}%")
Dia 7: Revisão e Exercício Prático
O fato de que os códigos para regressão linear e logística são tão parecidos não é uma
coincidência, mas o resultado de um design de API deliberado. Keras abstrai o processo de
treinamento em um padrão consistente (definir, compilar, treinar), permitindo que o
desenvolvedor se concentre nas decisões que definem o modelo (arquitetura, função de
perda, otimizador), em vez de se preocupar com os detalhes da implementação do loop de
treinamento. Isso acelera drasticamente o desenvolvimento e reduz a carga cognitiva,
permitindo a aplicação de um mesmo fluxo de trabalho mental a uma vasta gama de
problemas.
Exercício Prático da Semana 4: Análise Preditiva de Sonegação Fiscal
Objetivo: Solidificar a compreensão da diferença entre regressão e classificação, aplicando
cada modelo a um problema fiscal relevante.
Enunciado:
Serão fornecidos dois conjuntos de dados sintéticos.
Parte 1: Regressão Linear - Previsão de Imposto Pago
Dados: Um conjunto de dados com as features faturamento_anual e
numero_de_funcionarios e a target valor_imposto_pago .
Tarefa: Construa, treine e avalie um modelo de regressão linear com Keras para prever
o valor_imposto_pago com base no faturamento. (Para simplificar, use apenas
faturamento_anual como entrada).
Parte 2: Regressão Logística - Classificação de Sonegação
Dados: Um conjunto de dados com as features inconsistencia_declaracao (um
score de 0 a 10) e transacoes_suspeitas (número de transações) e a target
sonegador (1 para sim, 0 para não).
Tarefa: Construa, treine e avalie um modelo de regressão logística com Keras para
classificar se um contribuinte é um potencial sonegador com base em suas features.
Para ambas as partes, após o treinamento, faça uma previsão para um novo dado
hipotético e interprete o resultado.
Semana 5: Uma Abordagem Alternativa com PyTorch
Após ganhar fluência na construção de modelos com a API de alto nível Keras, esta
semana introduz o PyTorch, um framework igualmente poderoso, mas com uma filosofia de
design diferente. O objetivo é aprofundar a compreensão dos mecanismos de Machine
Learning, mostrando uma abordagem mais explícita e flexível.
Dias 1-2: O Ecossistema PyTorch e sua Filosofia
Teoria:
PyTorch é um framework de código aberto, mantido principalmente pelo Facebook (Meta),
que se tornou extremamente popular, especialmente na comunidade de pesquisa
acadêmica. Sua popularidade deriva de várias características-chave:
Sensação "Pythonica": A API do PyTorch é projetada para se integrar perfeitamente
com o ecossistema Python. O código em PyTorch tende a ser mais intuitivo e legível
para quem já está familiarizado com Python e NumPy.
Grafos Computacionais Dinâmicos (Define-by-Run): Diferentemente das versões
mais antigas do TensorFlow que exigiam a definição de um grafo estático antes da
execução, o PyTorch constrói o grafo computacional dinamicamente, à medida que o
código é executado. Isso torna a depuração muito mais simples (é possível usar o
depurador do Python para inspecionar tensores em qualquer ponto) e permite a criação
de modelos com estruturas que mudam durante o treinamento, algo essencial para
áreas como o Processamento de Linguagem Natural.34
Flexibilidade e Controle: PyTorch oferece um nível de controle mais granular sobre o
processo de treinamento, como será visto na próxima semana. Embora isso exija
escrever um pouco mais de código em comparação com o [Link]() do Keras, essa
explicitude desmistifica o processo e dá ao desenvolvedor total controle sobre cada
etapa.
Dias 3-4: Tensores em PyTorch
Teoria:
Assim como no TensorFlow, a unidade fundamental de dados no PyTorch é o tensor
([Link]). Os conceitos são idênticos: um tensor é um array n-dimensional que pode
ser executado em GPUs para aceleração. As propriedades de
shape , dtype e device (o dispositivo onde o tensor está armazenado, ex: 'cpu' ou 'cuda')
são as mesmas.
Uma das características mais elogiadas do PyTorch é sua interoperabilidade perfeita com
NumPy. É trivialmente fácil converter um tensor PyTorch que está na CPU para um array
NumPy e vice-versa, muitas vezes sem nem mesmo copiar os dados. Eles podem
compartilhar o mesmo local na memória, tornando a integração com outras bibliotecas de
ciência de dados (como Scikit-learn, Pandas) extremamente eficiente.
Código de Exemplo Comentado (Comparativo):
O código a seguir replica as operações da Semana 3, mas com a sintaxe do PyTorch, para
facilitar a comparação direta.
Python
import torch
import numpy as np
# --- Criação de Tensores ---
# A partir de uma lista Python
tensor_rank2 = [Link]([, ], dtype=torch.float32)
print("Tensor criado a partir de lista:")
print(tensor_rank2)
# A partir de um array NumPy
numpy_array = [Link]([, ])
tensor_from_numpy = torch.from_numpy(numpy_array) # Compartilha memória
print("\nTensor criado a partir de NumPy array:")
print(tensor_from_numpy)
# Tensores com valores específicos
tensor_zeros = [Link](size=(2, 3)) # Matriz 2x3 de zeros
tensor_ones = [Link](size=(3, 2)) # Matriz 3x2 de uns
print("\nTensor de zeros:")
print(tensor_zeros)
# --- Atributos do Tensor ---
print("\nAtributos do tensor_rank2:")
print(f" - Shape: {tensor_rank2.shape}")
print(f" - Rank: {tensor_rank2.ndim}") # [Link]()
print(f" - Dtype: {tensor_rank2.dtype}")
print(f" - Device: {tensor_rank2.device}")
# --- Operações Matemáticas ---
a = [Link]([, ], dtype=torch.float32)
b = [Link]([, ], dtype=torch.float32)
# Adição elemento a elemento
soma = a + b
print("\nSoma de tensores:")
print(soma)
# Multiplicação de matrizes
produto_matricial = [Link](a, b) # ou a @ b
print("\nProduto matricial:")
print(produto_matricial)
# --- Interoperabilidade com NumPy ---
numpy_back = produto_matricial.numpy() # Converte de volta para NumPy array
print("\nTensor convertido para NumPy:")
print(type(numpy_back))
Dias 5-6: Autograd - Diferenciação Automática
Teoria:
O coração do PyTorch e o que permite o treinamento de redes neurais é seu motor de
diferenciação automática, chamado Autograd. Quando um tensor é criado com o parâmetro
requires_grad=True , o PyTorch começa a rastrear todas as operações realizadas com ele.
Esse rastreamento constrói um grafo computacional dinâmico.
Quando o treinamento está em andamento, o processo é o seguinte:
1. Forward Pass: Os dados de entrada passam pelo modelo para gerar uma previsão. O
PyTorch registra todas as operações nesse caminho.
2. Cálculo da Perda: A previsão é comparada com o valor real para calcular um valor de
perda (um escalar).
3. Backward Pass: Ao chamar o método .backward() no tensor de perda
( [Link]() ), o PyTorch percorre o grafo computacional de trás para frente (da
perda até as entradas) e calcula o gradiente (a derivada) da perda em relação a cada
tensor que tinha requires_grad=True .
4. Armazenamento do Gradiente: Os gradientes calculados são armazenados no atributo
.grad de cada tensor respectivo. Por exemplo, [Link] conterá a derivada da
perda em relação aos pesos do modelo.
Esses gradientes são exatamente o que o otimizador (como o Gradiente Descendente)
precisa para saber como ajustar os pesos do modelo para minimizar a perda.
Código de Exemplo Comentado: Desmistificando o Autograd
Python
import torch
# Criar tensores com rastreamento de gradiente ativado
# Imagine que 'w' e 'b' são os parâmetros de um modelo simples
w = [Link](2.0, requires_grad=True)
b = [Link](1.0, requires_grad=True)
# Definir uma entrada 'x'
x = [Link](3.0)
# Forward pass: calcular a previsão 'y_pred'
# A equação é y = w * x + b
y_pred = w * x + b
print(f"Previsão (y_pred): {y_pred}")
# Definir um valor real 'y_true' e calcular a perda (loss)
# Usaremos um erro quadrático simples: loss = (y_pred - y_true)^2
y_true = [Link](10.0)
loss = (y_pred - y_true)**2
print(f"Perda (loss): {loss}")
# Backward pass: calcular os gradientes
# Isso calcula d(loss)/dw e d(loss)/db
[Link]()
# Inspecionar os gradientes armazenados no [Link]
print(f"\nGradiente da perda em relação a 'w' (d(loss)/dw): {[Link]}")
print(f"Gradiente da perda em relação a 'b' (d(loss)/db): {[Link]}")
# Verificação manual (usando a regra da cadeia):
# loss = (w*x + b - y_true)^2
# d(loss)/dw = 2 * (w*x + b - y_true) * x = 2 * (2*3 + 1 - 10) * 3 = 2 *
(-3) * 3 = -18
# d(loss)/db = 2 * (w*x + b - y_true) * 1 = 2 * (2*3 + 1 - 10) * 1 = 2 *
(-3) * 1 = -6
# Os valores calculados pelo Autograd correspondem aos calculados
manualmente!
Dia 7: Revisão e Exercício Prático
A escolha entre TensorFlow e PyTorch muitas vezes se resume a uma preferência de estilo
e ao caso de uso. TensorFlow, com Keras, brilha na prototipagem rápida e na implantação
para produção, oferecendo um caminho mais direto do desenvolvimento à aplicação.
PyTorch, com sua natureza mais explícita, é frequentemente preferido em ambientes de
pesquisa onde a flexibilidade para experimentar arquiteturas de modelos e processos de
treinamento não convencionais é primordial.
Aspecto TensorFlow (com Keras) PyTorch
Abstração da API Alto nível, declarativa Nível mais baixo, imperativa (loop
( [Link]() ) explícito)
Loop de Abstraído, escondido do Explícito, escrito manualmente pelo
Treinamento usuário usuário
Definição do Estático (com Dinâmico (Define-by-Run), flexível e
Grafo [Link] ), otimizado fácil de depurar
para performance
Ecossistema e Ecossistema maduro para Forte na pesquisa, mas com
Produção produção (TFX, TF Serving) ecossistema de produção em rápido
crescimento (TorchServe)
Curva de Mais suave para iniciantes Requer compreensão do loop de
Aprendizagem devido ao Keras treinamento, um pouco mais íngreme
Depuração Pode ser mais complexa Mais fácil, permite o uso de
devido à abstração depuradores Python padrão
Exercício Prático da Semana 5: Cálculo de Gradiente Manual vs. Automático
Objetivo: Obter uma compreensão intuitiva e profunda do que o autograd faz,
comparando seus resultados com cálculos manuais.
Enunciado:
Sua tarefa é:
1. Crie tensores PyTorch para x_train e y_train com alguns dados simples (ex:
x_train = [Link]([1.0, 2.0, 3.0]) , y_train = [Link]([2.0, 4.0,
6.0]) ).
2. Inicialize os tensores w e b com valores aleatórios e requires_grad=True .
3. Parte 1 (Cálculo com Autograd):
Faça o forward pass para calcular y_pred .
Calcule a loss (MSE).
Chame [Link]() .
Imprima os valores de [Link] e [Link] .
4. Parte 2 (Cálculo Manual):
Usando os mesmos y_pred e y_train da Parte 1, calcule os gradientes para w e
b manualmente usando as fórmulas matemáticas fornecidas acima e as operações
de tensores do PyTorch.
Imprima os gradientes calculados manualmente.
5. Compare os resultados da Parte 1 e da Parte 2. Eles devem ser praticamente idênticos,
provando que autograd está implementando corretamente o cálculo do gradiente.
Semana 6: Construindo Modelos Preditivos com PyTorch
Esta semana final consolida todo o aprendizado, focando na construção de modelos da
"maneira PyTorch". Ao escrever o loop de treinamento explicitamente, o processo de
aprendizado de máquina, que pode parecer mágico quando se usa [Link]() , é
completamente desmistificado. Essa compreensão profunda é o que transforma um usuário
de frameworks em um verdadeiro praticante de Machine Learning, capaz de diagnosticar
problemas de treinamento e implementar lógicas personalizadas.
Dias 1-3: Regressão Linear - O Jeito PyTorch
Teoria:
Construir um modelo em PyTorch segue um padrão bem estabelecido que oferece um
excelente equilíbrio entre estrutura e flexibilidade.
1. Definindo o Modelo ( [Link] ): O padrão é criar uma classe que herda de
[Link] . Este é o bloco de construção base para todos os modelos de redes
neurais em PyTorch.40
No método __init__ , definem-se as camadas que o modelo usará. Para regressão
linear, isso é tipicamente uma única camada linear,
[Link](input_features, output_features) .
No método forward(self, x) , define-se como os dados de entrada x fluem
através das camadas definidas no __init__ para produzir uma saída.
2. Loss e Otimizador: A função de perda (ex: [Link]() ) e o otimizador (ex:
[Link]([Link](), lr=0.01) ) são instanciados como objetos
separados. O otimizador recebe os parâmetros do modelo ( [Link]() ) para
saber quais tensores ele deve atualizar.41
3. O Loop de Treinamento Explícito: O treinamento ocorre dentro de um loop for que
itera sobre o número desejado de épocas. Dentro de cada época, cinco passos
canônicos são executados 42:
1. optimizer.zero_grad() : Zera os gradientes da iteração anterior. PyTorch
acumula gradientes por padrão, então este passo é crucial para evitar que os
gradientes de diferentes batches se misturem.
2. Forward Pass ( outputs = model(inputs) ): Passa os dados de entrada pelo
modelo para obter as previsões.
3. Cálculo da Perda ( loss = criterion(outputs, targets) ): Calcula a perda
comparando as previsões com os alvos reais.
4. Backward Pass ( [Link]() ): Calcula os gradientes da perda em relação a
todos os parâmetros do modelo.
5. Atualização dos Pesos ( [Link]() ): O otimizador usa os gradientes
calculados para atualizar os pesos e biases do modelo.
Código de Exemplo Comentado: Regressão Linear em PyTorch
Python
import torch
import [Link] as nn
import numpy as np
import [Link] as plt
# --- 1. Gerar os mesmos dados sintéticos da Semana 4 ---
tamanho_m2 = [Link](50, 200, 100).reshape(-1, 1) # Precisa ser 2D para
o modelo
valor_real_milhar = (50 + 2 * tamanho_m2 + [Link](100, 1) * 20)
# Converter para tensores PyTorch
inputs = torch.from_numpy(tamanho_m2).float()
targets = torch.from_numpy(valor_real_milhar).float()
# --- 2. Definir o Modelo ---
class LinearRegressionModel([Link]):
def __init__(self):
super(LinearRegressionModel, self).__init__()
# 1 entrada (tamanho_m2), 1 saída (valor_previsto)
[Link] = [Link](1, 1)
def forward(self, x):
return [Link](x)
model = LinearRegressionModel()
# --- 3. Definir Loss e Otimizador ---
criterion = [Link]()
optimizer = [Link]([Link](), lr=0.0001)
# --- 4. Treinar o Modelo (Loop Explícito) ---
epochs = 100
for epoch in range(epochs):
# 1. Forward pass
outputs = model(inputs)
# 2. Calcular a perda
loss = criterion(outputs, targets)
# 3. Zerar gradientes, backward pass, atualizar pesos
optimizer.zero_grad()
[Link]()
[Link]()
if (epoch+1) % 10 == 0:
print(f'Epoch [{epoch+1}/{epochs}], Loss: {[Link]():.4f}')
# --- 5. Avaliar e Usar o Modelo ---
# O mé[Link]() remove o tensor do grafo computacional para que não seja
rastreado
predicted = model(inputs).detach().numpy()
[Link](tamanho_m2, valor_real_milhar, label='Dados Reais')
[Link](tamanho_m2, predicted, color='red', label='Linha de Regressão
(Modelo)')
[Link]('Tamanho (m²)')
[Link]('Valor (R$ mil)')
[Link]()
[Link]()
Dias 4-6: Regressão Logística - O Jeito PyTorch
Teoria:
A transição da regressão linear para a logística em PyTorch segue a mesma lógica vista
com Keras. A estrutura do código é quase idêntica, com duas modificações-chave:
1. Modelo: A função Sigmoide precisa ser aplicada à saída da camada linear. Isso pode
ser feito adicionando [Link]() como uma camada no __init__ e
aplicando-a no forward , ou simplesmente aplicando a função [Link]() à
saída no método forward .
2. Função de Perda: A função de perda apropriada é a Entropia Cruzada Binária,
[Link]() .
O loop de treinamento de cinco passos permanece exatamente o mesmo, demonstrando a
robustez e a generalidade desse padrão de treinamento.
Código de Exemplo Comentado: Classificação de Fraude em PyTorch
Python
import torch
import [Link] as nn
import numpy as np
# --- 1. Gerar os mesmos dados sintéticos da Semana 4 ---
num_amostras = 200
valor_normal = [Link](loc=500, scale=200, size=num_amostras//2)
horario_normal = [Link](loc=14, scale=4, size=num_amostras//2)
valor_fraude = [Link](loc=2000, scale=500, size=num_amostras//2)
horario_fraude = [Link](loc=3, scale=2, size=num_amostras//2)
features = [Link]([np.column_stack([valor_normal, horario_normal]),
np.column_stack([valor_fraude, horario_fraude])])
labels = [Link]([[Link](num_amostras//2),
[Link](num_amostras//2)]).reshape(-1, 1)
# Converter para tensores PyTorch
inputs = torch.from_numpy(features).float()
targets = torch.from_numpy(labels).float()
# --- 2. Definir o Modelo ---
class LogisticRegressionModel([Link]):
def __init__(self):
super(LogisticRegressionModel, self).__init__()
# 2 entradas (valor, horario), 1 saída (probabilidade)
[Link] = [Link](2, 1)
def forward(self, x):
# Aplicar a função sigmoide na saída da camada linear
return [Link]([Link](x))
model = LogisticRegressionModel()
# --- 3. Definir Loss e Otimizador ---
criterion = [Link]()
optimizer = [Link]([Link](), lr=0.001)
# --- 4. Treinar o Modelo ---
epochs = 500
for epoch in range(epochs):
outputs = model(inputs)
loss = criterion(outputs, targets)
optimizer.zero_grad()
[Link]()
[Link]()
if (epoch+1) % 100 == 0:
print(f'Epoch [{epoch+1}/{epochs}], Loss: {[Link]():.4f}')
# --- 5. Usar o Modelo ---
with torch.no_grad(): # Desativa o cálculo de gradiente para inferência
# Prever uma transação suspeita
transacao_suspeita = [Link]([[2500.0, 2.0]], dtype=torch.float32)
prob_fraude = model(transacao_suspeita)
print(f"\nProbabilidade de fraude para transação suspeita:
{prob_fraude.item()*100:.2f}%")
# Prever uma transação normal
transacao_normal = [Link]([[150.0, 15.0]], dtype=torch.float32)
prob_normal = model(transacao_normal)
print(f"Probabilidade de fraude para transação normal:
{prob_normal.item()*100:.2f}%")
Dia 7: Revisão e Exercício Prático
Exercício Prático da Semana 6: Reimplementando a Análise de Sonegação com
PyTorch
Objetivo: Consolidar o conhecimento de PyTorch, reimplementando os modelos da
Semana 4 para uma comparação direta de frameworks.
Enunciado:
Utilize os mesmos dois conjuntos de dados sintéticos do exercício da Semana 4 ("Análise
Preditiva de Sonegação Fiscal").
Parte 1: Regressão Linear com PyTorch
Tarefa: Construa, treine e avalie um modelo de regressão linear em PyTorch para prever
o valor_imposto_pago com base no faturamento_anual . Você deve:
1. Criar uma classe que herde de [Link] .
2. Definir a função de perda [Link] e um otimizador.
3. Escrever o loop de treinamento explícito.
Parte 2: Regressão Logística com PyTorch
Tarefa: Construa, treine e avalie um modelo de regressão logística em PyTorch para
classificar um contribuinte como sonegador (1 ou 0). Você deve:
1. Criar uma nova classe de modelo (ou adaptar a anterior) para incluir a ativação
Sigmoide.
2. Usar a função de perda [Link] .
3. Escrever o loop de treinamento.
Ao final, compare a estrutura do código e os resultados com os obtidos na Semana 4. Esta
reimplementação direta solidificará as diferenças e semelhanças entre as abordagens de
Keras e PyTorch.
Conclusão: Consolidando seu Conhecimento para o Concurso
Ao longo destas seis semanas, a jornada de aprendizado progrediu desde os blocos de
construção fundamentais do Python até a implementação de modelos preditivos em dois
dos mais importantes frameworks de Machine Learning do mundo. A habilidade adquirida
vai além da simples memorização de código; ela reside na compreensão dos princípios que
governam a manipulação de dados e o treinamento de modelos.
Os principais conceitos que devem ser revisados e que possuem alta probabilidade de
serem abordados em um concurso são:
Python: A distinção crítica entre estruturas de dados mutáveis (listas, dicionários) e
imutáveis (tuplas) e as implicações práticas dessa diferença em termos de segurança e
performance. Os princípios da Programação Orientada a Objetos, especialmente a ideia
de encapsular dados (atributos) e comportamentos (métodos) em classes.
TensorFlow e PyTorch: O conceito de tensor como um array multidimensional, a
unidade básica de dados em ambos os frameworks. O propósito fundamental de uma
função de perda (medir o erro do modelo) e de um otimizador (minimizar esse erro). A
diferença conceitual e de aplicação entre regressão linear (prever valores contínuos) e
regressão logística (classificar em categorias binárias).
Para continuar a preparação, a recomendação é refazer os exercícios práticos de cada
semana, focando em explicar para si mesmo o propósito de cada linha de código. Buscar
pequenos conjuntos de dados públicos, como os disponíveis na plataforma Kaggle, e tentar
aplicar esses modelos simples a novos problemas é uma excelente forma de solidificar o
conhecimento.
O domínio dessas ferramentas representa um diferencial competitivo significativo. A
capacidade de analisar dados, construir modelos e extrair insights é uma competência cada
vez mais valorizada na administração tributária moderna. Com a base sólida construída
através deste plano, o candidato está bem preparado não apenas para responder às
questões da prova, mas para aplicar esse conhecimento na resolução de problemas reais e
complexos que encontrará em sua carreira como Auditor Tributário.