Desenvolvendo uma aplicação de Recomendação do Spotify

Desenvolvendo uma aplicação de Recomendação do Spotify

O que está sendo feito

Resolvi escrever esse texto para explicar melhor como que foi construída a aplicação web que avalia qual música das 50 mais tocadas no Spotify se parece com uma música que seja testada pelo usuário ().

Esse texto está bastante resumido, a ideia é apenas comentar como foi construído. Disponibilizei o código apenas da parte que calcula a recomendação. Abaixo o resultado final.

Em linhas gerais tentarei explicar tudo em 4 partes:

  • Descrição das ferramentas na aplicação
  • Como funciona a API do Spotify
  • Como é calculado a semelhança entre as músicas
  • O cálculo na práticaImage for post

Descrição das ferramentas na aplicação

Eu resolvi fazer essa aplicação utilizando o back-end em Python e o front-end utilizando React. Ambas ferramentas me dão um certo conforto em trabalhar.

O back-end em Python foi uma escolha quase que essencial, pois pude utilizar várias bibliotecas muito boas para quem é cientista de dados, como Pandas, Numpy, Scikitlearn.

Em parceria com essas bibliotecas, que foram responsáveis pelos cálculos e estimativas, utilizei o framework Django, que ficou responsável pela criação dos end-points.

Neste caso, o back-end havia apenas três end-points:

  1. Requisitar a música que será comparada,
  2. Requisitar as 50 músicas mais tocadas atualmente no Spotify,
  3. Realizar os cálculos e devolver os resultados.

Não utilizei nada de banco de dados nesta aplicação, por não tinha interesse em armazenar nenhuma informação, ao menos até este momento.

Como funciona a API do Spotify

O Spotify disponibiliza um serviço no qual diversas métricas do seu conteúdo são disponibilizadas.

Com um cadastro e em posse de suas credenciais você tem acesso. Como suporte utilizei a biblioteca Spotify do Python.

Quando coletei dados de uma música, diversas informações são retornadas, inclusive características das músicas hospedadas neste aplicativo.

Apenas baseado nas métricas por eles geradas, consiste essa aplicação.

Utilizei uma transformação, de modo a ficar entre zero e um, isso facilita em aspecto de interpretação e cálculos.

Como é calculado a semelhança entre as músicas

Para buscar quais músicas parecem com a requisitada pelo usuário fiz uso de um conceito bastante simples que foi nada mais do que a distância euclidiana entre pontos.

De uma forma sucinta explicarei, com um caso de apenas três variáveis, como seria este cálculo.

Quem já tiver clareza deste conceito pode seguir a próxima seção.

Primeiramente, eu partir de duas variáveis muito comuns: Peso e Altura.

De forma muito fácil gerei um conjunto de dados com 500 observações.

Assumindo que peso e altura são grandezas que aderem a uma distribuição normal multivariada, com médias 60kg e 1.66m, respectivamente, e uma matriz de covariância:

Image for post

import pandas as pd
import numpy as np
import warningsfrom scipy.stats import multivariate_normal

df = pd.DataFrame(np.random.multivariate_normal([60, 1.66], [[100, 0.8], [0.8, 0.0025]],500))

df.columns = ["Peso","Altura"]

df.head(5)

Image for post

Daí criei duas variáveis a mais: IMC e Estado. A primeira, é referente a um indicador que pode ser utilizado na avaliação nutricional.

A segunda é apenas uma categorização da primeira, subdividindo-a em: Desnutrido, Peso normal e Sobrepeso.

df['IMC'] = df['Peso']/(df['Altura']**2)

df['Estado'] = pd.cut(df['IMC'], [0,18.5,25,np.inf], right=False, labels= ["Desnutrido","Peso normal","Sobrepeso"])df = df.drop(["IMC"],axis=1)

Com um gráfico de pontos, pode-se ver claramente que os grupos se separam de forma bem evidente.

import seaborn as sns; sns.set()
import matplotlib.pyplot as plt

sns.scatterplot(x="Peso", y="Altura",  hue="Estado", data=df)

Image for post

Desta forma vamos supor um novo indivíduo com 1.60m e 68kg. Após transformar os dados de forma que todas as variáveis fiquem entre zero e um, calculei a distância euclidiana deste novo indivíduo em comparação com todos os demais gerados anteriormente.

E simplesmente coletei o que mais se aproxima dele. Como todas as variáveis estão apenas variando entre zero e um, a distância máxima possível é raiz de 2.

Dividindo esta distância por raiz de dois temos uma distância entre zero e um. Utilizei este indicador para avaliar similaridade. Quanto mais próximo de zero mais similar são.

from sklearn.preprocessing import MinMaxScaler

scala = MinMaxScaler()

scala.fit(df.drop(['Estado'],axis=1))
df_escalonado = pd.DataFrame(scala.transform(df.drop(['Estado'],axis=1)))
df_escalonado.columns = df.drop(['Estado'],axis=1).columns

df_escalonado.head(5)

Image for post

sujeito = {
    "Peso":[68],
    "Altura":[1.60]
}

sujeito = pd.DataFrame(sujeito)minimos = {}
largura = {}

for id,nome in enumerate(df_escalonado.columns):
  minimos[nome] = scala.data_min_[id]
  largura[nome] = scala.data_range_[id]for n in sujeito.columns:
  sujeito[n] = (sujeito[n] - minimos[n])/largura[n]dists

O cálculo na prática

Primeiramente um objeto foi criado com funções úteis para realizar todo o processo.

Em seguida iniciamos o objeto Funcoes. Nesta etapa é feita a requisição de acesso a API. Aqui ficam incluídas as credenciais.

