Anda di halaman 1dari 98

Prática e Laboratório II

PRÁTICA E LABORATÓRIO II 1
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 2
#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 3
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 4
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 5
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 6
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 7
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 8
>>> 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 9
>>> 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 10
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 11
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 12
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 13
>>> 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 14
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 15
>>> 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 16
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 17
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 18
“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 19
Á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 20
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 21
({'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 22
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 23
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)
return partition_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 24
# 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 25
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 26
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 27
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 28
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 29
# 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 30
'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 entrada de 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 31
# 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 32
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 33
# -*- 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 34
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 35
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 36
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 37
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 38
# 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 39
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 40
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 41
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 42
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 43
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 44
("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 45
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 46
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 47
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 48
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 49
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 50
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 &lt;= 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 51
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 52
["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 53
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 54
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 55
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 56
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 57
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 58
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 59
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 60
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 61
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 62
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 63
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 64
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 65
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 66
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 67
"""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 68
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 69
# 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 70
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 71
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 72
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 &lt;r 1&gt; 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 73
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 74
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 75
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 é o produto dot da i-ésima linha de A com a j-ésima coluna de B,


tecnicamente denominados de vetor.

def matrix_product_entry (A, B, i, j):


return dot(get_row(A, i), get_column(B, j)

Depois do qual, temos:

def matrix_multiply(A, B):


n1, k1 = shape(A)
n2, k2 = shape(B)
if k1 != n2:
raise ArithmeticError(‘ formatos incompatíveis’)
PRÁTICA E LABORATÓRIO II 76
return make_matrix(n1, k2, partial(matrix_product_entry,
A, B))

Note que se A é uma matriz n x k e B é uma matriz k x 1, então AB é uma matriz n x


1. Se tratarmos um vetor como uma matriz de coluna, podemos pensar em A como
uma função que mapeia vetores dimensionais k para vetores dimensionais n, em que
a função é apenas a multiplicação de matrizes.

Depois disso, nos é facilitado o entendimento da rede DataSciencester, acerca da


centralidade, onde, para começar, precisamos representar as conexões em nossa rede
como adjacency_matrix, cuja entrada (i, j) ou é 1, se os usuários i e j forem amigos,
ou 0, se eles não forem.

def entry_fn(i, j):


return 1 if (i, j) in friendships or (j, i) in friendships else 0

n = len(users)
adjacency_matrix = make_matrix(n, n, entry_fn)

A rede DataSciencester dimensionada por centralidade de vetor próprio, conforme


ilustrada na figura a seguir, informa que usuários com alta centralidade de vetor
próprio deveriam ser aqueles que têm muitas conexões e conexões com pessoas que
tem alta centralidade.

PRÁTICA E LABORATÓRIO II 77
Pode-se observar que os usuários 1 e 2 são os mais centrais, pois ambos possuem
três conexões com pessoas que são altamente centrais. Dessa forma, conforme nos
afastamos deles, as centralidades das pessoas gradualmente diminuem.

4.3 Gráficos direcionados e PageRank

De acordo com Grus (2016, p. 264), DataSciencester não está conseguindo deslanchar
conforme foi previsto. Então, o vice-presidente de rendimentos considera a troca de
um modelo de amizade por um modelo de aprovação.

Entretanto, ninguém particularmente se importa com quais cientistas de dados são


efetivamente amigos, mas recrutadores diversos se importam muito com quais
cientistas de dados são respeitados por outros cientistas.

Nesse novo modelo, as aprovações (source, target) são acompanhadas, pois é


necessário identificar quais não mais representam um relacionamento recíproco, mas
sim que source aprove target como um excelente cientista de dados. Para tanto,
precisamos considerar esta simetria:

endorsements = [(0, 1), (1, 0), (0, 2), (2, 0), (1, 2), (2, 1), (1, 3),
(2, 3), (3, 4), (5, 4), (5, 6), (7, 5), (6, 8), (8, 7), (8, 9)]

for user in users:


user["endorses"] = [] #adiciona lista para acompanhar
aprovacoes
user["endorsed_by"] = [] #adiciona outra lista para
acompanhar aprovacoes recebidas

for source_id, target_id in endorsements:


users[source_id]["endorses"].append(users[target_id])
PRÁTICA E LABORATÓRIO II 78
users[target_id]["endorsed_by"].append(users[source_id])

Uma métrica melhor para se levar em consideração é quem aprova você. Segundo
Grus (2016), aprovações de pessoas com muitas aprovações deveriam de alguma
forma contar mais do que as aprovações de pessoas com poucas aprovações. Esta é
a essência do algoritmo PageRank, usado pelo Google para ordenar web sites
baseados em quais outros sites estão conectados a eles e assim por diante.

def page_rank(users, damping = 0.85, num_iters = 100):

# inicialmente distribui PageRank igualmente


num_users = len(users)
pr = { user["id"] : 1 / num_users for user in users }

# esta e a pequena fracao de PageRank


# que cada no recebe a cada iteracao
base_pr = (1 - damping) / num_users

for __ in range(num_iters):
next_pr = { user["id"] : base_pr for user in users }
for user in users:
# distribui PageRank para links de saida
links_pr = pr[user["id"]] * damping
for endorsee in user["endorses"]:
next_pr[endorsee["id"]] += links_pr / len(user["endorses"])

pr = next_pr

return pr

PRÁTICA E LABORATÓRIO II 79
Na rede DataSciencester ilustrada anteriormente, o algoritmo PageRank identifica o
usuário número 4 (Thor) como o cientista de dados com o maior número de
classificações.

Dessa forma, mesmo que ele tenha menos aprovações do que os usuários 0, 1 e 2, as
aprovações dele carregam classificações de suas aprovações.

A seguir, temos acesso ao código-fonte completo em Phyton do programa para análise


de redes extraído de Grus (2016):

# Analise de Rede
from __future__ import division
import math, random, re
from collections import defaultdict, Counter, deque
from linear_algebra import dot, get_row, get_column, make_matrix,
magnitude, scalar_multiply, shape, distance
from functools import partial

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" },
PRÁTICA E LABORATÓRIO II 80
{ "id": 6, "name": "Hicks" },
{ "id": 7, "name": "Devin" },
{ "id": 8, "name": "Kate" },
{ "id": 9, "name": "Klein" }
]

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)]

# give each user a friends list


for user in users:
user["friends"] = []

# and populate it
for i, j in friendships:
# this works because users[i] is the user whose id is i
users[i]["friends"].append(users[j]) # add i as a friend of j
users[j]["friends"].append(users[i]) # add j as a friend of i

#
# Betweenness Centrality
#

def shortest_paths_from(from_user):

# a dictionary from "user_id" to *all* shortest paths to that user


shortest_paths_to = { from_user["id"] : [[]] }

# a queue of (previous user, next user) that we need to check.


# starts out with all pairs (from_user, friend_of_from_user)
frontier = deque((from_user, friend)

PRÁTICA E LABORATÓRIO II 81
for friend in from_user["friends"])

# keep going until we empty the queue


while frontier:

prev_user, user = frontier.popleft() # take from the beginning


user_id = user["id"]

# the fact that we're pulling from our queue means that
# necessarily we already know a shortest path to prev_user
paths_to_prev = shortest_paths_to[prev_user["id"]]
paths_via_prev = [path + [user_id] for path in paths_to_prev]

# it's possible we already know a shortest path to here as well


old_paths_to_here = shortest_paths_to.get(user_id, [])

# what's the shortest path to here that we've seen so far?


if old_paths_to_here:
min_path_length = len(old_paths_to_here[0])
else:
min_path_length = float('inf')

# any new paths to here that aren't too long


new_paths_to_here = [path_via_prev
for path_via_prev in paths_via_prev
if len(path_via_prev) <= min_path_length
and path_via_prev not in old_paths_to_here]

shortest_paths_to[user_id] = old_paths_to_here +
new_paths_to_here

PRÁTICA E LABORATÓRIO II 82
# add new neighbors to the frontier
frontier.extend((user, friend)
for friend in user["friends"]
if friend["id"] not in shortest_paths_to)

return shortest_paths_to

for user in users:


user["shortest_paths"] = shortest_paths_from(user)

for user in users:


user["betweenness_centrality"] = 0.0

for source in users:


source_id = source["id"]
for target_id, paths in source["shortest_paths"].iteritems():
if source_id < target_id: # don't double count
num_paths = len(paths) # how many shortest paths?
contrib = 1 / num_paths # contribution to centrality
for path in paths:
for id in path:
if id not in [source_id, target_id]:
users[id]["betweenness_centrality"] += contrib

#
# closeness centrality
#

def farness(user):
"""the sum of the lengths of the shortest paths to each other user"""
return sum(len(paths[0])

PRÁTICA E LABORATÓRIO II 83
for paths in user["shortest_paths"].values())

for user in users:


user["closeness_centrality"] = 1 / farness(user)

#
# matrix multiplication
#

def matrix_product_entry(A, B, i, j):


return dot(get_row(A, i), get_column(B, j))

def matrix_multiply(A, B):


n1, k1 = shape(A)
n2, k2 = shape(B)
if k1 != n2:
raise ArithmeticError("incompatible shapes!")

return make_matrix(n1, k2, partial(matrix_product_entry, A, B))

def vector_as_matrix(v):
"""returns the vector v (represented as a list) as a n x 1 matrix"""
return [[v_i] for v_i in v]

def vector_from_matrix(v_as_matrix):
"""returns the n x 1 matrix as a list of values"""
return [row[0] for row in v_as_matrix]

def matrix_operate(A, v):


v_as_matrix = vector_as_matrix(v)

PRÁTICA E LABORATÓRIO II 84
product = matrix_multiply(A, v_as_matrix)
return vector_from_matrix(product)

def find_eigenvector(A, tolerance=0.00001):


guess = [1 for __ in A]

while True:
result = matrix_operate(A, guess)
length = magnitude(result)
next_guess = scalar_multiply(1/length, result)

if distance(guess, next_guess) < tolerance:


return next_guess, length # eigenvector, eigenvalue

guess = next_guess

#
# eigenvector centrality
#

def entry_fn(i, j):


return 1 if (i, j) in friendships or (j, i) in friendships else 0

n = len(users)
adjacency_matrix = make_matrix(n, n, entry_fn)

eigenvector_centralities, _ = find_eigenvector(adjacency_matrix)

#
# directed graphs
#

PRÁTICA E LABORATÓRIO II 85
endorsements = [(0, 1), (1, 0), (0, 2), (2, 0), (1, 2), (2, 1), (1, 3),
(2, 3), (3, 4), (5, 4), (5, 6), (7, 5), (6, 8), (8, 7), (8, 9)]

for user in users:


user["endorses"] = [] # add one list to track outgoing endorsements
user["endorsed_by"] = [] # and another to track endorsements

for source_id, target_id in endorsements:


users[source_id]["endorses"].append(users[target_id])
users[target_id]["endorsed_by"].append(users[source_id])

endorsements_by_id = [(user["id"], len(user["endorsed_by"]))


for user in users]

sorted(endorsements_by_id,
key=lambda (user_id, num_endorsements): num_endorsements,
reverse=True)

def page_rank(users, damping = 0.85, num_iters = 100):

# initially distribute PageRank evenly


num_users = len(users)
pr = { user["id"] : 1 / num_users for user in users }

# this is the small fraction of PageRank


# that each node gets each iteration
base_pr = (1 - damping) / num_users

for __ in range(num_iters):

PRÁTICA E LABORATÓRIO II 86
next_pr = { user["id"] : base_pr for user in users }
for user in users:
# distribute PageRank to outgoing links
links_pr = pr[user["id"]] * damping
for endorsee in user["endorses"]:
next_pr[endorsee["id"]] += links_pr / len(user["endorses"])

pr = next_pr

return pr

if __name__ == "__main__":

print "Betweenness Centrality"


for user in users:
print user["id"], user["betweenness_centrality"]
print

print "Closeness Centrality"


for user in users:
print user["id"], user["closeness_centrality"]
print

print "Eigenvector Centrality"


for user_id, centrality in enumerate(eigenvector_centralities):
print user_id, centrality
print

print "PageRank"
for user_id, pr in page_rank(users).iteritems():
print user_id, pr

PRÁTICA E LABORATÓRIO II 87
raw_input( )

4.4 Sistemas recomendadores

Um problema de dados em rede comum é a produção de algum tipo de


recomendação. A Netflix, por exemplo, recomenda filmes que você poderia querer
assistir. A Amazon recomenda produtos que você poderia querer comprar. O Twitter
recomenda usuários para que você os siga.

Os sistemas de recomendação utilizam um mecanismo de combinação social


denominado social engine motor para facilitar as possíveis recomendações.

Dessa forma, os sistemas de recomendação combinam várias técnicas


computacionais para selecionar itens personalizados com base nos interesses de seus
usuários.

Para facilitar essa prática, utilizaremos o conjunto de dados users_interests, que já


usamos anteriormente.

users_interests = [
["Hadoop", "Big Data", "HBase", "Java", "Spark", "Storm", "Cassandra"],
PRÁTICA E LABORATÓRIO II 88
["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"]
]

Uma abordagem fácil para isto é simplesmente recomendar aquilo que é considerado
popular:

popular_interests = Counter(interest
for user_interests in users_interests
for interest in user_interests).most_common()

Que se parece com alguma coisa que já tenhamos pensado ou necessitado um dia:

[(‘Phyton’, 4),
(‘R’, 4),
(‘Java’, 3),
(‘regression’, 3),
(‘statistics’, 3),
(‘propability’, 3),

PRÁTICA E LABORATÓRIO II 89
# ...
]

Tendo computado isso, podemos apenas sugerir a um usuário os interesses mais


populares pelos quais ele ainda não está interessado:

def most_popular_new_interests(user_interests, max_results=5):


suggestions = [(interest, frequency)
for interest, frequency in popular_interests
if interest not in user_interests]
return suggestions[:max_results]

4.5 Filtragem colaborativa – Base usuários

Uma forma de levar em consideração os interesses do usuário é procurar aqueles que


são similares a ele, e então sugerir as coisas nas quais aqueles usuários são
interessados.

Para tanto, precisaremos de uma forma de medição para saber quão similares os dois
usuários são. Então, utilizaremos uma métrica chamada similaridade do cosseno,
conforme ilustração do gráfico a seguir:

Dado dois vetores, v e w, é definida como:


PRÁTICA E LABORATÓRIO II 90
def cosine_similarity(v, w):
return dot(v, w) / math.sqrt(dot(v, v) * dot(w, w))

Ela mede o ângulo entre v e w. Se v e w apontam para a mesma direção, então o


numerador e o denominador são iguais e sua similaridade do cosseno é igual a 1. Se
v e w apontam para direções opostas, sua similaridade do cosseno é igual a -1. Se v
é igual a 0 sempre que w não é, e vice-versa, então dot (v, w) é igual a 0 e sua
similaridade do cosseno será 0.

4.6 Filtragem colaborativa – Base itens

De acordo com Grus (2016, p. 272), uma abordagem alternativa é computar


similaridade entre interesses diretamente. Podemos então gerar sugestões para cada
usuário agregando interesses que são similares a seus interesses atuais.

Inicialmente, deve-se transpor a matriz de interesse do nosso usuário para que linhas
correspondam a interesses e colunas correspondam a usuários.

interest_user_matrix = [[user_interest_vector[j]
for user_interest_vector in user_interest_matrix]
for j, _ in enumerate(unique_interests)]

Seu funcionamento está relacionado a usuários com interesse e a outros sem nenhum
interesse. Por exemplo, unique_interests[0] é Big Data e
interest_user_matrix[0] também é:
[1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0]

Depois disso, novamente podemos usar a similaridade do cosseno. Então, se


precisamente os mesmos usuários estão interessados em dois tópicos, suas
similaridades serão iguais a 1. Porém, se nenhum dos dois usuários estiver interessado
em ambos os tópicos, a similaridade será igual a 0.
PRÁTICA E LABORATÓRIO II 91
interest_similarities = [[cosine_similarity(user_vector_i, user_vector_j)
for user_vector_j in interest_user_matrix]
for user_vector_i in interest_user_matrix]

Podemos, portanto, gerar inúmeras recomendações para certo grupo de usuários, de


acordo com seus interesses.

A seguir, segue o código-fonte em Phyton do programa acerca de sistemas


recomendadores referenciado por Grus (2016).

# Sistemas Recomendadores
from __future__ import division
import math, random
from collections import defaultdict, Counter
from linear_algebra import dot

users_interests = [
["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"],

PRÁTICA E LABORATÓRIO II 92
["databases", "HBase", "Postgres", "MySQL", "MongoDB"],
["libsvm", "regression", "support vector machines"]
]

popular_interests = Counter(interest
for user_interests in users_interests
for interest in user_interests).most_common()

def most_popular_new_interests(user_interests, max_results=5):


suggestions = [(interest, frequency)
for interest, frequency in popular_interests
if interest not in user_interests]
return suggestions[:max_results]

#
# user-based filtering
#

def cosine_similarity(v, w):


return dot(v, w) / math.sqrt(dot(v, v) * dot(w, w))

unique_interests = sorted(list({ interest


for user_interests in users_interests
for interest in user_interests }))

def make_user_interest_vector(user_interests):
"""given a list of interests, produce a vector whose i-th element is 1
if unique_interests[i] is in the list, 0 otherwise"""
return [1 if interest in user_interests else 0
for interest in unique_interests]

PRÁTICA E LABORATÓRIO II 93
user_interest_matrix = map(make_user_interest_vector, users_interests)

user_similarities = [[cosine_similarity(interest_vector_i,
interest_vector_j)
for interest_vector_j in user_interest_matrix]
for interest_vector_i in user_interest_matrix]

def most_similar_users_to(user_id):
pairs = [(other_user_id, similarity) # find other
for other_user_id, similarity in # users with
enumerate(user_similarities[user_id]) # nonzero
if user_id != other_user_id and similarity > 0] # similarity

return sorted(pairs, # sort them


key=lambda (_, similarity): similarity, # most similar
reverse=True) # first

def user_based_suggestions(user_id, include_current_interests=False):


# sum up the similarities
suggestions = defaultdict(float)
for other_user_id, similarity in most_similar_users_to(user_id):
for interest in users_interests[other_user_id]:
suggestions[interest] += similarity

# convert them to a sorted list


suggestions = sorted(suggestions.items(),
key=lambda (_, weight): weight,
reverse=True)

# and (maybe) exclude already-interests

PRÁTICA E LABORATÓRIO II 94
if include_current_interests:
return suggestions
else:
return [(suggestion, weight)
for suggestion, weight in suggestions
if suggestion not in users_interests[user_id]]

#
# Item-Based Collaborative Filtering
#

interest_user_matrix = [[user_interest_vector[j]
for user_interest_vector in user_interest_matrix]
for j, _ in enumerate(unique_interests)]

interest_similarities = [[cosine_similarity(user_vector_i, user_vector_j)


for user_vector_j in interest_user_matrix]
for user_vector_i in interest_user_matrix]

def most_similar_interests_to(interest_id):
similarities = interest_similarities[interest_id]
pairs = [(unique_interests[other_interest_id], similarity)
for other_interest_id, similarity in enumerate(similarities)
if interest_id != other_interest_id and similarity > 0]
return sorted(pairs,
key=lambda (_, similarity): similarity,
reverse=True)

def item_based_suggestions(user_id, include_current_interests=False):


suggestions = defaultdict(float)
user_interest_vector = user_interest_matrix[user_id]

PRÁTICA E LABORATÓRIO II 95
for interest_id, is_interested in enumerate(user_interest_vector):
if is_interested == 1:
similar_interests = most_similar_interests_to(interest_id)
for interest, similarity in similar_interests:
suggestions[interest] += similarity

suggestions = sorted(suggestions.items(),
key=lambda (_, similarity): similarity,
reverse=True)

if include_current_interests:
return suggestions
else:
return [(suggestion, weight)
for suggestion, weight in suggestions
if suggestion not in users_interests[user_id]]

if __name__ == "__main__":

print "Popular Interests"


print popular_interests
print

print "Most Popular New Interests"


print "already like:", ["NoSQL", "MongoDB", "Cassandra", "HBase",
"Postgres"]
print most_popular_new_interests(["NoSQL", "MongoDB", "Cassandra",
"HBase", "Postgres"])
print

PRÁTICA E LABORATÓRIO II 96
print "already like:", ["R", "Python", "statistics", "regression",
"probability"]
print most_popular_new_interests(["R", "Python", "statistics",
"regression", "probability"])
print

print "User based similarity"


print "most similar to 0"
print most_similar_users_to(0)

print "Suggestions for 0"


print user_based_suggestions(0)
print

print "Item based similarity"


print "most similar to 'Big Data'"
print most_similar_interests_to(0)
print

print "suggestions for user 0"


print item_based_suggestions(0)
raw_input( )

PRÁTICA E LABORATÓRIO II 97
Referências

AMARAL, Fernando. Introdução à ciência de dados: mineração de dados e big


data. Rio de Janeiro: Alta Books, 2016.

BOENTE, Alfredo. Projeto de cloud computing. Apostila do curso de pós-graduação


MBA em computação em nuvem da Universidade Estácio de Sá, 2015.

CORRAR, Luiz J.; PAULO, Edilson; DIAS FILHO, José Maria. et al. (Coordenadores).
Análise multivariada para os cursos de administração, ciências contábeis e
economia. SãoPaulo: Atlas, 2007.

DAVENPORT, Thomas Hayes; PATIL, Dhanurjay. Data Scientist: The Sexiest Job of the
21st Century, Harvard Business Review, October, 2012.

GRUS, Joel. Data science do zero: primeiras regras com o Phyton. Rio de Janeiro:
Alta Books, 2016.

MUELLER, John Paul. Começando a programar em Phyton: para leigos. Rio de


Janeiro: Alta Books, 2016.

MARTINS, João Pavão. Programação em Phyton: introdução à programação


utilizando múltiplos paradigmas. Lisboa: IST Press, 2015.

PROVOST, Foster; FAWCETT, Tom. Data science for business: what you nedd to
know about data mining and data-analytic thinking. [s./l.]: Spring, 2015.

VERAS, Manoel. Arquitetura de nuvem: Amazon Web Services (AWS). Rio de


Janeiro: Brasport, 2013.

PRÁTICA E LABORATÓRIO II 98

Anda mungkin juga menyukai