Data Science, Machine Learning, Recuperação da Informação

O Word2Vec Ilustrado

O conceito de embeddings é uma das idéias mais fascinantes no aprendizado de máquina. Se você já usou o Siri, o Google Assistente, o Alexa, o Google Tradutor ou até mesmo o teclado do smartphone com a previsão da próxima palavra, então provavelmente você se beneficiou dessa ideia que se tornou central nos modelos de processamento de linguagem natural. Houve um grande desenvolvimento nas últimas duas décadas no uso de embeddings para modelos neurais (desenvolvimentos recentes incluem incorporação de palavras contextualizadas que levam a modelos de ponta como o BERT e o GPT2).

O Word2vec é um método para criar de maneira eficiente a incorporação de palavras e existe desde 2013. Mas, além de sua utilidade como método de incorporação de palavras, alguns de seus conceitos demonstraram ser eficazes na criação de mecanismos de recomendação e no sentido de dados sequenciais em tarefas comerciais, não linguísticas. Empresas como a Airbnb , a Alibaba , a Spotify e a Anghami se beneficiaram ao esculpir essa peça brilhante de maquinário do mundo da NLP e usá-la na produção para capacitar uma nova geração de mecanismos de recomendação.

Neste post, falaremos sobre o conceito de incorporação e a mecânica de geração de integrações com o word2vec. Mas vamos começar com um exemplo para nos familiarizarmos com o uso de vetores para representar as coisas. Você sabia que uma lista de cinco números (um vetor) pode representar muito sobre sua personalidade?

Personality Embeddings: Como você é?

“Dou-lhe o camaleão do deserto, cuja capacidade de se misturar com o pano de fundo diz tudo que você precisa saber sobre as raízes da ecologia e os fundamentos de uma identidade pessoal” ~ Children of Dune

Em uma escala de 0 a 100, quão introvertido (introverted) / extrovertido (extraverted) você é (onde 0 é o mais introvertido e 100 é o mais extrovertido)?

Você já fez um teste de personalidade como o MBTI – ou melhor, o teste Big Five Personality Traits ? Se você não tiver feito ainda, esses são testes que fazem uma lista de perguntas e, em seguida, pontua em vários eixos, sendo um deles  introversão / extroversão.


Exemplo do resultado de um teste Big Five Personality Trait. Ele pode realmente dizer muito sobre si mesmo e é mostrado para ter capacidade de previsão no sucesso acadêmico , pessoal e profissional . Este é um lugar para encontrar seus resultados.

Imagine que eu tenha marcado 38/100 como minha pontuação de introversão / extroversão. podemos traçar isso desta maneira:

Vamos mudar o intervalo para ser de -1 a 1:

Você acha que conhece bem uma pessoa apenas com essa informação de introversão / extroversão? Creio que não, pessoas são complexas – então, vamos adicionar outra dimensão – a pontuação de uma outra característica do teste.


Podemos representar as duas dimensões como um ponto no gráfico ou, melhor ainda, como um vetor da origem até aquele ponto. Temos ferramentas incríveis para lidar com vetores que serão úteis muito em breve. Eu escondi quais traços estamos tramando apenas para que você se acostume a não saber o que cada dimensão representa – mas ainda assim obter muito valor da representação vetorial da personalidade de uma pessoa.

Podemos agora dizer que esse vetor representa parcialmente minha personalidade. A utilidade de tal representação vem quando você quer comparar duas outras pessoas para mim. Digamos que eu seja atingido por um buse preciso ser substituído por alguém com uma personalidade parecida. Na figura a seguir, qual das duas pessoas é mais parecida comigo?

Ao lidar com vetores, uma maneira comum de calcular uma pontuação de similaridade é a similaridade cosseno :


A Person # 1 é mais parecida comigo (Jay) na personalidade. Vetores apontando na mesma direção (o comprimento também desempenha um papel) têm um maior valor de similaridade de cosseno.

Mais uma vez, duas dimensões não são suficientes para capturar informações suficientes sobre como as pessoas são diferentes. Décadas de pesquisa em psicologia levaram a cinco características principais (e muitas sub-características). Então, vamos usar todas as cinco dimensões em nossa comparação:

