Usando Tensorflow para prever sua playlist no Spotify!

Paulo Eduardo Sampaio
10 min readOct 28, 2019

Há quase um ano, escrevi um texto sobre segmentação onde dizia que em breve escreveria um sobre classificação. Bem… acabei mudando de emprego e nunca me sobrava tempo pra escrever algo maneiro. Um dia desses, assistindo Rock in Rio com minha namorada, falei brincando que provavelmente poderia escrever um modelinho pra decidir se eu ou ela gostaríamos mais de determinada musica. Olha aí que oportunidade de construir alguma coisa divertida e usar pra explicar como algoritmos de classificação funcionam!

Então o que veremos hoje? Dada uma música, será que um algoritmo de machine learning pode identificar quem de nós dois vai gostar mais dela?

Spoiler: Dá sim! Vamos usar as ferramentas de desenvolvedores do spotify para extrair features e o tensorflow para montar uma rede neural capaz de classificar as músicas.

Resumindo: Vamos usar spotify pra extrair features e o tensorflow pra fazer um modelo de classificação

Qual é a música!

Bom, primeiro passo: como conseguir os dados?

Pablo do qual é a música, que depois virou participante do reality “A fazenda”

Bem, embora eu tenha uma bela coleção de discos de vinil, hoje em dia é muito mais prático e fácil ouvir música no glorioso spotify, que sendo uma empresa marota de tecnologia como é, oferece maneiras de não apenas você acessar os dados deles através de APIs, como também algumas informações já prontinhas pra você usar no seu modelo. Então qual foi a ideia: eu e minha namorada passamos não somente a ouvir um pouco mais de música pelo Spotify, mas a também marcar as que gostávamos, para ter um dataset devidamente etiquetado. Não foi fácil pois usualmente estamos ouvindo música fazendo outras coisas e não lembramos de dar like, mas tentamos! Depois de algumas semanas recolhemos os dados, eu tinha cerca de 200 músicas marcadas e ela cerca de 100. É pouco? É… mas já da pra brincar! Você, caro leitor, sendo o jovem dinâmico e conectado que provavelmente é, se quiser recriar essa brincadeira provavelmente vai fazer bem melhor que a gente!

Acessando as musicas através do Spotipy, API de python para acessar os dados do Spotify, temos diversas features disponíveis. Alguns exemplos:

É Paul Stanley… não foi dessa vez…
  • Energy: Diz o Spotify que é uma percepção de intensidade e o quão “cheia” uma musica é. Mas coloquei aqui só porque a de menos energia é aquele hit do pa-pa-pa-papel

Enfim, ele já te da tudo o que você precisa pra fazer um modelinho maroto.

Classificação

Bom… como funciona? Vou tentar explicar aqui a ideia de um tipo de rede neural que é bem fácil de entender. Vem comigo:

  • Digamos que você tenha n musicas e m features
  • Vamos dar um peso w para cada uma das features
  • Cada musica vai receber uma pontuação, que nada mais é que a soma de cada peso vezes cada uma das features (também conhecido como produto interno). Em matematiques:
Estou ocultando aqui os intercepts pra facilitar a interpretação. Foca na ideia.
  • Essa soma passa por uma transformação (f(x)), chamada de transformação logística ou sigmoide, que vai transformá-la em valores de 0 a 1. Se for mais pra 1, é uma música que eu gostaria. Se for 0, uma música que minha namorada gostaria.
f(x) é a transformação logística para dar resposta de 0 a 1

Vamos desenhar isso:

Cada feature vai ser multiplicada por um peso e a soma disso vai passar por uma função de classificação

Legal, temos nossas músicas, nossas features e sabemos quais são minhas e quais são dela, mas não sabemos os pesos que cada feature deve ter. Como escolher esses pesos? Aqui que a magia da matemática se mostra em toda sua sensualidade. A gente sabe qual o resultado tem que dar, certo? Para as minhas musicas tem que dar 1. Para as da Amélia, tem que dar 0. Se der 0 e for uma música minha é um erro e vice-versa. Ou seja, eu preciso escolher os pesos que minimizem o erro!

Método do gradiente (gradient descent)

Se você teve alguma aula de exatas em nível universitário, em algum momento você ouviu falar de derivada, correto? Aprendeu a regra do tombo, regra da cadeia, regra do produto, todas essas coisas. Não aprendeu? Tudo bem, não vamos precisar nada disso, apenas do “conceito” de derivada.

A derivada de primeira ordem nos dá a variação no resultado quando aplicamos uma alteração infinitesimal nas variáveis.

Achou que não ia ter gif da Nazaré?

Calma Nazaré. É tipo assim, o quanto o resultado de uma conta muda se alterarmos só um tiquinho as variáveis. Vamos a um exemplo gráfico.