Utilizando o id da música escolhida, tive acesso aos dados que queria. Exclui as variáveis que não me interessava e transformei as únicas duas variáveis que não possuíam domínio entre zero e um.

O mesmo processo foi realizado para requisitar as informações das músicas mais tocadas. Para este caso, o método em questão, primeiramente coleta os id de todas as músicas que pertencem as mais tocadas e em seguida coleta as informações dos áudios em questão. Realizei a mesma transformação feita com a música solicitada na etapa anterior.

De agora em diante fica fácil. Apenas calculei as distâncias euclidianas e verifiquei qual a música do top 50 que mais se aproxima da solicitada.

import spotipy
import spotipy.util as util

from spotipy.oauth2 import SpotifyClientCredentials



class Funcoes:

    def __init__(self):
        self.client_id = 'client_id'
        self.client_secret = 'client_secret'
        self.client_credentials_manager = SpotifyClientCredentials(client_id=self.client_id, client_secret=self.client_secret)
        self.sp = spotipy.Spotify(client_credentials_manager=self.client_credentials_manager)
        
    def get_musica(self,music_id):
        sp = self.sp
        saida = {
            'features':sp.audio_features(music_id),
            'track':sp.track(music_id)
        }
        return(saida)

    

    def get_playlist_audio_features(self,username, playlist_id):
        sp = self.sp
        offset = 0
        songs = []
        items = []
        ids = []
        while True:
            content = sp.user_playlist_tracks(username, playlist_id, fields=None, limit=100, offset=offset, market=None)
            songs += content['items']
            if content['next'] is not None:
                offset += 100
            else:
                break

        for i in songs:
            ids.append(i['track']['id'])

        index = 0
        audio_features = []
        while index < len(ids):
            try:
                audio_features += sp.audio_features(ids[index:index + 50])
                index += 50
            except:
                pass
        
        top_null = []
        for i in audio_features:
            if not i is None:
                top_null.append(i)

        return (top_null)funcoes = Funcoes()

musicas = funcoes.get_musica(music_id = "2XU0oxnq2qxCpomAAuJY8K")
musicas = pd.DataFrame(musicas['features'])

links_musica = musicas['uri']

musicas = musicas.drop(['time_signature','key','mode','type','uri','id','track_href','analysis_url','duration_ms'],axis=1)

musicas['loudness'] = (musicas['loudness']+60)/(60)
musicas['tempo'] = (musicas['tempo'] - 25)/200


musicas

Image for post

top = funcoes.get_playlist_audio_features('renan_bispo',"37i9dQZEVXbMDoHDwVN2tF")top = pd.DataFrame(top)

top.head(5)

Image for post

top = top.drop(['time_signature','key','mode','type','uri','id','track_href','analysis_url','duration_ms'],axis=1)
nomes = top.columns

top['loudness'] = (top['loudness'] + 60)/(60)
top['tempo'] = (top['tempo'] - 25)/200

top.head(5)for id,col in enumerate(top.columns):
    top.rename(columns={col:nomes[id]}, inplace=True)minimos = {}
maximos = {}
largura = {}

saida = []
for id,nome in enumerate(top.columns):
    if nome == "loudness":
        minimos[nome] = -60
        maximos[nome] = 0
        largura[nome] = 60

    elif nome == "tempo":
        minimos[nome] = 25
        maximos[nome] = 225
        largura[nome] = 200
        
    else:
        minimos[nome] = 0
        maximos[nome] = 1
        largura[nome] = 1dists = []
for i in range(0,np.shape(top)[0]):
    dists.append(np.sqrt(np.sum((top.iloc[i,:].values - musicas.values)**2)))

dists = dists/np.sqrt(2)posicao = np.where(dists == np.amin(dists))[0][0]


parecida = top.iloc[posicao,:]np.where(dists == np.amin(dists))dist = np.absolute(top.iloc[posicao,:].values - musicas.values)

distcaracteristica = nomes[np.where(dist == np.amin(dist))[1][0]]

caracteristicadist_parecidas = 1 - dists[posicao]

saida = {
    "parecida":parecida,
    "dist_parecidas": dist_parecidas
}

saida

Como resumir esta aplicação

O objetivo aqui foi de tentar ilustrar o uso de técnicas simples para se propor soluções de problemas reais. Mostrar também uma aplicação de forma simples de uso também é importante, pois não limita-se a um único relatório e mostra como isso pode ser encorporado em produtos digitais.

Gostou desse artigo? Tem alguma sugestão? Deixe um comentário.

 

Sobre o Autor

Renan Bispo
Renan Bispo

Graduado em Ciência e Tecnologia pela Universidade Federal da Bahia (2014). Graduação (Ultimo Semestre) em Estatística pela Universidade Federal da Bahia (UFBA). Ótima habilidade avançada no software R e Python. Desenvolvimento web utilizando Django (para geração de visualizações de dados). Participação na construção dos conteúdos da Flai - Inteligência artificial.Pesquisa na área de Machine learning e métodos não paramétricos na análise de séries temporais. Experiência na área de Saúde Coletiva, atuando, principalmente, na pesquisa da Zika, Chikungunya, Epidemiologia e Dengue, dentro do grupo de pesquisa ”Estudo clínico e epidemiológico de infecções por arbovírus em Salvador, Brasil” (ISC - UFBA). Experiência junto a ABG consultoria desenvolvendo análises estatísticas. Conhecimento em estatística espacial com o uso do R. Conhecimento básico em banco de dados Postgre e MongoDB.

0 Comentários

Deixe uma resposta

O seu endereço de e-mail não será publicado. Campos obrigatórios são marcados com *