O problema com cinco dimensões é que perdemos a capacidade de desenhar pequenas setas em duas dimensões. Este é um desafio comum no aprendizado de máquina, onde muitas vezes temos que pensar em um espaço de dimensão superior. O bom é que a semelhança de cosseno ainda funciona. Funciona com qualquer número de dimensões:


O cosine_similarity funciona para qualquer número de dimensões. Essas são pontuações muito melhores porque são calculadas com base em uma representação de maior resolução das coisas comparadas.

No final desta seção, desejo que saibamos duas ideias centrais:

  1. Podemos representar pessoas (e coisas) como vetores de números (o que é ótimo para máquinas!).
  2. Podemos calcular facilmente como vetores semelhantes são um ao outro.

Word Embeddings

Com esse entendimento, podemos examinar exemplos de vetores de palavras treinados (também chamados de incorporação de palavras) e começar a examinar algumas de suas propriedades interessantes.

Esta é uma palavra incorporada para a palavra “king” (rei) (vetor da GloVe treinado na Wikipedia):

[ 0.50451 , 0.68607 , -0.59517 , -0.022801, 0.60046 , -0.13498 , -0.08813 , 0.47377 , -0.61798 , -0.31012 , -0.076666, 1.493 , -0.034189, -0.98173 , 0.68229 , 0.81722 , -0.51874 , -0.31503 , -0.55809 , 0.66421 , 0.1961 , -0.13495 , -0.11476 , -0.30344 , 0.41177 , -2.223 , -1.0756 , -1.0783 , -0.34354 , 0.33505 , 1.9927 , -0.04234 , -0.64319 , 0.71125 , 0.49159 , 0.16754 , 0.34344 , -0.25663 , -0.8523 , 0.1661 , 0.40102 , 1.1685 , -1.0137 , -0.21585 , -0.15155 , 0.78321 , -0.91241 , -1.6106 , -0.64426 , -0.51042 ]

É uma lista de 50 números. Não podemos dizer muito olhando para os valores. Mas vamos visualizar um pouco para que possamos comparar outros vetores de palavras. Vamos colocar todos esses números em uma linha:

Vamos codificar em cores as células com base em seus valores (vermelho se estiverem perto de 2, brancas se estiverem próximas de 0, azuis se estiverem próximas de -2):

Continuaremos ignorando os números e observando apenas as cores para indicar os valores das células. Vamos agora contrastar “king” contra outras palavras:

Veja como “man (homem)” e “woman (mulher)” são muito mais semelhantes entre si do que qualquer um deles é “king”? Isso te diz uma coisa. Essas representações vetoriais capturam um pouco da informação / significado / associação dessas palavras.

Aqui está outra lista de exemplos (compare verificando as colunas à procura de colunas com cores semelhantes):

Algumas coisas a salientar:

  1. Há uma coluna vermelha direta através de todas essas palavras diferentes. Eles são semelhantes nessa dimensão (e não sabemos o que cada dimensão codifica)
  2. Você pode ver como “woman – mulher” e “girl – menina” são semelhantes entre si em muitos lugares. O mesmo com “man” e “boy
  3. Boy – Menino” e “girl – menina” também têm lugares onde eles são parecidos entre si, mas diferentes de “woman – mulher” ou “man – homem”. Poderiam estes ser codificação para uma concepção vaga da juventude? possível.
  4. Todas, exceto a última palavra, são palavras representando pessoas. Eu adicionei um objeto (water – água) para mostrar as diferenças entre as categorias. Você pode, por exemplo, ver a coluna azul indo até o fim e parando antes da inclusão para “água”.
  5. Há lugares claros onde “king” e “queen” são semelhantes entre si e distintos de todos os outros. Poderiam estes estar codificando para um conceito vago de realeza?

Analogias

Os famosos exemplos que mostram uma propriedade incrível de embeddings é o conceito de analogias. Podemos adicionar e subtrair a palavra “embeddings” e chegar a resultados interessantes. O exemplo mais famoso é a fórmula: “king” – “man” + “woman”:


Usando a biblioteca Gensim em Python, podemos adicionar e subtrair vetores de palavras e encontrar as palavras mais semelhantes ao vetor resultante. A imagem mostra uma lista das palavras mais semelhantes, cada uma com sua semelhança de cosseno.

Podemos visualizar essa analogia como fizemos anteriormente:


O vetor resultante de “king – man + woman” não é exatamente igual a “queen“, mas “queen” é a palavra mais próxima das 400.000 palavras que temos nesta coleção.