Imagina essa função nos ponto destacado. Começando pelo ponto vermelho, o que vai acontecer com o y se eu somar um tantinho pequenininho assim no x? O y vai diminuir um tantinho também, certo? Ou seja, a derivada é negativa. E no ponto verde?? Se aumentarmos x um tiquinho só, y vai aumentar, correto? Então a derivada agora é positiva.

Agora pensa assim. E se a gente quisesse encontrar o mínimo dessa função? O pontinho ali em baixo? O famoso mamilo da função? Como fazer? Bem, se a derivada nos dá a taxa de variação, enquanto estiver variando negativamente ainda não chegou no mínimo, certo? Se a variação passar a ser positiva, é porque passamos do mínimo e temos que voltar, correto? Então se eu começar em um ponto qualquer, digamos do ponto vermelho, calcular a derivada, se ela for negativa, eu posso dar um passo a frente que estarei indo em direção ao mínimo. Recalculo a derivada. Se ela continua negativa, dou mais um passinho a frente. Se ela passou a ser positiva, é porque passei do mínimo, preciso então dar um passinho pra trás. Quando a derivada for zero, é sinal que estamos no mínimo! Esse método de resolução se chama gradient descent e é a base de praticamente todos os métodos de otimização para se encontrar a solução de redes neurais.

Pra ser mais formal o passinho chama “learning rate”, e esse método chama gradient descendent mas isso são detalhes…

Entendi tudo, mas o que isso tem a ver com nosso problema??

Então, lembra da maneira como definimos erro? Se considerarmos que o nosso erro é uma função dos pesos, podemos usar o método de derivadas que aprendemos para achar quais pesos levam ao ponto mínimo do erro.

Sofisticando só um pouquinho mais

Entendeu tudo até agora? Então sacastes que vamos ter um peso para cada feature e a uma função aplicada à soma peso*feature vai me dar um score e se for 1 é uma música que eu gosto e se for 0 é da minha namorada. Ótimo, mas dessa maneira, a pontuação vai ser baseada nas features sendo, de certa maneira, “independentes”, percebe? Por exemplo, vai ter um peso pra acousticness e um peso pra danceability. Mas e se eu quiser a interação entre essas features? Por exemplo eu posso em geral não gostar de música acústica e em geral não gostar de música dançante mas adorar quando a música acústica é dançante. Pra captar isso, vamos adicionar mais uma camada (layer) na nossa rede que recebe como input todas as features anteriores… percebe onde estamos chegando??

FC vem de “fully connected”, já que esta camada é conectada com todas as features anteriores

Sim, é uma rede neural simples mas é uma rede neural! É o que se chama de “MLP”, multi layer perceptron. O que essa camada do meio está fazendo é calculando a interação entre as features. Essa layer vai multiplicar cada uma das features e dar um peso pra cada uma dessas multiplicações. Cada unidade que colocamos pode receber mais peso para uma multiplicação, então uma unidade pode se especializar (ter um peso maior) por exemplo em “dançabilidade acústica” outro para “músicas de baixa energia em si bemol” e por aí vai.

Mão na massa

Então vamos lá, hora de abrir aquele seu python maroto e começar a trabalhar. Vamos usar o glorioso tensorflow que agora tem o keras dentro dele pra isso… vamos lá. Ah, antes tem aqueles passinhos padrão né, one-hot-encoding das features categoricas, uma standardizada nas numéricas se quiser, aquele train-test-split, você sabe.

Vamos ao que interessa. Eu tenho um total de 53 features que extraí usando o API do Spotify, então começo minha rede com 53 unidades de input, depois adiciono o meu layer de interações, que vai ter 5 unidades e função de ativação relu e finalmente uma unidade de output, com função sigmoid, também conhecida como logística, que vai me dar a probabilidade de 0 a 1.

input = Input(shape=(53,), name="Input")
x = Dense(5, activation='relu', name="Hidden_1")(input)
output = Dense(1, activation='sigmoid', name="Output")(x)
model = Model(inputs=input, outputs=output)

Podemos usar a função summary para dar uma olhada como as coisas estão:

Model: "model_1"
_________________________________________________________________
Layer (type) Output Shape Param #
=================================================================
Input (InputLayer) [(None, 53)] 0
_________________________________________________________________
Hidden_1 (Dense) (None, 5) 270
_________________________________________________________________
Output (Dense) (None, 1) 6
=================================================================
Total params: 276
Trainable params: 276
Non-trainable params: 0

Beleza, tá batendo com o que queríamos. Vamos calcular 276 parâmetros:

  • Na camada intermediária 53 features * 5 unidades + 5 intercepts = 270
  • No output 5 features que saem do intermediário mais um intercept = 6

