Baixe o app para aproveitar ainda mais
Prévia do material em texto
PRÁTICA E LABORATÓRIO II 1 Prática e Laboratório II PRÁTICA E LABORATÓRIO II 2 Prática e Laboratório II 1. Orientação a Objetos 1.1 Trabalhando com Classes O paradigma orientado a objetos considera o objeto como entidade fundamental de todo o processo de programação por reúso. Isto porque o objeto consegue responder a mensagens de acordo com suas características (atributos) e suas ações (métodos) que estão incorporados nele mesmo, caracterizando, portanto, um dos princípios básicos da POO, programação orientada a objetos, denominado “encapsulamento”. Outro princípio importante é a herança (este iremos destacar mais adiante), em que um objeto “filho” poderá ser criado baseado em um objeto “pai”, herdando todos os seus atributos e métodos, além de apresentar os seus próprios. Por fim, o outro princípio é denominado “polimorfismo”, que é caracterizado quando duas ou mais classes distintas têm métodos de mesmo nome, de forma que uma função possa utilizar um objeto de qualquer uma das classes polimórficas sem a necessidade de tratar de forma diferenciada cada classe do objeto. Neste contexto, surge outro termo importante no paradigma orientado a objetos: classes. Classes representam um conjunto de objetos com atributos e métodos semelhantes e que tenham um objetivo em comum. Por exemplo: na disciplina matemática existem 45 alunos matriculados regularmente. A disciplina matemática seria a classe e os 45 alunos matriculados nesta turma seriam os objetos dessa classe. Na linguagem de programação Phyton, uma classe é criada a partir da palavra reservada class. Observe a sintaxe: PRÁTICA E LABORATÓRIO II 3 #Criando classe class Animal: pass Neste exemplo, foi utilizada a palavra reservada pass, que expressa um bloco vazio, ou seja, sem função alguma. Note que a classe criada se inicia com letra maiúscula. Embora esteja vazia, a classe Animal seria criada: <class __main__.Animal at 0x00B04360> Mesmo vazia, a classe recém-criada já apresenta dois atributos: [‘__doc__’, ‘__module__’] Vejamos um exemplo prático: class Pessoa: def __init__(self, nome, idade): self.nome=nome self.idade=idade def obterNome(self): return self.nome def obterIdade(self): return self.idade pessoa = Pessoa('Pedro', 49) print pessoa.obterNome( ) print pessoa.obterIdade( ) raw_input( ) PRÁTICA E LABORATÓRIO II 4 No exemplo, criou-se a classe Pessoa, tendo sido definidos em sua inicialização os atributos Nome e Idade. Os métodos e/ou atributos da classe são obrigados a passar por parâmetro ou argumento a palavra self antes de mais nada. Isso é convencionado pelo próprio Phyton. Foram definidas duas funções, obterNome( ) e obterIdade( ), para, respectivamente, receberem um valor para o nome e para a idade. Vejamos o programa no exemplo abaixo que manipula classes e objetos em Phyton: class Gelatina: def __init__(self, tam, cor, sabor): self.tam=tam self.cor=cor self.sabor=sabor gel1 = Gelatina ("pequena", "vermelha", "morango") gel2 = Gelatina ("media", "amarela", "abacaxi") gel3 = Gelatina ("grande", "roxa", "uva") print gel1.tam, print gel1.cor, print gel1.sabor print gel2.tam, print gel2.cor, print gel2.sabor print gel3.tam, print gel3.cor, print gel3.sabor PRÁTICA E LABORATÓRIO II 5 raw_input ( ) A saída produzida pelo programa é a seguinte: Vejamos outro exemplo prático de orientação a objetos, agora para permitir cadastro por meio de entrada de dados: class Produto: def __init__(self, cod, nome, quant): self.cod=cod self.nome=nome self.quant=quant codigo = raw_input('Entre com o Codigo: ') nome = raw_input('Nome do produto: ') quantidade = raw_input('Qual a quantidade? ') produt = Produto(codigo, nome, quantidade) print '\n' PRÁTICA E LABORATÓRIO II 6 print 'Dados de Saida:\n' print 'Codigo: ' + produt.cod print 'Produto: ' + produt.nome print 'Quantidade: ' + produt.quant raw_input ( ) 1.2 Herança As classes Phyton têm por propriedade herdar as características (atributos) e as ações (métodos) de outras classes. Assim, se uma classe “b” é criada a partir de uma classe “a”, dizemos que a classe “a” é a classe pai e a classe “b” é a classe filha, e, portanto, esta última herda os atributos e métodos da classe pai. Embora uma classe filha tenha por preceito herdar os atributos e métodos da classe pai, a classe filha também pode apresentar seus próprios atributos e métodos. PRÁTICA E LABORATÓRIO II 7 Na figura anterior, pode-se observar que a classe Animal é a classe pai e as classes Cachorro, Papagaio e Mosca são classes filhas. Portanto, por exemplo, a classe filha Cachorro apresenta como atributos espécie e cor (herdadas da classe pai) e corPelo (próprio da classe). Tecnicamente falando, a classe pai é denominada Superclasse e a classe filha é denominada Subclasse. Dessa forma, podemos observar o seguinte exemplo: # definindo a classe Animal: class Animal: def __init__(self): # codigo para o init aqui # metodos de Animal aqui #definindo a classe Mamifero, herdando de Animal class Mamifero(Animal): def __init__(self): # definindo Felino PRÁTICA E LABORATÓRIO II 8 class Felino(Mamifero): # definindo Gato class Gato(Felino): 1.3 Importando módulos Um dos recursos importantes do Phyton é a importação de módulos aos programas. Os módulos são carregados través da instrução import, como já foi apresentado ao longo do aprendizado da linguagem de programação Phyton. import <módulo_1> [ as nome_1 ] [, <módulo_2> [ as nome_2]] ... from <módulo> import [<ident_1>, <ident_2>, ...] Qualquer forma utilizada é considerada uma sintaxe válida para o Phyton. Ainda podemos utilizar este recurso da forma mais simplificada, por meio do símbolo “*”, que, na verdade, indica a importação de todos os métodos, funções etc., para estarem disponíveis no programa fonte que fez a importação. from <módulo> import * >>> import fibonacci >>> fibonacci.fib(1000) 1 1 2 3 5 8 13 21 34 55 89 144 233 377 610 987 >>> dir() ['__builtins__', '__doc__', '__name__', 'fibonacci'] Importando tudo para o raiz, temos: >>> from fibonacci import * PRÁTICA E LABORATÓRIO II 9 >>> fib(1000) 1 1 2 3 5 8 13 21 34 55 89 144 233 377 610 987 >>> dir() ['__builtins__', '__doc__', '__name__', 'fib', 'fib2'] Através da instrução dir( ), foi permitido visualizar todos os detalhes acerca do módulo utilizado (métodos, funções etc.). A maioria dos programas de computador desenvolvidos em Phyton faz a importação de módulos, os quais, em outras linguagens de programação, por exemplo, C, denominam-se bibliotecas. A seguir, iremos explorar um pouco mais o recurso de módulos, permitindo a visualização de alguns bem interessantes. 1.4 Módulos sys e re O módulo sys possui várias funções, o que permite que haja uma interação com o próprio interpretador Phyton. Podemos visualizar inicialmente as funções ps1 e ps2, que têm por objetivo definir os prompts utilizados pelo interpretador Phyton (“>>>” e “...”). >>>import sys >>> sys.ps1 = '>' > sys.ps2 = '. ' > for i in range(10) . print i Neste exemplo, o prompt “>>>” foi substituído por “>” (sys.ps1) e o prompt “...” foi substituído por “.” (sys.ps2). Só para enfatizar, o padrão seria da seguinte forma: PRÁTICA E LABORATÓRIO II 10 >>> for i in range(10) ... print i No exemplo a seguir, iremos utilizar a função argv, que tem por objetivo armazenar os argumentos passados pela linha de comandos na lista de strings argv[], onde o primeiro elemento é o nome do programa chamado, seguido pelos outros argumentos que sejam necessários para o carregamento do programa. # Modulo args.py from sys import argv print sys.argv Então, o primeiro argumento seria o nome do programa, neste caso, args.py, e os demais argumentos seriam eventuais valores que seriam utilizados, por exemplo, em um programa Phyton. $ python args.py 2 5 -3 ['args.py', '2', '5', '-3'] Outro recurso interessante é a função path, cujo objetivo é apresentar os caminhos utilizados pelo Phyton para buscar os módulos solicitados pela instrução import. Dessa forma, teremos: >>> sys.path ['', '/usr/lib64/python25.zip', '/usr/lib64/python2.5', '/usr/lib64/python2.5/plat-linux2', '/usr/lib64/python2.5/libtk', '/usr/lib64/python2.5/lib-dynload', '/usr/lib64/python2.5/site-packages', '/usr/lib64/python2.5/site-packages/gtk-2.0'] PRÁTICA E LABORATÓRIO II 11 Caso você queira ter acesso a informações do Phyton, como parâmetros de instalação, por exemplo, podemos visualizar plataforma, prefixo e versão do interpretador Phyton utilizada. Observe o código a seguir: >>> sys.platform, sys.prefix, sys.version ('linux2', '/usr', '2.5.1 (r251:54863, Jan 4 2017, 19:00:19) \n[GCC 4.1.2]') Outro recurso interessante está na redefinição dos dispositivos padrões de entrada e saída do programa. Para tanto, existem as funções stdin, stdout e stderr. Vejamos o exemplo a seguir: >>> sys.stdout.write('Interpretador Phyton') Interpretador Phyton>>> A função exit pode ser utilizada para encerrar uma seção do programa Phyton diretamente. Observe a seguir: >>> sys.exit() $ _ Quanto ao módulo re (regular expression), ele tem por objetivo fornecer ferramentas para realizar a filtragem de strings através de expressões regulares. A primeira função interessante desse módulo é conhecida como findall, que permite encontrar a ocorrência de certa string, filtrando-a através de uma expressão regular. Vejamos o exemplo abaixo: >>> import re >>> re.findall(r'\bf[a-z]*', 'which foot or hand fell fastest') ['foot', 'fell', 'fastest'] PRÁTICA E LABORATÓRIO II 12 Outra função interessante é conhecida como sub, cujo objetivo é substituir uma ocorrência de certa string por outra qualquer. Observe: >>> re.sub(r'\bAMD', r'AuthenticAMD', 'AMD Turion(tm) 64 X2 Mobile') 'AuthenticAMD Turion(tm) 64 X2 Mobile' A função sub permite substituir duas ocorrências de uma string, assumindo apenas uma delas. Veja: >>> re.sub(r'(\b[a-z]+) \1', r'\1', 'Hoje esta um dia dia ensolarado') 'Hoje esta um dia ensolarado' 1.5 Módulos math e random O módulo math, como já foi visto no estudo da linguagem de programação Phyton, permite que sejam acessadas diversas funções matemáticas e constantes numéricas para o programa que esteja sendo utilizado. Para visualizar as diversas funções matemáticas que existem associadas ao módulo math, utilizaremos a instrução dir( ). >>> dir(math) ['__doc__', '__file__', '__name__', 'acos', 'asin', 'atan', 'atan2', 'ceil', 'cos', 'cosh', 'degrees', 'e', 'exp', 'fabs', 'floor', 'fmod', 'frexp', 'hypot', 'ldexp', 'log', 'log10', 'modf', 'pi', 'pow', 'radians', 'sin', 'sinh', 'sqrt', 'tan', 'tanh'] Vamos recordar, através do programa exemplo a seguir, uma aplicação que utiliza uma função matemática, neste caso, o seno de um número. PRÁTICA E LABORATÓRIO II 13 import math def Sin(a): # Calcula seno de angulo em graus ang = a*math.pi/180. # mesmo que radians() return math.sin(ang) Dessa forma, ao utilizar a função seno passando como parâmetro um número para que este seno seja calculado, o mesmo será efetivado. >>> Sin(30) 0.49999999999999994 >>> Sin(60) 0.8660254037844386 Podemos também manipular o módulo random, cujo objetivo é permitir a geração de números aleatórios. A função choice( ) permite que seja escolhido de forma aleatória um elemento da lista. Observe o programa exemplo a seguir: >>> import random >>> random.choice(['goiaba', 'laranja', 'abacate', 'pera']) 'pera' Ao executar o mesmo programa outra vez, nada impede que a mesma saída aconteça. No entanto, outra saída diferente também será possível. Veja o exemplo: >>> import random >>> random.choice(['goiaba', 'laranja', 'abacate', 'pera']) 'goiaba' Outra função interessante é a randrange( ), cujo objetivo é gerar um número inteiro aleatório, considerando certo intervalo, no nosso caso, 0 e n-1, onde n=10: PRÁTICA E LABORATÓRIO II 14 >>> random.randrange(10) 3 1.6 Módulo para internet – urllib2 e smtplib A linguagem de programação Phyton oferece módulos especiais para manipulação de internet. O urllib2( ) permite criar modos de navegação na internet, carregar páginas web, realizar pesquisas etc. >>> import urllib2 >>> for line in urllib2.urlopen(’http://tycho.usno.navy.mil/cgibin/ timer.pl’): ... if ’EST’ in line: # look for Eastern Standard Time ... print line <BR>Jan. 05, 09:43:38 PM EST Outra função interessante é o smtplib( ), que permite o envio de e-mails através de um servidor smtp. >>> import smtplib >>> server = smtplib.SMTP(’localhost’) >>> server.sendmail(’professor@boente.eti.br’, ’alfredo.boente@estacio.br’, """To: alfredo.boente@estacio.br From: professor@boente.eti.br Envio de e-mail. """) >>> server.quit() PRÁTICA E LABORATÓRIO II 15 1.7 Módulo datetime O módulo datetime fornece classes para manipulação de datas e horas nos mais variados formatos possíveis. A função date (ano, mês, dia) cria um objeto do tipo data. Observe o código em Phyton a seguir: >>> from datetime import date >>> hoje = date.today() >>> nascimento = date(1968, 12, 25) >>> idade = hoje – nascimento >>> print ‘Sua idade e %d anos’ % int(idade.days/365) A resposta será: “Sua idade é 49 anos”. 1.8 Módulos zlib e timeit O módulo zlib permite trabalhar com dados comprimidos, comprimindo e descomprimindo sempre que necessário. A função compress(string) serve para comprimir dados, e a função decompress(string) serve para descomprimir dados. Veja o exemplo: >>> from zlib import compress, decompress >>> s = “Em 23 de outubro de 1906, o brasileiro de Minas Gerais Alberto Santos Dumont voou cerca de 60 metros e a uma altura de dois a tres metros com seu 14 Bis, no Campo de Bagatelle em Paris.” >>>len(s) A resposta será 187 caracteres. Vamos comprimir a mensagem e reduzir de 187 para 143 caracteres. Observe: PRÁTICA E LABORATÓRIO II 16 >>> z = compress(s) >>> len(z) 2. Árvores de Decisão e Redes Neurais 2.1 Entendendo a Árvore de Decisão Uma árvore de decisão é uma estrutura em forma de árvore usada para representar um número de possíveis caminhos de decisão. Cada um desses caminhos leva a um resultado específico.Trata-se de um dos modelos mais práticos e mais usados em inferência indutiva, um dos métodos preferidos dos cientistas de dados. As árvores de decisão são treinadas de acordo com um conjunto de regras de aprendizagem, e posteriormente outros exemplos são classificados de acordo com elas. Vamos considerar o clássico exemplo da construção de uma árvore de decisão de jogar tênis, que utiliza como parâmetros de decisão dias passados nos quais treinos de tênis foram realizados. PRÁTICA E LABORATÓRIO II 17 Através desse simples exemplo, é possível construir a seguinte árvore de decisão: A relação existente entre os elementos da árvore, nó e folhas, e entre os atributos, valores e classificações pode ser compreendida da seguinte forma: PRÁTICA E LABORATÓRIO II 18 Então, a classificação de um exemplo, de acordo com esta árvore, é feita da seguinte maneira: O atributo “aspecto” tem como valor “sol”, e o atributo “humidade” tem como valor “elevada”. O evento em si é classificado como “não”, ou seja, quando teve sol e humidade elevada, não teve treino de tênis. Note que neste exemplo os atributos PRÁTICA E LABORATÓRIO II 19 “temperatura” e “vento” não foram considerados, pois não são relevantes, já que não houve treino de tênis. Também é possível o uso de conectivos lógicos, como representar conjunções e disjunções de atributos. Vamos então visualizar o exemplo de árvore de decisão no qual os atributos aspecto “sol” e vento “fraco” são considerados. Se alterarmos simplesmente o conectivo lógico de conjunção para disjunção, teremos então como resposta a seguinte árvore de decisão. PRÁTICA E LABORATÓRIO II 20 Árvores de decisão, portanto, podem ser utilizadas para auxiliar no processo de tomada de decisão em qualquer situação que envolva o processo decisório. É importante saber que para construirmos uma árvore de decisão é necessário saber quais perguntas fazer e em que ordem. Em Phyton, basta definir uma função e colocar tudo o quer que seja parametrizado dentro dela. Veja o exemplo abaixo: def entropia(class_probabilidades): ””” dada a lista de probabilidades de classes, compute a entopia ””” return sum(-p*math.log(p, 2) for p in class_probabilidades 2.2 Particionamento de Árvore de Decisão Matematicamente, segundo Grus (2016), se um dado S é dividido em subconjuntos s1, s2... sn contendo porções de dados q1, q1... qn, então estamos computando a entropia da partição como uma soma ponderada. Observe o código Phyton abaixo, que cria a entropia, as probabilidades e as partições necessárias para o nosso programa: def entropy(class_probabilities): """given a list of class probabilities, compute the entropy""" return sum(-p * math.log(p, 2) for p in class_probabilities if p) def class_probabilities(labels): total_count = len(labels) return [count / total_count for count in Counter(labels).values()] PRÁTICA E LABORATÓRIO II 21 def data_entropy(labeled_data): labels = [label for _, label in labeled_data] probabilities = class_probabilities(labels) return entropy(probabilities) def partition_entropy(subsets): """find the entropy from this partition of data into subsets""" total_count = sum(len(subset) for subset in subsets) return sum( data_entropy(subset) * len(subset) / total_count for subset in subsets ) 2.3 Criando Árvores de Decisão Para a criação da árvore de decisão, é necessário ter uma boa base de dados para poder manipular. Segue um exemplo utilizado por Grus (2016): print ("ARVORE DE DECISAO PHYTON\n") if __name__ == "__main__": inputs = [ ({'level':'Senior','lang':'Java','tweets':'no','phd':'no'}, False), ({'level':'Senior','lang':'Java','tweets':'no','phd':'yes'}, False), ({'level':'Mid','lang':'Python','tweets':'no','phd':'no'}, True), ({'level':'Junior','lang':'Python','tweets':'no','phd':'no'}, True), ({'level':'Junior','lang':'R','tweets':'yes','phd':'no'}, True), ({'level':'Junior','lang':'R','tweets':'yes','phd':'yes'}, False), ({'level':'Mid','lang':'R','tweets':'yes','phd':'yes'}, True), ({'level':'Senior','lang':'Python','tweets':'no','phd':'no'}, False), ({'level':'Senior','lang':'R','tweets':'yes','phd':'no'}, True), ({'level':'Junior','lang':'Python','tweets':'yes','phd':'no'}, True), PRÁTICA E LABORATÓRIO II 22 ({'level':'Senior','lang':'Python','tweets':'yes','phd':'yes'},True), ({'level':'Mid','lang':'Python','tweets':'no','phd':'yes'}, True), ({'level':'Mid','lang':'Java','tweets':'yes','phd':'no'}, True), ({'level':'Junior','lang':'Python','tweets':'no','phd':'yes'},False) ] A partir daí, precisamos encontrar a partição com entropia mínima para todos os conjuntos de dados: for key in ['level','lang','tweets','phd']: print key, partition_entropy_by(inputs, key) A menor entropia vem da divisão baseada em level, então precisamos fazer uma subárvore para cada valor level possível. senior_inputs = [(input, label) for input, label in inputs if input["level"] == "Senior"] for key in ['lang', 'tweets', 'phd']: print key, partition_entropy_by(senior_inputs, key) 2.4 Trabalhando com Floresta Aleatória Considerando que árvores de decisão podem se ajustar quase que perfeitamente, seus dados em treinamento, não nos surpreende quando eles tentam se reajustar. Vejamos o código completo do programa que implementa uma solução de árvore de decisão: from __future__ import division from collections import Counter, defaultdict PRÁTICA E LABORATÓRIO II 23 from functools import partial import math, random def entropy(class_probabilities): """given a list of class probabilities, compute the entropy""" return sum(-p * math.log(p, 2) for p in class_probabilities if p) def class_probabilities(labels): total_count = len(labels) return [count / total_count for count in Counter(labels).values()] def data_entropy(labeled_data): labels = [label for _, label in labeled_data] probabilities = class_probabilities(labels) return entropy(probabilities) def partition_entropy(subsets): """find the entropy from this partition of data into subsets""" total_count = sum(len(subset) for subset in subsets) return sum( data_entropy(subset) * len(subset) / total_count for subset in subsets ) def group_by(items, key_fn): """returns a defaultdict(list), where each input item is in the list whose key is key_fn(item)""" groups = defaultdict(list) for item in items: key = key_fn(item) groups[key].append(item) PRÁTICA E LABORATÓRIO II 24 return groups def partition_by(inputs, attribute): """returns a dict of inputs partitioned by the attribute each input is a pair (attribute_dict, label)""" return group_by(inputs, lambda x: x[0][attribute]) def partition_entropy_by(inputs,attribute): """computes the entropy corresponding to the given partition""" partitions = partition_by(inputs, attribute) returnpartition_entropy(partitions.values()) def classify(tree, input): """classify the input using the given decision tree""" # if this is a leaf node, return its value if tree in [True, False]: return tree # otherwise find the correct subtree attribute, subtree_dict = tree subtree_key = input.get(attribute) # None if input is missing attribute if subtree_key not in subtree_dict: # if no subtree for key, subtree_key = None # we'll use the None subtree subtree = subtree_dict[subtree_key] # choose the appropriate subtree return classify(subtree, input) # and use it to classify the input def build_tree_id3(inputs, split_candidates=None): PRÁTICA E LABORATÓRIO II 25 # if this is our first pass, # all keys of the first input are split candidates if split_candidates is None: split_candidates = inputs[0][0].keys() # count Trues and Falses in the inputs num_inputs = len(inputs) num_trues = len([label for item, label in inputs if label]) num_falses = num_inputs - num_trues if num_trues == 0: # if only Falses are left return False # return a "False" leaf if num_falses == 0: # if only Trues are left return True # return a "True" leaf if not split_candidates: # if no split candidates left return num_trues >= num_falses # return the majority leaf # otherwise, split on the best attribute best_attribute = min(split_candidates, key=partial(partition_entropy_by, inputs)) partitions = partition_by(inputs, best_attribute) new_candidates = [a for a in split_candidates if a != best_attribute] # recursively build the subtrees subtrees = { attribute : build_tree_id3(subset, new_candidates) for attribute, subset in partitions.iteritems() } PRÁTICA E LABORATÓRIO II 26 subtrees[None] = num_trues > num_falses # default case return (best_attribute, subtrees) def forest_classify(trees, input): votes = [classify(tree, input) for tree in trees] vote_counts = Counter(votes) return vote_counts.most_common(1)[0][0] print ("ARVORE DE DECISAO PHYTON\n") if __name__ == "__main__": inputs = [ ({'level':'Senior','lang':'Java','tweets':'no','phd':'no'}, False), ({'level':'Senior','lang':'Java','tweets':'no','phd':'yes'}, False), ({'level':'Mid','lang':'Python','tweets':'no','phd':'no'}, True), ({'level':'Junior','lang':'Python','tweets':'no','phd':'no'}, True), ({'level':'Junior','lang':'R','tweets':'yes','phd':'no'}, True), ({'level':'Junior','lang':'R','tweets':'yes','phd':'yes'}, False), ({'level':'Mid','lang':'R','tweets':'yes','phd':'yes'}, True), ({'level':'Senior','lang':'Python','tweets':'no','phd':'no'}, False), ({'level':'Senior','lang':'R','tweets':'yes','phd':'no'}, True), ({'level':'Junior','lang':'Python','tweets':'yes','phd':'no'}, True), ({'level':'Senior','lang':'Python','tweets':'yes','phd':'yes'},True), ({'level':'Mid','lang':'Python','tweets':'no','phd':'yes'}, True), ({'level':'Mid','lang':'Java','tweets':'yes','phd':'no'}, True), ({'level':'Junior','lang':'Python','tweets':'no','phd':'yes'},False) ] PRÁTICA E LABORATÓRIO II 27 for key in ['level','lang','tweets','phd']: print key, partition_entropy_by(inputs, key) print senior_inputs = [(input, label) for input, label in inputs if input["level"] == "Senior"] for key in ['lang', 'tweets', 'phd']: print key, partition_entropy_by(senior_inputs, key) print print "building the tree" tree = build_tree_id3(inputs) print tree print "Junior / Java / tweets / no phd", classify(tree, { "level" : "Junior", "lang" : "Java", "tweets" : "yes", "phd" : "no"} ) print "Junior / Java / tweets / phd", classify(tree, { "level" : "Junior", "lang" : "Java", "tweets" : "yes", "phd" : "yes"} ) print "Intern", classify(tree, { "level" : "Intern" } ) print "Senior", classify(tree, { "level" : "Senior" } ) raw_input( ) PRÁTICA E LABORATÓRIO II 28 Observe a saída do programa: Neste caso, através da técnica floresta aleatória, podemos construir várias árvores de decisão, deixando-as escolher como classificar as entradas possíveis. def florest_classify(trees, input): votes = [classify(tree, input) for tree in trees] vote_counts = Counter(votes) return vote_counts.most_common(1)[0][0] 2.5 Entendendo Redes Neurais Uma rede neural artificial é um modelo preditivo motivado pela forma como o cérebro humano funciona. (GRUS, 2016). PRÁTICA E LABORATÓRIO II 29 Redes neurais artificiais consistem em neurônios artificiais que desenvolvem cálculos similares acerca de suas entradas. Esse tipo de estrutura é muito útil para os cientistas de dados, pois podem resolver uma grande variedade de problemas, como reconhecimento de caligrafia, detecção facial etc. 2.6 Perceptrons A rede neural mais simples é tecnicamente denominada perceptron, que aproxima um único neurônio com n entradas binárias. Ela computa a soma ponderada de suas entradas e “starta” se essa soma for maior ou igual a zero. Vejamos o código fonte a seguir: #!/usr/bin/env python # -*- coding: utf-8 -*- # aplicativo para verificar se o ser vivo eh quadrupede ou bipede # quadrupede = 1, bipede = -1 # cao = [-1,-1,1,1] | resposta = 1 # gato = [1,1,1,1] | resposta = 1 # cavalo = [1,1,-1,1] | resposta = 1 PRÁTICA E LABORATÓRIO II 30 # homem = [-1,-1,-1,1] | resposta = -1 # pesos (sinapses) w = [0,0,0,0] # entradas x = [[-1,-1,1,1], [1,1,1,1], [1,1,-1,1], [-1,-1,-1,1]] # respostas esperadas t = [1,1,1,-1] # bias (ajuste fino) b = 0 #saida y = 0 # numero maximo de interacoes max_int = 10 # taxa de aprendizado taxa_aprendizado = 1 #soma soma = 0 #theshold threshold = 1 # nome do animal animal = "" # resposta = acerto ou falha resposta = "" # dicionario de dados d = {'-1,-1,1,1' : 'cao', '1,1,1,1' : 'gato', PRÁTICA E LABORATÓRIO II 31 '1,1,-1,1' : 'cavalo', '-1,-1,-1,1' : 'homem' } print("APRENDIZADO COM REDES NEURAIS ARTIFICIAIS - Perceptrons") print("Treinando") print(" ") # funcao para converter listas em strings def listToString(list): s = str(list).strip('[]') s = s.replace(' ', '') return s # inicio do algoritmo for k in range(1,max_int): acertos = 0 print("INTERACAO "+str(k)+"-------------------------") for i in range(0,len(x)): soma = 0 # pega o nome do animal no dicionário if d.has_key(listToString(x[i])): animal = d[listToString(x[i])] else: animal = "" # para calcular a saida do perceptron, cada entradade x eh multiplicada # pelo seu peso w correspondente for j in range(0,len(x[i])): soma += x[i][j] * w[j] PRÁTICA E LABORATÓRIO II 32 # a saida eh igual a adicao do bias com a soma anterior y_in = b + soma #print("y_in = ",str(y_in)) # funcao de saida eh determinada pelo threshold if y_in > threshold: y = 1 elif y_in >= -threshold and y_in <= threshold: y = 0 else: y = -1 # atualiza os pesos caso a saida nao corresponda ao valor esperado if y == t[i]: acertos+=1 resposta = "acerto" else: for j in range (0,len(w)): w[j] = w[j] + (taxa_aprendizado * t[i] * x[i][j]) b = b + taxa_aprendizado * t[i] resposta = "Falha - Peso atualizado" #imprime a resposta if y == 1: print(animal+" = quadrupede = "+resposta) elif y == 0: print(animal+" = padrao nao identificado = "+resposta) elif y == -1: print(animal+" = bipede = "+resposta) if acertos == len(x): PRÁTICA E LABORATÓRIO II 33 print("\nFuncionalidade aprendida com "+str(k)+" interacoes") break; print("") print("\nFinalizado com Sucesso") raw_input( ) A saída produzida pelo programa é a seguinte: 2.7 Redes Neurais Feed-Forward Como a topologia do cérebro é demasiadamente complicada, segundo Grus (2016, p. 215), é normal aproximá-la como uma rede neural feed-forward idealizada, que consiste de camadas discretas de neurônios, cada uma conectada à seguinte camada disponível. Assim como o perceptron, deve-se somar os produtos de suas entradas com seus respectivos pesos. Observe o código Phyton a seguir: #!/usr/bin/env python PRÁTICA E LABORATÓRIO II 34 # -*- coding: utf-8 -*- # aplicativo para analise de portas OR # falso = 0, verdadeiro = 1 # [0,0] | resposta = 0 # [0,1] | resposta = 1 # [1,0] | resposta = 1 # [1,1] | resposta = 1 # numero maximo de interacoes max_int = 20 # threshold (limiar) threshold = 0 # peso 0 w_0 = -threshold # entrada 0 x_0 = 1 # entradas x = [[x_0,0,0], [x_0,0,1], [x_0,1,0], [x_0,1,1]] # quantos itens tem o vetor x (4) tamanho_x = len(x) # quantos itens estão em cada posicao do vetor x PRÁTICA E LABORATÓRIO II 35 qtde_itens_x = len(x[0]) # pesos (sinapses) w = [w_0,0,0] # quantos itens tem o vetor w (3) tamanho_w = len(w) # respostas desejadas d = [0,1,1,1] # taxa de aprendizado (n) taxa_aprendizado = 0.5 #saida y = 0 # resposta = acerto ou falha resposta = "" # soma u = 0 #erro e = 0 print("REDES NEURAIS ARTIFICIAIS - Feed-Forward\n") # inicio do algoritmo for k in range(1,max_int): acertos = 0 PRÁTICA E LABORATÓRIO II 36 e = 0 print("INTERACAO "+str(k)+"-------------------------") for t in range(0,tamanho_x): u = 0 # para calcular a saida do perceptron, cada entrada de x eh multiplicada # pelo seu peso w correspondente for j in range(0,qtde_itens_x): u += x[t][j] * w[j] # funcao de saida if u > 0: y = 1 else: y = 0 # atualiza os pesos caso a saida nao corresponda ao valor esperado if y == d[t]: resposta = "acerto" acertos += 1 e = 0 else: resposta = "erro" # calculando o erro e = d[t] - y # atualizando os pesos for j in range (0,tamanho_w): w[j] = w[j] + (taxa_aprendizado * e * x[t][j]) print(resposta + " >>> u = "+str(u)+ ", y = "+ str(y)+ ", e = "+str(e)) PRÁTICA E LABORATÓRIO II 37 if acertos == tamanho_x: print("\nFuncionalidade aprendida com "+str(k)+" interacoes") print("\nPesos encontrados =============== ") for j in range (0,tamanho_w): print(w[j]) break; print("") print("Finalizado") raw_input( ) A saída produzida pelo programa é a seguinte: PRÁTICA E LABORATÓRIO II 38 2.8 Backpropagation Uma das opções de redes neurais artificiais é trabalhar com o famoso algoritmo de backpropagation, cujas principais vantagens é trabalhar com multicamadas e resolver problemas “não linearmente separáveis” que alguns algoritmos não resolvem. Um problema “não linearmente separável”, conforme ilustra a figura anterior, é aquele em que não poderemos separar duas classes distintas no eixo cartesiano bidimensional apenas traçando uma reta. Segue um exemplo clássico do uso da rede neural backpropagation: print ("APRENDIZADO COM REDES NEURAIS ARTIFICIAIS - BACKPROPAGATION\n") import math import random import string class NN: def __init__(self, NI, NH, NO): PRÁTICA E LABORATÓRIO II 39 # number of nodes in layers self.ni = NI + 1 # +1 for bias self.nh = NH self.no = NO # initialize node-activations self.ai, self.ah, self.ao = [],[], [] self.ai = [1.0]*self.ni self.ah = [1.0]*self.nh self.ao = [1.0]*self.no # create node weight matrices self.wi = makeMatrix (self.ni, self.nh) self.wo = makeMatrix (self.nh, self.no) # initialize node weights to random vals randomizeMatrix ( self.wi, -0.2, 0.2 ) randomizeMatrix ( self.wo, -2.0, 2.0 ) # create last change in weights matrices for momentum self.ci = makeMatrix (self.ni, self.nh) self.co = makeMatrix (self.nh, self.no) def runNN (self, inputs): if len(inputs) != self.ni-1: print 'incorrect number of inputs' for i in range(self.ni-1): self.ai[i] = inputs[i] for j in range(self.nh): sum = 0.0 for i in range(self.ni): PRÁTICA E LABORATÓRIO II 40 sum +=( self.ai[i] * self.wi[i][j] ) self.ah[j] = sigmoid (sum) for k in range(self.no): sum = 0.0 for j in range(self.nh): sum +=( self.ah[j] * self.wo[j][k] ) self.ao[k] = sigmoid (sum) return self.ao def backPropagate (self, targets, N, M): # calc output deltas output_deltas = [0.0] * self.no for k in range(self.no): error = targets[k] - self.ao[k] output_deltas[k] = error * dsigmoid(self.ao[k]) # update output weights for j in range(self.nh): for k in range(self.no): # output_deltas[k] * self.ah[j] is the full derivative of dError/dweight[j][k] change = output_deltas[k] * self.ah[j] self.wo[j][k] += N*change + M*self.co[j][k] self.co[j][k] = change # calc hidden deltas hidden_deltas = [0.0] * self.nh PRÁTICA E LABORATÓRIO II 41 for j in range(self.nh): error = 0.0 for k in range(self.no): error += output_deltas[k]* self.wo[j][k] hidden_deltas[j] = error * dsigmoid(self.ah[j]) #update input weights for i in range (self.ni): for j in range (self.nh): change = hidden_deltas[j] * self.ai[i] #print 'activation',self.ai[i],'synapse',i,j,'change',change self.wi[i][j] += N*change + M*self.ci[i][j] self.ci[i][j] = change # calc combined error # 1/2 for differential convenience & **2 for modulus error = 0.0 for k in range(len(targets)): error = 0.5 * (targets[k]-self.ao[k])**2 return error def weights(self): print 'Input weights:' for i in range(self.ni): print self.wi[i] print print 'Output weights:' for j in range(self.nh): print self.wo[j] print '' PRÁTICA E LABORATÓRIO II 42 def test(self, patterns): for p in patterns: inputs = p[0] print 'Inputs:', p[0], '-->', self.runNN(inputs), '\tTarget', p[1] def train (self, patterns, max_iterations = 1000, N=0.5, M=0.1): for i in range(max_iterations): for p in patterns: inputs = p[0] targets = p[1] self.runNN(inputs) error = self.backPropagate(targets, N, M) if i % 50 == 0: print 'Combined error', error self.test(patterns) def sigmoid (x): return math.tanh(x) # the derivative of the sigmoid function in terms of output # proof here: # http://www.math10.com/en/algebra/hyperbolic-functions/hyperbolic- functions.html def dsigmoid (y): return 1 - y**2 def makeMatrix ( I, J, fill=0.0): m = [] for i in range(I): PRÁTICA E LABORATÓRIO II 43 m.append([fill]*J) return m def randomizeMatrix ( matrix, a, b): for i in range ( len (matrix) ): for j in range ( len (matrix[0]) ): matrix[i][j] = random.uniform(a,b) def main (): pat = [ [[0,0], [1]], [[0,1], [1]], [[1,0], [1]], [[1,1], [0]] ] myNN = NN ( 2, 2, 1) myNN.train(pat) if __name__ == "__main__": main() raw_input( ) A saída produzida pelo programa será a seguinte: PRÁTICA E LABORATÓRIO II 44 3. Processamento de Linguagem Natural 3.1 Nuvens de Palavras Nuvens de palavras é uma atividade comum aos cientistas de dados, principalmente pelo simples fato de computar contagens. Segundo Grus (2016), eles não penam muito em nuvens de palavras, em grande parte porque a colocação das palavras não significa nada além de “este é um espaço onde eu consigo encaixar uma palavra”. Quando cientistas de dados têm que criar nuvens de palavras, em linhas gerais, devem pensar se querem fazer os eixos transmitirem alguma coisa com algum sentido lógico. Grus (2016, p. 239) cita um exemplo interessante quando supõe que para cada coleção de dados de jargões relacionados à ciência você tenha dois números entre 0 e 100 – o primeiro representando a frequência com que ele aparece em postagens de empregos, e o segundo a frequência com que aparece em currículos: data = [ ("big data", 100, 15), ("Hadoop", 95, 25), ("Python", 75, 50), ("R", 50, 40), ("machine learning", 80, 20), ("statistics", 20, 60), ("data science", 60, 70), ("analytics", 90, 3), ("team player", 85, 85), ("dynamic", 2, 90), ("synergies", 70, 0), PRÁTICA E LABORATÓRIO II 45 ("actionable insights", 40, 30), ("think out of the box", 45, 10), ("self-starter", 30, 50), ("customer focus", 65, 15), ("thought leadership", 35, 35)] A abordagem nuvens de palavras serve apenas para organizar as palavras na página usando certo tipo de fonte que, de certa forma, acaba chamando muito atenção. O interessante sobre nuvens de palavras é que elas têm uma abordagem de dispersão dessas palavras para que a posição horizontal possa indicar a popularidade de postagens, e a vertical, a popularidade de currículos, o que certamente, produziria uma visualização que transmitiria alguns insights. PRÁTICA E LABORATÓRIO II 46 def text_size(total): """equals 8 if total is 0, 28 if total is 200""" return 8 + total / 200 * 20 for word, job_popularity, resume_popularity in data: plt.text(job_popularity, resume_popularity, word, ha='center', va='center', size=text_size(job_popularity + resume_popularity)) plt.xlabel("Popularity on Job Postings") plt.ylabel("Popularity on Resumes") plt.axis([0, 100, 0, 100]) plt.show() Dessa forma, teríamos uma nuvem de palavras com palavras mais significativas, embora aparentemente fosse menos atrativa. 3.2 Modelo n-gramas Grus (2016) apresenta uma situação como exemplo para adentrar no estudo de modelos de n-gramas. Vamos a ela: “A vice-presidente de marketing de pesquisa quer que você crie milhares de páginas Web sobre Data Science para que seu site seja classificado no topo dos resultados de pesquisa para os termos relacionados”. Na verdade, ela não quer escrever milhares de PRÁTICA E LABORATÓRIO II 47 páginas Web. Em vez disso, ela pergunta se você pode, de alguma forma, gerar estas páginas. Teremos que utilizar recursos de recuperação de dados, requests e BeautifulSoup. No entanto, existem alguns problemas nos quais devemos prestar muita atenção. O primeiro está relacionado aos apóstrofos no texto que, são representados pelo caractere Unicode u”\u2019”. Dessa forma, há necessidade de se criar uma função auxiliar para substituí-las por apóstrofos normais. # n-gram models def fix_unicode(text): return text.replace(u"\u2019", "'") O segundo problema está relacionado com o texto da página Web, pois devemos dividi-lo em sequências de palavras e pontos para que possamos dizer onde se inicia e termina uma sentença. Podemos fazer isto usando a função findall( ) do módulo re: from bs4 import BeautifulSoup import request url = "http://radar.oreilly.com/2010/06/what-is-data-science.html" html = requests.get(url).text soup = BeautifulSoup(html, 'html5lib') content = soup.find("div", "article-body") # encontra conteúdo de entrada div regex = r"[\w']+|[\.]" # combina uma palavra ou um ponto document = [] PRÁTICA E LABORATÓRIO II 48 for paragraph in content("p"): words = re.findall(regex, fix_unicode(paragraph.text)) document.extend(words) return document Certamente, você, como cientista de dados, poderia e deveria limpar um pouco mais esses dados. Ainda existe uma grande quantidade de textos extrínsecos no documento. Agora que o texto disponível é uma sequência de palavras, podemos modelar uma linguagem da seguinte forma: dada alguma palavra inicial, procura-se a partir de todas as demais palavras, nos documentos-fonte, a próxima que nos interessa, de acordo com a nossa necessidade. A partir daí, deve-se escolher aleatoriamente uma dessas palavras para ser, portanto, a próxima palavra, e assim por diante, até que cheguemos ao ponto, que expressa o final da sentença. Tecnicamente, essa técnica é denominada modelo bigrama e pode ser determinada completamente por sequências de bigramas nos dados originais.Pode surgir a seguinte dúvida: por qual palavra começar? Deve-se começar a computar as possíveis transições de palavras. Observe o trecho de código a seguir: bigrams = zip(document, document[1:]) transitions = defaultdict(list) for prev, current in bigrams: transitions[prev].append(current) A partir de então, tem-se acesso aos pares de elementos consecutivos do documento. Então, já estamos prontos para gerar sentenças: def generate_using_bigrams(transitions): PRÁTICA E LABORATÓRIO II 49 current = "." # a próxima palavra sera uma sentenca result = [] while True: next_word_candidates = transitions[current] # bigramas current = random.choice(next_word_candidates) # escolhe um de forma aleatória result.append(current) # anexa aos resultados if current == ".": return " ".join(result) # se "." então terminamos As sentenças que produz são “besteiras”, mas são o tipo de bobagem que você deveria colocar no seu web site, se está tentando fazer parecer com Data Science. 3.3 Gramáticas Uma abordagem diferente para modelar linguagem é através do uso de gramáticas, regras para gerar sentenças aceitáveis. Seremos, portanto, obrigados a definir uma gramática independentemente da complexidade dada a ela: grammar = { "_S" : ["_NP _VP"], "_NP" : ["_N", "_A _NP _P _A _N"], "_VP" : ["_V", "_V _NP"], "_N" : ["data science", "Python", "regression"], "_A" : ["big", "linear", "logistic"], "_P" : ["about", "near"], "_V" : ["learns", "trains", "tests", "is"] } PRÁTICA E LABORATÓRIO II 50 Depois de tudo, sentenças poderão ser geradas com base na gramática a partir de então. def generate_sentence(grammar): return expand(grammar, [“_S”]) As gramáticas são mais interessantes quando usadas em outra direção. Assim, dada uma sentença, podemos usar uma gramática para analisá-la. Isso permite que possamos identificar sujeitos e verbos que nos auxiliarão a compreender a sentença. Utilizar ciência de dados para gerar textos é um truque esperto; usá-las para entender o texto é mais esperto ainda. 3.4 Amostragem Gibbs Grus (2016) afirma que gerar abordagens de algumas distribuições é fácil. Devemos seguir variáveis aleatórias e uniformes. No entanto, algumas distribuições são mais difíceis de criar amostras. A amostragem de Gibbs nada mais é que uma técnica para gerar amostras de distribuição multidimensionais quando apenas conhecemos algumas das distribuições condicionais. Observe um trecho de código Phyton com tal abordagem: # Exemplo Amostragem Gibbs def roll_a_die(): return random.choice([1,2,3,4,5,6]) def direct_sample(): d1 = roll_a_die() d2 = roll_a_die() return d1, d1 + d2 PRÁTICA E LABORATÓRIO II 51 def random_y_given_x(x): """equally likely to be x + 1, x + 2, ... , x + 6""" return x + roll_a_die() def random_x_given_y(y): if y <= 7: # se o total for 7 ou menos, o primeiro dado e igualmente # 1, 2, ..., (total - 1) return random.randrange(1, y) else: # se o total for 7 ou mais, o primeiro dado e igualmente # (total - 6), (total - 5), ..., 6 return random.randrange(y - 6, 7) def gibbs_sample(num_iters=100): x, y = 1, 2 # doesn't really matter for _ in range(num_iters): x = random_x_given_y(y) y = random_y_given_x(x) return x, y def compare_distributions(num_samples=1000): counts = defaultdict(lambda: [0, 0]) for _ in range(num_samples): counts[gibbs_sample()][0] += 1 counts[direct_sample()][1] += 1 return counts A forma como a amostragem Gibbs funciona é que se começarmos com qualquer valor considerado válido para x e y, e então repetida e alternadamente substituímos x PRÁTICA E LABORATÓRIO II 52 por um valor aleatório escolhido condicionado a y por um valor aleatório escolhido a x. 3.5 Modelagem de tópicos Os tópicos precisam ser definidos pelo cientista de dados antes de mais nada. Será necessária então uma função para escolher aleatoriamente um índice baseado num conjunto arbitrário de pesos. # TOPIC MODELING def sample_from(weights): total = sum(weights) rnd = total * random.random() # uniform entre 0 e total for i, w in enumerate(weights): rnd -= w # retorna o menor i tal que if rnd <= 0: return i # sum(weights[:(i+1)]) >= rnd Nossos documentos são os interesses de nossos usuários, que podem ser definidos, por exemplo, como: documents = [ ["Hadoop", "Big Data", "HBase", "Java", "Spark", "Storm", "Cassandra"], ["NoSQL", "MongoDB", "Cassandra", "HBase", "Postgres"], ["Python", "scikit-learn", "scipy", "numpy", "statsmodels", "pandas"], ["R", "Python", "statistics", "regression", "probability"], ["machine learning", "regression", "decision trees", "libsvm"], ["Python", "R", "Java", "C++", "Haskell", "programming languages"], ["statistics", "probability", "mathematics", "theory"], ["machine learning", "scikit-learn", "Mahout", "neural networks"], ["neural networks", "deep learning", "Big Data", "artificial intelligence"], ["Hadoop", "Java", "MapReduce", "Big Data"], ["statistics", "R", "statsmodels"], PRÁTICA E LABORATÓRIO II 53 ["C++", "deep learning", "artificial intelligence", "probability"], ["pandas", "R", "Python"], ["databases", "HBase", "Postgres", "MySQL", "MongoDB"], ["libsvm", "regression", "support vector machines"] ] Depois do entendimento de todas essas engrenagens necessárias, segue o programa exemplo completo para processamento de linguagem natural: # Programa exemplo disponibilizado por Grus (2016) from __future__ import division import math, random, re from collections import defaultdict, Counter from bs4 import BeautifulSoup import requests def plot_resumes(plt): data = [ ("big data", 100, 15), ("Hadoop", 95, 25), ("Python", 75, 50), ("R", 50, 40), ("machine learning", 80, 20), ("statistics", 20, 60), ("data science", 60, 70), ("analytics", 90, 3), ("team player", 85, 85), ("dynamic", 2, 90), ("synergies", 70, 0), ("actionable insights", 40, 30), ("think out of the box", 45, 10), ("self-starter", 30, 50), ("customer focus", 65, 15), ("thought leadership", 35, 35)] def text_size(total): """equals 8 if total is 0, 28 if total is 200""" return 8 + total / 200 * 20 for word, job_popularity, resume_popularity in data: plt.text(job_popularity, resume_popularity, word, ha='center', va='center', PRÁTICA E LABORATÓRIO II 54 size=text_size(job_popularity + resume_popularity)) plt.xlabel("Popularity on Job Postings") plt.ylabel("Popularity on Resumes") plt.axis([0, 100, 0, 100]) plt.show() # # n-gram models # def fix_unicode(text): return text.replace(u"\u2019", "'") def get_document(): url = "http://radar.oreilly.com/2010/06/what-is-data-science.html" html = requests.get(url).text soup= BeautifulSoup(html, 'html5lib') content = soup.find("div", "article-body") # find article-body div regex = r"[\w']+|[\.]" # matches a word or a period document = [] for paragraph in content("p"): words = re.findall(regex, fix_unicode(paragraph.text)) document.extend(words) return document PRÁTICA E LABORATÓRIO II 55 def generate_using_bigrams(transitions): current = "." # this means the next word will start a sentence result = [] while True: next_word_candidates = transitions[current] # bigrams (current, _) current = random.choice(next_word_candidates) # choose one at random result.append(current) # append it to results if current == ".": return " ".join(result) # if "." we're done def generate_using_trigrams(starts, trigram_transitions): current = random.choice(starts) # choose a random starting word prev = "." # and precede it with a '.' result = [current] while True: next_word_candidates = trigram_transitions[(prev, current)] next = random.choice(next_word_candidates) prev, current = current, next result.append(current) if current == ".": return " ".join(result) def is_terminal(token): return token[0] != "_" def expand(grammar, tokens): for i, token in enumerate(tokens): # ignore terminals PRÁTICA E LABORATÓRIO II 56 if is_terminal(token): continue # choose a replacement at random replacement = random.choice(grammar[token]) if is_terminal(replacement): tokens[i] = replacement else: tokens = tokens[:i] + replacement.split() + tokens[(i+1):] return expand(grammar, tokens) # if we get here we had all terminals and are done return tokens def generate_sentence(grammar): return expand(grammar, ["_S"]) # # Gibbs Sampling # def roll_a_die(): return random.choice([1,2,3,4,5,6]) def direct_sample(): d1 = roll_a_die() d2 = roll_a_die() return d1, d1 + d2 def random_y_given_x(x): """equally likely to be x + 1, x + 2, ... , x + 6""" PRÁTICA E LABORATÓRIO II 57 return x + roll_a_die() def random_x_given_y(y): if y <= 7: # if the total is 7 or less, the first die is equally likely to be # 1, 2, ..., (total - 1) return random.randrange(1, y) else: # if the total is 7 or more, the first die is equally likely to be # (total - 6), (total - 5), ..., 6 return random.randrange(y - 6, 7) def gibbs_sample(num_iters=100): x, y = 1, 2 # doesn't really matter for _ in range(num_iters): x = random_x_given_y(y) y = random_y_given_x(x) return x, y def compare_distributions(num_samples=1000): counts = defaultdict(lambda: [0, 0]) for _ in range(num_samples): counts[gibbs_sample()][0] += 1 counts[direct_sample()][1] += 1 return counts # # TOPIC MODELING # def sample_from(weights): PRÁTICA E LABORATÓRIO II 58 total = sum(weights) rnd = total * random.random() # uniform between 0 and total for i, w in enumerate(weights): rnd -= w # return the smallest i such that if rnd <= 0: return i # sum(weights[:(i+1)]) >= rnd documents = [ ["Hadoop", "Big Data", "HBase", "Java", "Spark", "Storm", "Cassandra"], ["NoSQL", "MongoDB", "Cassandra", "HBase", "Postgres"], ["Python", "scikit-learn", "scipy", "numpy", "statsmodels", "pandas"], ["R", "Python", "statistics", "regression", "probability"], ["machine learning", "regression", "decision trees", "libsvm"], ["Python", "R", "Java", "C++", "Haskell", "programming languages"], ["statistics", "probability", "mathematics", "theory"], ["machine learning", "scikit-learn", "Mahout", "neural networks"], ["neural networks", "deep learning", "Big Data", "artificial intelligence"], ["Hadoop", "Java", "MapReduce", "Big Data"], ["statistics", "R", "statsmodels"], ["C++", "deep learning", "artificial intelligence", "probability"], ["pandas", "R", "Python"], ["databases", "HBase", "Postgres", "MySQL", "MongoDB"], ["libsvm", "regression", "support vector machines"] ] K = 4 document_topic_counts = [Counter() for _ in documents] topic_word_counts = [Counter() for _ in range(K)] PRÁTICA E LABORATÓRIO II 59 topic_counts = [0 for _ in range(K)] document_lengths = map(len, documents) distinct_words = set(word for document in documents for word in document) W = len(distinct_words) D = len(documents) def p_topic_given_document(topic, d, alpha=0.1): """the fraction of words in document _d_ that are assigned to _topic_ (plus some smoothing)""" return ((document_topic_counts[d][topic] + alpha) / (document_lengths[d] + K * alpha)) def p_word_given_topic(word, topic, beta=0.1): """the fraction of words assigned to _topic_ that equal _word_ (plus some smoothing)""" return ((topic_word_counts[topic][word] + beta) / (topic_counts[topic] + W * beta)) def topic_weight(d, word, k): """given a document and a word in that document, return the weight for the k-th topic""" return p_word_given_topic(word, k) * p_topic_given_document(k, d) def choose_new_topic(d, word): PRÁTICA E LABORATÓRIO II 60 return sample_from([topic_weight(d, word, k) for k in range(K)]) random.seed(0) document_topics = [[random.randrange(K) for word in document] for document in documents] for d in range(D): for word, topic in zip(documents[d], document_topics[d]): document_topic_counts[d][topic] += 1 topic_word_counts[topic][word] += 1 topic_counts[topic] += 1 for iter in range(1000): for d in range(D): for i, (word, topic) in enumerate(zip(documents[d], document_topics[d])): # remove this word / topic from the counts # so that it doesn't influence the weights document_topic_counts[d][topic] -= 1 topic_word_counts[topic][word] -= 1 topic_counts[topic] -= 1 document_lengths[d] -= 1 # choose a new topic based on the weights new_topic = choose_new_topic(d, word) document_topics[d][i] = new_topic # and now add it back to the counts PRÁTICA E LABORATÓRIO II 61 document_topic_counts[d][new_topic] += 1 topic_word_counts[new_topic][word] += 1 topic_counts[new_topic] += 1 document_lengths[d] += 1 if __name__ == "__main__": document = get_document() bigrams = zip(document, document[1:]) transitions = defaultdict(list) for prev, current in bigrams: transitions[prev].append(current) random.seed(0) print "bigram sentences" for i in range(10):print i, generate_using_bigrams(transitions) print # trigrams trigrams = zip(document, document[1:], document[2:]) trigram_transitions = defaultdict(list) starts = [] for prev, current, next in trigrams: if prev == ".": # if the previous "word" was a period starts.append(current) # then this is a start word PRÁTICA E LABORATÓRIO II 62 trigram_transitions[(prev, current)].append(next) print "trigram sentences" for i in range(10): print i, generate_using_trigrams(starts, trigram_transitions) print grammar = { "_S" : ["_NP _VP"], "_NP" : ["_N", "_A _NP _P _A _N"], "_VP" : ["_V", "_V _NP"], "_N" : ["data science", "Python", "regression"], "_A" : ["big", "linear", "logistic"], "_P" : ["about", "near"], "_V" : ["learns", "trains", "tests", "is"] } print "grammar sentences" for i in range(10): print i, " ".join(generate_sentence(grammar)) print print "gibbs sampling" comparison = compare_distributions() for roll, (gibbs, direct) in comparison.iteritems(): print roll, gibbs, direct # topic MODELING PRÁTICA E LABORATÓRIO II 63 for k, word_counts in enumerate(topic_word_counts): for word, count in word_counts.most_common(): if count > 0: print k, word, count topic_names = ["Big Data and programming languages", "Python and statistics", "databases", "machine learning"] for document, topic_counts in zip(documents, document_topic_counts): print document for topic, count in topic_counts.most_common(): if count > 0: print topic_names[topic], count, print 3.6 Explorando Big Data e Clound Computing Depois de aprimorarmos todas essas técnicas, é hora de começarmos a utilizar a ciência de dados a partir da exploração de Big Data e de Cloud Computing. Uma técnica utilizada é o mapreduce, que nada mais é que um modelo de programação proposto pelo Google para facilitar o processamento de grandes volumes de dados a partir de Big Data. PRÁTICA E LABORATÓRIO II 64 A partir de um paradigma inspirado em primitivas de programação funcional, foi criado um framework que permite a manipulação de grande volume de dados de forma paralela e distribuída, além de prover tolerância à falha, escalonamento de I/O e monitoramento. Um grande número de aplicações reais pode ser expresso nesse modelo de programação, que consiste na construção de um programa formado por duas operações básicas: map e reduce. A operação de map recebe um par chave/valor e gera um conjunto intermediário de dados também no formato chave/valor. A operação de reduce é executada para cada chave intermediária, com todos os conjuntos de valores intermediários associados àquela chave, combinados. Os dados podem estar disponíveis 24 horas por dia, 7 dias por semana e por todas as semanas no ano, através do recurso de Cloud Computing. map(String input_key, String input_value): // input_key: document name // input_value: document contents for each word w in input_value: PRÁTICA E LABORATÓRIO II 65 EmitIntermediate(w, "1"); reduce(String output_key, Iterator intermediate_values): // output_key: a word // output_values: a list of counts int result = 0; for each v in intermediate_values: result += ParseInt(v); Emit(AsString(result)); Para cara palavra do documento de entrada, a função map emite o valor ‘1’ associado à chave que representa a palavra em questão. A função de reduce soma todas as contagens emitidas para uma mesma chave, ou seja, uma mesma palavra. Em geral a operação de map é usada para encontrar algo, e a operação de reduce é usada para fazer a sumarização do resultado. Segue um exemplo completo de operação com mapreduce a partir de Big Data: # Big Data com mapreduce from __future__ import division import math, random, re, datetime from collections import defaultdict, Counter from functools import partial from naive_bayes import tokenize def word_count_old(documents): """word count not using MapReduce""" return Counter(word for document in documents for word in tokenize(document)) PRÁTICA E LABORATÓRIO II 66 def wc_mapper(document): """for each word in the document, emit (word,1)""" for word in tokenize(document): yield (word, 1) def wc_reducer(word, counts): """sum up the counts for a word""" yield (word, sum(counts)) def word_count(documents): """count the words in the input documents using MapReduce""" # place to store grouped values collector = defaultdict(list) for document in documents: for word, count in wc_mapper(document): collector[word].append(count) return [output for word, counts in collector.iteritems() for output in wc_reducer(word, counts)] def map_reduce(inputs, mapper, reducer): """runs MapReduce on the inputs using mapper and reducer""" collector = defaultdict(list) for input in inputs: for key, value in mapper(input): collector[key].append(value) PRÁTICA E LABORATÓRIO II 67 return [output for key, values in collector.iteritems() for output in reducer(key,values)] def reduce_with(aggregation_fn, key, values): """reduces a key-values pair by applying aggregation_fn to the values""" yield (key, aggregation_fn(values)) def values_reducer(aggregation_fn): """turns a function (values -> output) into a reducer""" return partial(reduce_with, aggregation_fn) sum_reducer = values_reducer(sum) max_reducer = values_reducer(max) min_reducer = values_reducer(min) count_distinct_reducer = values_reducer(lambda values: len(set(values))) # # Analyzing Status Updates # status_updates = [ {"id": 1, "username" : "joelgrus", "text" : "Is anyone interested in a data science book?", "created_at" : datetime.datetime(2013, 12, 21, 11, 47, 0), "liked_by" : ["data_guy", "data_gal", "bill"] }, # add your own ] def data_science_day_mapper(status_update): PRÁTICA E LABORATÓRIO II 68 """yields (day_of_week, 1) if status_update contains "data science" """ if "data science" in status_update["text"].lower(): day_of_week = status_update["created_at"].weekday() yield (day_of_week, 1) data_science_days = map_reduce(status_updates, data_science_day_mapper, sum_reducer) def words_per_user_mapper(status_update): user = status_update["username"] for word in tokenize(status_update["text"]): yield (user, (word, 1)) def most_popular_word_reducer(user, words_and_counts): """given a sequence of (word, count) pairs, return the word with the highest total count""" word_counts = Counter() for word, count in words_and_counts:word_counts[word] += count word, count = word_counts.most_common(1)[0] yield (user, (word, count)) user_words = map_reduce(status_updates, words_per_user_mapper, most_popular_word_reducer) def liker_mapper(status_update): PRÁTICA E LABORATÓRIO II 69 user = status_update["username"] for liker in status_update["liked_by"]: yield (user, liker) distinct_likers_per_user = map_reduce(status_updates, liker_mapper, count_distinct_reducer) # # matrix multiplication # def matrix_multiply_mapper(m, element): """m is the common dimension (columns of A, rows of B) element is a tuple (matrix_name, i, j, value)""" matrix, i, j, value = element if matrix == "A": for column in range(m): # A_ij is the jth entry in the sum for each C_i_column yield((i, column), (j, value)) else: for row in range(m): # B_ij is the ith entry in the sum for each C_row_j yield((row, j), (i, value)) def matrix_multiply_reducer(m, key, indexed_values): results_by_index = defaultdict(list) for index, value in indexed_values: results_by_index[index].append(value) PRÁTICA E LABORATÓRIO II 70 # sum up all the products of the positions with two results sum_product = sum(results[0] * results[1] for results in results_by_index.values() if len(results) == 2) if sum_product != 0.0: yield (key, sum_product) if __name__ == "__main__": documents = ["data science", "big data", "science fiction"] wc_mapper_results = [result for document in documents for result in wc_mapper(document)] print "wc_mapper results" print wc_mapper_results print print "word count results" print word_count(documents) print print "word count using map_reduce function" print map_reduce(documents, wc_mapper, wc_reducer) print print "data science days" print data_science_days PRÁTICA E LABORATÓRIO II 71 print print "user words" print user_words print print "distinct likers" print distinct_likers_per_user print # matrix multiplication entries = [("A", 0, 0, 3), ("A", 0, 1, 2), ("B", 0, 0, 4), ("B", 0, 1, -1), ("B", 1, 0, 10)] mapper = partial(matrix_multiply_mapper, 3) reducer = partial(matrix_multiply_reducer, 3) print "map-reduce matrix multiplication" print "entries:", entries print "result:", map_reduce(entries, mapper, reducer) raw_input( ) PRÁTICA E LABORATÓRIO II 72 A operação deverá ser realiza a partir de um a plataforma de software especial denominada Hadoop. Hadoop é, portanto, uma plataforma de software de computação distribuída voltada para clusters e processamento de grandes massas de dados (Big Data). Foi inspirada no mapreduce e no GoogleFS – GFS (sistema de arquivos escalável para aplicações de distribuição intensiva de dados). O framework do Apache Hadoop é composto dos módulos seguintes na versão 2.2.x: Hadoop Common – Contém as bibliotecas e arquivos comuns e necessários para todos os módulos Hadoop. Hadoop Distributed File System (HDFS) – Sistema de arquivos distribuídos que armazena dados em máquinas dentro do cluster, sob demanda, permitindo uma largura de banda muito grande em todo o cluster. Hadoop Yarn – Trata-se de uma plataforma de gerenciamento de recursos responsável pelo gerenciamento dos recursos computacionais em cluster, bem como pelo agendamento dos recursos. Hadoop MapReduce – É um modelo de programação para processamento em larga escala (Big Data e Cloud Computing). PRÁTICA E LABORATÓRIO II 73 Todos os módulos do Hadoop são desenhados com a premissa fundamental de que falhas em hardware são comuns, sejam elas máquinas individuais ou um conjunto inteiro de máquinas em racks, e devem, portanto, ser automaticamente tratadas por software pelo framework. hduser@ubuntu:/usr/local/hadoop$ bin/hadoop dfs -ls /user/hduser/gutenberg-output Found 1 items /user/hduser/gutenberg-output/part-00000 <r 1> 903193 2007- 09-21 13:00 hduser@ubuntu:/usr/local/hadoop$ 4. Análise de rede e sistemas de recomendação 4.1 Analisando redes Muitos problemas de dados interessantes podem ser lucrativos em termos de redes consistentes de nós de algum tipo e vínculos que as juntam. A partir de diversas redes sociais, como Facebook, Instagram, Linkedin, Twitter etc., pode-se buscar esses possíveis vínculos a fim de se analisar dados a partir da rede mundial de computadores. No estudo da linguagem de programação Phyton, você aprendeu a computar os conectores de chave na rede DataSciencester contando o número de amigos que cada usuário tinha. PRÁTICA E LABORATÓRIO II 74 Então, já temos maquinário suficiente para fazer outras abordagens em rede. Lembre-se de que a rede engloba diversos usuários: users = [ { “id”: 0, “name”: “Hero” }, { “id”: 1, “name”: “Dunn” }, { “id”: 2, “name”: “Sue” }, { “id”: 3, “name”: “Chi” }, { “id”: 4, “name”: “Thor” }, { “id”: 5, “name”: “Clive” }, { “id”: 6, “name”: “Hicks” }, { “id”: 7, “name”: “Devin” }, { “id”: 8, “name”: “Keite” }, { “id”: 9, “name”: “Klein” } ] E também amizades: friendships = [(0, 1), (0, 2), (1, 2), (1, 3), (2, 3), (3, 4), (4, 5), (5, 6), (5, 7), (6, 8), (7, 8), (8, 9)] PRÁTICA E LABORATÓRIO II 75 A figura reporta, portanto, a rede social de cientistas de dados DataSciencester. Além disso, também adicionamos listas de amigos para cada dict de usuário: for user in users: user[“friends”] = [ ] for i, j in friendships: users[i][“friends”].append(users[j]) #adiciona i como amigo de j users[j][“friends”].append(users[i]) #adiciona j como amigo de i Quando começamos nossos estudos na linguagem de programação Phyton, ainda estávamos insatisfeitos com a nossa noção de grau de centralidade, que não concordava com a nossa intuição sobre quem eram os conectores-chave da rede. Uma alternativa métrica é a centralidade de intermediação, conforme ilustra a próxima figura, que indica pessoas que frequentemente estão no menor caminho entre pares de outras pessoas. PRÁTICA E LABORATÓRIO II 76 4.2 Centralidade de vetor próprio Grus (2016, p.260) afirma que para falar de centralidade de vetor próprio é necessário falar sobre vetores próprios, e para falar sobre vetores próprios temos que falar sobre multiplicidade de matrizes. Para entender a multiplicidade de matrizes, devemos analisar se A é uma matriz n1 x k1 e B é uma matriz n2 x k2, e se k1 = n2, então o produto AB deles é a matriz n1 x k2, cuja entrada (i, j) é: Ai1B1j + Ai2B2j + ...+ AikBkj Que na verdade
Compartilhar