Agora que analisamos a incorporação de palavras treinadas, vamos aprender mais sobre o processo de treinamento. Mas antes de chegarmos ao word2vec, precisamos olhar para um pai conceitual da incorporação de palavras: o modelo de linguagem neural.

Modelagem de Linguagem

Um exemplo de um aplicativo NLP, seria o recurso de previsão de palavra seguinte de um teclado de smartphone. É um recurso que bilhões de pessoas usam centenas de vezes todos os dias.

A previsão da palavra seguinte é uma tarefa que pode ser abordada por um modelo de linguagem . Um modelo de linguagem pode obter uma lista de palavras (digamos duas palavras) e tentar prever a palavra que as segue.

Na imagem acima, podemos pensar no modelo como um que pegou essas duas palavras verdes ( thou  shalt ) e retornou uma lista de sugestões (“não” sendo a que tem maior probabilidade):

Podemos pensar no modelo como se parecesse com essa caixa preta:

Mas na prática, o modelo não produz apenas uma palavra. Na verdade, gera uma pontuação de probabilidade para todas as palavras que ele conhece (o “vocabulário” do modelo, que pode variar de alguns milhares a mais de um milhão de palavras). O aplicativo de teclado, então, precisa encontrar as palavras com as pontuações mais altas e apresentá-las ao usuário.


A saída do modelo de linguagem neural é uma pontuação de probabilidade para todas as palavras que o modelo conhece. Estamos nos referindo à probabilidade como uma porcentagem aqui, mas 40% seria representado como 0,4 no vetor de saída.

Depois de treinados, os primeiros modelos de linguagem neural ( Bengio 2003 ) calculariam uma previsão em três etapas:

O primeiro passo é o mais relevante para nós quando discutimos os embeddings. Um dos resultados do processo de treinamento foi essa matriz que contém uma incorporação para cada palavra em nosso vocabulário. Durante o tempo de previsão, apenas procuramos os encaixes da palavra de entrada e os usamos para calcular a previsão:

Vamos agora nos voltar para o processo de treinamento para aprender mais sobre como esta matriz de incorporação foi desenvolvida.

Treinamento de Modelo de Linguagem

Os modelos de linguagem têm uma enorme vantagem sobre a maioria dos outros modelos de aprendizado de máquina. Essa vantagem é que somos capazes de treiná-los na execução de texto – o que temos em abundância. Pense em todos os livros, artigos, conteúdo da Wikipedia e outras formas de dados de texto que temos por aí. Compare isso com muitos outros modelos de aprendizado de máquina que precisam de recursos feitos à mão e dados especialmente coletados.

As palavras obtêm suas incorporações por meio de outras palavras nas quais tendem a aparecer ao lado. A mecânica disso é que

  1. Recebemos muitos dados de texto (digamos, todos os artigos da Wikipédia, por exemplo). então
  2. Nós temos uma janela (digamos, de três palavras) que nós deslizamos contra todo esse texto.
  3. A janela deslizante gera amostras de treinamento para o nosso modelo

À medida que essa janela desliza no texto, geramos (virtualmente) um conjunto de dados que usamos para treinar um modelo. Para ver exatamente como isso é feito, vamos ver como a janela deslizante processa essa frase:

Quando começamos, a janela está nas primeiras três palavras da frase:

Nós consideramos as duas primeiras palavras como recursos e a terceira palavra como rótulo:


Agora geramos o primeiro exemplo no conjunto de dados que podemos usar posteriormente para treinar um modelo de linguagem.

Em seguida, deslizamos nossa janela para a próxima posição e criamos uma segunda amostra:


Um segundo exemplo é agora gerado.

E em breve teremos um conjunto de dados maior, cujas palavras tendem a aparecer depois de diferentes pares de palavras:

Na prática, os modelos tendem a ser treinados enquanto deslizamos a janela. Mas acho mais claro separar logicamente a fase de “geração de dataset” da fase de treinamento. Além de abordagens baseadas em redes neurais para modelagem de linguagem, uma técnica chamada N-grams era comumente usada para treinar modelos de linguagem (consulte o Capítulo 3 do processamento de fala e linguagem ). Para ver como isso muda de N-gramas para modelos neurais reflete sobre produtos reais, aqui está um post de 2015 do Swiftkey , meu teclado Android favorito, apresentando seu modelo de linguagem neural e comparando-o com seu modelo anterior de N-gram. Eu gosto desse exemplo porque ele mostra como as propriedades algorítmicas dos embeddings podem ser descritas no discurso de marketing.

Olhe para ambos os lados

“Paradoxo é um ponteiro que diz para você olhar para além disso. Se paradoxos te incomodam, isso trai seu profundo desejo por absolutos. O relativista trata um paradoxo meramente como interessante, talvez divertido ou mesmo, terrível, educacional.” ~ Deus Imperador das Dunas

Sabendo o que você sabe mais cedo no post, preencha o espaço em branco:

O contexto que lhe dei aqui é de cinco palavras antes da palavra em branco (e uma menção anterior de “bus”). Tenho certeza de que a maioria das pessoas imaginaria que a palavra busentra em branco. Mas e se eu te desse mais uma informação – uma palavra depois do branco, isso mudaria sua resposta?

Isso muda completamente o que deve ficar em branco. a palavra red é agora a mais provável de entrar no espaço em branco. O que aprendemos disso são as palavras antes e depois de uma palavra específica ter valor informativo. Acontece que a contabilização de ambas as direções (palavras à esquerda e à direita da palavra que estamos adivinhando) leva a uma melhor incorporação de palavras. Vamos ver como podemos ajustar a forma como estamos treinando o modelo para explicar isso.

Skipgram

Em vez de apenas olhar duas palavras antes da palavra alvo, também podemos ver duas palavras depois dela.

Se fizermos isso, o conjunto de dados que estamos virtualmente construindo e treinando contra o modelo ficaria assim:

Isso é chamado de arquitetura de Continuous Bag of Words e é descrito em um dos artigos word2vec [pdf]. Outra arquitetura que também tendia a mostrar ótimos resultados faz as coisas um pouco diferente.

Em vez de adivinhar uma palavra com base em seu contexto (as palavras antes e depois), essa outra arquitetura tenta adivinhar palavras vizinhas usando a palavra atual. Podemos pensar na janela que ela desliza contra o texto de treinamento com a seguinte aparência:


A palavra no slot verde seria a palavra de entrada, cada caixa rosa seria uma saída possível.

As caixas cor-de-rosa estão em tons diferentes porque esta janela deslizante cria quatro amostras separadas em nosso conjunto de dados de treinamento:

Esse método é chamado de arquitetura skipgram . Podemos visualizar a janela deslizante fazendo o seguinte:

Isso adicionaria essas quatro amostras ao nosso conjunto de dados de treinamento:

Então, nós deslizamos nossa janela para a próxima posição:

O que gera nossos próximos quatro exemplos:

Algumas posições depois, temos muito mais exemplos:

Revisitando o processo de treinamento

Agora que temos nosso conjunto de dados de treinamento skipgram que extraímos do texto existente, vamos dar uma olhada em como o usamos para treinar um modelo básico de linguagem neural que prevê a palavra vizinha.

Começamos com a primeira amostra em nosso conjunto de dados. Pegamos o recurso e alimentamos o modelo não treinado, pedindo para prever uma palavra vizinha apropriada.

O modelo conduz as três etapas e gera um vetor de previsão (com uma probabilidade atribuída a cada palavra em seu vocabulário). Como o modelo não é treinado, a previsão é certa de estar errada nesse estágio. Mas está tudo bem. Sabemos que palavra deveria ter adivinhado – a célula label / output na linha que estamos usando atualmente para treinar o modelo:


O ‘vetor alvo’ é aquele em que a palavra alvo tem a probabilidade 1 e todas as outras palavras têm a probabilidade 0.

A que distância estava o modelo? Subtraímos os dois vetores resultando em um vetor de erro:

Agora, esse vetor de erro pode ser usado para atualizar o modelo, então, da próxima vez, é mais provável que ele adivinhe thou quando recebe not como entrada.

E isso conclui o primeiro passo do treinamento. Passamos a fazer o mesmo processo com o próximo exemplo no nosso conjunto de dados e, em seguida, no seguinte, até cobrirmos todas as amostras no conjunto de dados. Isso conclui uma época de treinamento. Fazemos isso de novo por um certo número de épocas, e então teríamos nosso modelo treinado e poderíamos extrair a matriz de incorporação dele e usá-lo para qualquer outra aplicação.

Embora isso amplie nossa compreensão do processo, ainda não é como o word2vec é realmente treinado. Estamos perdendo algumas idéias-chave.

Amostragem Negativa

Lembre-se das três etapas de como esse modelo de linguagem neural calcula sua previsão:

O terceiro passo é muito caro do ponto de vista computacional – especialmente sabendo que faremos isso uma vez para cada amostra de treinamento em nosso conjunto de dados (facilmente dezenas de milhões de vezes). Precisamos fazer algo para melhorar o desempenho.

Uma maneira é dividir nossa meta em duas etapas:

  1. Gere incorporações de palavras de alta qualidade (não se preocupe com a previsão da próxima palavra).
  2. Use esses encaixes de alta qualidade para treinar um modelo de idioma (para fazer a previsão da próxima palavra).

Vamos nos concentrar no passo 1. nesta postagem, pois estamos nos concentrando em incorporações. Para gerar inserções de alta qualidade usando um modelo de alto desempenho, podemos alternar a tarefa do modelo de prever uma palavra vizinha:

E mude para um modelo que pegue a palavra de entrada e saída, e produza uma pontuação indicando se eles são vizinhos ou não (0 para “não vizinhos”, 1 para “vizinhos”).

Esse simples switch muda o modelo que precisamos de uma rede neural para um modelo de regressão logística – assim, torna-se muito mais simples e muito mais rápido calcular.

Essa opção exige que nós mudemos a estrutura do nosso conjunto de dados – o rótulo agora é uma nova coluna com valores 0 ou 1. Eles serão todos 1, pois todas as palavras que adicionamos são vizinhas.

Isso agora pode ser calculado a uma velocidade incrível – processando milhões de exemplos em minutos. Mas há uma lacuna que precisamos fechar. Se todos os nossos exemplos forem positivos (alvo: 1), nos abriremos para a possibilidade de um modelo smartass que sempre retorne 1 – atingindo 100% de precisão, mas não aprendendo nada e gerando confinamentos de lixo.

Para resolver isso, precisamos introduzir amostras negativas em nosso conjunto de dados – amostras de palavras que não são vizinhas. Nosso modelo precisa retornar 0 para essas amostras. Agora, esse é um desafio que o modelo tem que trabalhar duro para resolver – mas ainda em velocidade acelerada.


Para cada amostra em nosso conjunto de dados, adicionamos exemplos negativos . Aqueles têm a mesma palavra de entrada e um rótulo 0.

Mas o que preenchemos como palavras de saída? Nós amostramos aleatoriamente palavras do nosso vocabulário

Esta ideia é inspirada na estimativa de contraste de ruído [pdf]. Estamos contrastando o sinal real (exemplos positivos de palavras vizinhas) com ruído (palavras selecionadas aleatoriamente que não são vizinhas). Isso leva a uma grande troca de eficiência computacional e estatística.

Skipgram com amostragem negativa (SGNS)

Nós cobrimos agora duas das idéias centrais em word2vec: como um par, elas são chamadas de skipgram com amostragem negativa.

Processo de Treinamento Word2vec

“A máquina não pode antecipar todos os problemas de importância para os seres humanos. É a diferença entre bits seriais e um contínuo ininterrupto. Temos um; as máquinas estão confinadas ao outro.” ~ Deus Imperador das Dunas

Agora que estabelecemos as duas ideias centrais do skipgram e da amostragem negativa, podemos examinar mais de perto o processo real de treinamento word2vec.

Antes do início do processo de treinamento, pré-processamos o texto no qual estamos treinando o modelo. Nesta etapa, determinamos o tamanho do nosso vocabulário (vamos chamar isso vocab_size, pense nisso como, digamos, 10.000) e quais palavras pertencem a ele.

No início da fase de treinamento, criamos duas matrizes – uma Embeddingmatriz e uma Contextmatriz. Estas duas matrizes têm uma incorporação para cada palavra em nosso vocabulário (assim vocab_sizeé uma de suas dimensões). A segunda dimensão é quanto tempo queremos que cada incorporação seja ( embedding_size– 300 é um valor comum, mas analisamos um exemplo de 50 anteriormente neste post).

No início do processo de treinamento, inicializamos essas matrizes com valores aleatórios. Então nós começamos o processo de treinamento. Em cada etapa de treinamento, tomamos um exemplo positivo e seus exemplos negativos associados. Vamos pegar nosso primeiro grupo:

Agora temos quatro palavras: a palavra de entrada note as palavras de saída / contexto: thou(o vizinho real) aaron, e taco(os exemplos negativos). Continuamos a procurar os seus envios – para a palavra de entrada, procuramos na Embedding matriz. Para as palavras de contexto, procuramos na Contextmatriz (mesmo que ambas as matrizes tenham uma incorporação para cada palavra em nosso vocabulário).

Em seguida, pegamos o produto escalar da incorporação de entrada com cada uma das incorporações do contexto. Em cada caso, isso resultaria em um número, esse número indica a similaridade da entrada e dos contextos de incorporação

Agora precisamos de uma maneira de transformar essas pontuações em algo que se pareça com probabilidades – precisamos que todas sejam positivas e tenham valores entre zero e um. Esta é uma grande tarefa para o sigmóide , a operação logística .

E agora podemos tratar a saída das operações sigmóides como a saída do modelo para esses exemplos. Você pode ver que tacotem a maior pontuação e aaronainda tem a pontuação mais baixa antes e depois das operações sigmóides.

Agora que o modelo não treinado fez uma previsão e vendo como se tivéssemos um rótulo de destino real para comparar, vamos calcular quanto erro está na previsão do modelo. Para fazer isso, apenas subtraímos as pontuações sigmóides dos rótulos de destino.


error = target – sigmoid_scores

Aí vem a parte de aprendizagem do aprendizado de máquina. Podemos agora usar essa pontuação erro para ajustar as incorporações de notthouaaron, e tacopara que a próxima vez que fazemos esse cálculo, o resultado seria mais perto das pontuações alvo.

Isso conclui a etapa de treinamento. Nós sair dela com um pouco melhores mergulhos para as palavras envolvidas nesta etapa ( notthouaarontaco). Passamos agora para a próxima etapa (a próxima amostra positiva e suas amostras negativas associadas) e fazemos o mesmo processo novamente.

Os embeddings continuam a ser melhorados enquanto percorremos todo o nosso conjunto de dados por várias vezes. Podemos então interromper o processo de treinamento, descartar a Contextmatriz e usar a Embeddingsmatriz como nossos encaixes pré-treinados para a próxima tarefa.

Tamanho da janela e número de amostras negativas

Dois hiperparâmetros chave no processo de treinamento word2vec são o tamanho da janela e o número de amostras negativas.

Diferentes tarefas são atendidas melhor por diferentes tamanhos de janela. Uma heurística é que tamanhos de janela menores (2-15) levam a inserções onde altas pontuações de similaridade entre dois embeddings indicam que as palavras são intercambiáveis (observe que antônimos são frequentemente intercambiáveis ​​se estamos apenas olhando para as palavras ao redor – por exemplo, boas e más freqüentemente aparecem em contextos semelhantes). Tamanhos maiores de janela (15-50, ou até mais) levam a inserções onde a similaridade é mais indicativa de parentesco das palavras. Na prática, você geralmente precisará fornecer anotações que guiam o processo de incorporação levando a um senso de similaridade útil para sua tarefa. O tamanho padrão da janela do Gensim é 5 (duas palavras antes e duas palavras após a palavra de entrada, além da própria palavra de entrada).

O número de amostras negativas é outro fator do processo de treinamento. O papel original prescreve 5-20 como sendo um bom número de amostras negativas. Também afirma que 2-5 parece ser suficiente quando você tem um conjunto de dados suficientemente grande. O padrão do Gensim é de 5 amostras negativas.

Referências e Leituras Adicionais

Autor: Jay Alammar (todos os direitos reservados ao autor)
Traduzido por Google Translator e adaptações por Alex Souza

Deixe um comentário

Preencha os seus dados abaixo ou clique em um ícone para log in:

Logotipo do WordPress.com

Você está comentando utilizando sua conta WordPress.com. Sair /  Alterar )

Foto do Google

Você está comentando utilizando sua conta Google. Sair /  Alterar )

Imagem do Twitter

Você está comentando utilizando sua conta Twitter. Sair /  Alterar )

Foto do Facebook

Você está comentando utilizando sua conta Facebook. Sair /  Alterar )

Conectando a %s