Para treinar a rede estou usando uma função auxiliar de early stopping. Isso cria uma regra que ajuda a evitar overfitting ao parar o treinamento quando o resultado no training set continuar melhorando, mas estabilizando (ou piorando) no test set. Enfim, vamos treinar e ver o que acontece.

early_stopping = EarlyStopping(monitor='val_loss', min_delta=0, patience=5, verbose=1, mode='auto', baseline=None, restore_best_weights=True)

history = model.fit(X_train,
y_train,
shuffle=True,
batch_size=128,
epochs=100,
validation_data=[X_test.values, y_test.values],
callbacks=[early_stopping])

Podemos plotar a evolução do treinamento pra ver o que aconteceu:

Resultado do treinamento
  • Vemos aqui que a acurácia no conjunto de treino e de teste chegaram basicamente ao mesmo ponto ali na casa dos 80%.
  • Vemos também que depois da rodada vinte, o resultado da função de erro continuou a decair no conjunto de treino e se manteve estável na de teste, aí que nossa função de early stopping parou e pegou o melhor modelo até então.

Resultados

Hora de ver o que aconteceu. Vamos começar olhando no conjunto de teste quais as músicas estão com pontuação mais alta para cada um de nós:

  • Com maior pontuação, 0.91, temos ele… sim, ele: a lenda, o mito, o herói de New Jersey, Bon Jovi com Wanted Dead or Alive. Concordo… talvez não minha música preferida, mas nem é essa a idéia: a alta pontuação significa uma música que é seguramente mais indicada para mim do que para ela. Pensando que o test set tem 20% de todos os dados, ou seja, umas 60 musicas entre nós dois, é possível que essa seja realmente a mais divisiva.
#voltarockfarofa
  • Com menor pontuação, 0.03, Nando Reis e Ana Vitoria com a musica N. Totalmente de acordo, digamos que não é o tipo de som que entraria na minha playlist, mas é a cara da minha namorada! Mais uma vez, a baixa pontuação não significa uma música que obrigatoriamente eu não goste, é uma música que é mais indicada para a playlist dela do que para a minha.

Agora mais interessante, vamos ver as que o algoritmo errou e o que aconteceu:

  • Uma música que estava na playlist de minha namorada mas o algoritmo previu que deveria estar na minha: Dinossaur Jr com Feel the Pain com 0.88!!! Sensacional, adoro esse som, um erro que faz todo sentido já que acho essa música muito a minha cara, se duvidar fui eu que curti no telefone dela sem querer!!
Ah o rock alternativo dos anos noventa…
  • Uma música que estava na minha playlist mas o algoritmo achou que deveria estar na dela: 1997 do Hateen com 0.07… aí é complicado, erro honesto pois não deveria estar na minha playlist… não lembro de ter colocado, deve ter sido em alguma tarde em que estava me sentindo emo… mas na real também não acho que deveria estar na dela… não deveria estar na de ninguém…

Pra terminar, vamos ver um plot usando a técnica T-SNE, que basicamente é um método de diminuir dimensionalidade pra dar pra plotar em duas dimensões e ver alguma coisa! Aqui peguei a saída da camada do meio, que tem 5 dimensões e reduzi para duas, plotei e usei as labels do dataset para pintar as bolinhas. Vamos ver o que saiu:

t-SNE plot dos dados. Em azul as musicas da minha namorada, em laranja as minhas

Olha que legal, de certa maneira, o algoritmo conseguiu separar as músicas. Lembrando que esse foi um conjunto de dados super pequeno e subjetivo, já que gosto musical é bem difícil de explicar!

Conclusão

Bom, espero que tenham entendido por alto como funciona uma rede neural simples e como dá pra criar algo divertido com modelos simples e um pouco de criatividade! O código esta disponível na minha conta do GitHub. Tem 3 arquivos:

  • Credentials, onde vc precisa colocar os dados do API do spotify e das contas que vc quer pegar informações
  • get_spotify_data, que vai de fato acessar o spotify e pegar as informações
  • spotify_model, aqui sim vamos treinar e salvar um modelo

Configurar acessos e APIs pode ser um pouco chatinho, mas é bastante útil e um conhecimento que vai abrir portas para acessar diversas bases de dados interessantes, então vale a pena gastar um tempinho entendendo como configurar e usar. Boa sorte, se curtiu clica aí no like, segue, compartilha, comenta, essas paradas de redes sociais! Prometo tentar postar com mais freqüência!

--

--

Paulo Eduardo Sampaio

Cientista de dados, engenheiro, músico de fim de semana e fã dos piores programas da TV aberta