In [1]:
# ignore esse código inicial, é apenas para preparar a página
from IPython.display import YouTubeVideo, HTML, Image, display
css_file = './modelo.css'
HTML(open(css_file, "r").read())
Out[1]:
Para gerar um texto aleatório, uma técnica bastante uitlizada é a de Cadeia de Markov.
Cadeia de Markov, de forma simplificada, diz que um evento temporal no instante $t$ é influenciado por todos os eventos anteriores $i=0 \dots t-1$. Porém, apenas os últimos eventos são os mais relevantes.
Em outras palavras, para fazer a previsão do tempo, embora o clima do início dos tempos tenha influência, utilizando apenas os 7 últimos dias já nos garantem uma boa probabilidade de acerto.
Em textos escritos por seres humanos, percebemos a mesma situação em que a probabilidade da $i$-ésima palavra ocorrer é influenciada por todas as palavras que apareceram antes dela, mas apenas um pequeno conjunto de palavras anteriores exerce real influência.
Vamos assumir uma Cadeia de Markov de palavras em um texto para calcular a probabilidade de ocorrer certa palavra $w$ dado que a palavra anterior foi $y$, ou $P(w | y)$.
Para isso vamos criar um dicionário P
cuja chave será uma palavra e o valor será uma lista de palavras, com repetição, que ocorrem após de determinada palavra.
In [2]:
P = {}
# do texto anterior
P['de'] = ['palavras','determinada']
A probabilidade de uma palavra $w$ ocorrer dado a palavra $y$ é determinada por:
$$ \frac{\# w|y}{\#y} $$com $\# w|y$ sendo a quantidade de ocorrência da palavra $w$ na lista da palavra $y$ e $\#y$ o tamanho da lista de $y$.
In [3]:
# vamos contar quantas vezes o termo 'palavras' ocorre
contagem = sum([1 for i in P['de'] if i == 'palavras'])
tamanho = len(P['de'])
prob = contagem/float(tamanho)
print prob
Mas a operação de criação de uma lista e soma pode tomar muito tempo em nosso programa.
Podemos então fazer com que o valor de cada entrada do dicionário seja um dicionário de contagem de palavras!
In [4]:
P['de'] = {}
P['de']['palavras'] = 1
P['de']['determinada'] = 1
Dessa forma, bastaria fazer:
In [5]:
prob = P['de']['palavras']/float(len(P['de']))
print prob
CUIDADO: se tentarmos avaliar a contagem de uma palavra que não existe, receberemos uma mensagem de erro.
In [6]:
print P['gato']
Para resolver isso podemos utilizar o comando in
para verificar se uma entrada consta do dicionário e o comando .get()
para solucionar o problema:
In [7]:
p = P.get( 'de', {} )
prob1 = p.get('palavras',0)/float(len(P['de']))
prob2 = p.get('atirei',0)/float(len(P['de']))
print prob1, prob2
Também temos como alternativa o uso do tipo Counter
da biblioteca collections
, ele cria automaticamente um contador de forma simplificada sem a preocupação com elementos inexistentes:
In [13]:
from collections import Counter
P = {}
P['de'] = Counter()
P['de'].update(['palavras', 'determinada'])
p = P.get('de',Counter())
print p['palavras']/float(len(P['de']))
print p['gato']/float(len(P['de']))
Para termos uma boa estimativa das probabilidades, precisamos ler tais probabilidades de um texto grande.
No Python, a melhor forma de ler um arquivo de textos é através da função open()
da biblioteca 'codecs'.
Essa função recebe $3$ parâmetros:
In [9]:
import codecs
f = codecs.open('texto', 'r', 'utf-8') # abre um arquivo para leitura
i = 0
for linha in f: # para cada linha do texto
palavras = linha.split() # gera uma lista de palavras separadas por espaço
print palavras
i = i+1
if i>5:
break
f.close() # fecha o arquivo
Nota: as strings codificadas em utf-8 são precedidas de u. Essa codificação tem maior suporte à acentuação e símbolos:
In [11]:
print u'üṕ❤'
Outro ponto é que temos que percorrer uma lista de duplas de palavras, ou seja, dada uma lista de palavras percorrer a palavra w e a palavra seguinte a ela.
Utilizaremos a função zip()
que permite juntar duas listas formando duplas de seus elementos:
In [22]:
print zip(palavras,palavras)
print
# queremos que a palavra 0 até n-1 forme dupla com a palavra 1 até n
print zip(palavras[:-1], palavras[1:])
Uma vez que temos listas de palavras, podemos juntar com tudo que aprendemos até então e formar nosso dicionário de contagens:
In [19]:
from collections import defaultdict
f = codecs.open('texto', 'r', 'utf-8') # abre um arquivo para leitura
P = defaultdict(Counter)
for linha in f: # para cada linha do texto
palavras = linha.split() # gera uma lista de palavras separadas por espaço
for y,w in zip(palavras[:-1],palavras[1:]):
P[y].update([w])
f.close() # fecha o arquivo
print P.get(u'é',Counter())
print
print P.get(u'É',Counter())
Ainda temos um problema, reparem que esse contador não diferencia palavras minúsculas das maiúsculas.
Resolveremos isso normalizando todas as palavras para minúsculas:
In [20]:
from collections import defaultdict
f = codecs.open('texto', 'r', 'utf-8') # abre um arquivo para leitura
P = defaultdict(Counter)
for linha in f: # para cada linha do texto
palavras = linha.split() # gera uma lista de palavras separadas por espaço
for y,w in zip(palavras[:-1],palavras[1:]):
P[y.lower()].update([w.lower()])
f.close() # fecha o arquivo
print P.get(u'é',Counter())
print
print P.get(u'É',Counter())
Finalmente podemos criar o programa principal que será composto de:
In [23]:
from collections import Counter, defaultdict
def GeraFreq( arquivo ):
f = codecs.open(arquivo, 'r', 'utf-8') # abre um arquivo para leitura
P = defaultdict(Counter)
for linha in f: # para cada linha do texto
palavras = linha.split() # gera uma lista de palavras separadas por espaço
for y,w in zip(palavras[:-1],palavras[1:]):
P[y.lower()].update([w.lower()])
f.close() # fecha o arquivo
return P
def GeraFrase(palavra, N, P):
frase = [palavra]
for i in range(N):
p = P.get(palavra, Counter())
if len(p) == 0:
return ' '.join(frase)
frase.append( Escolhe(p) )
return ' '.join(frase)
A função GeraFrase()
inicia uma frase em forma de lista de palavras e repete $N$ vezes o procedimento:
A função escolhe deve receber um Counter
contendo as palavras e suas frequências e retornar uma palavra aleatória, com probabilidade proporcional as frequências.
Para isso utilizaremos a função choice()
da biblioteca random
. Essa função recebe uma lista como parâmetro e retorna um dos elementos dessa lista.
Uma forma de simular a probabilidade proporcional a frequência é repetir cada elemento da lista o mesmo número de frequência dele no dicionário.
In [25]:
import random
def Escolhe(p):
lista = [palavra for palavra, freq in p.iteritems() for i in range(freq)]
return random.choice(lista)
P = GeraFreq('texto')
GeraFrase('para', 10, P)
Out[25]:
In [ ]: