Aula 8

Aprendizado não-supervisionado

Introdução

Aprendizado de máquina muitas vezes é utilizado como sinônimo de aprendizado supervisionado, área na qual tarefas partem de labels que queremos predizer partir de uma função \(f(\cdot)\) que será treinada pela máquina. Esse tipo de aprendizado, no entanto, deixa de lado uma ampla gama de tarefas e de aplicações nas quais não temos labels e, mesmo assim, queremos descobrir padrões comuns nos dados: as de aprendizado não-supervisionado.

Nesta aula, cobriremos o básico deste tipo de aprendizado. Em particular, focaremos em dois tipos de aplicação úteis que nos ajudarão a encontrar padrões em bases nas quais não temos um \(Y\): redução de dimensionalidade, como PCA, que pode ser utilizado em etapas de pré-processamento de tarefas supervisionadas; e clustering, bastante utilizado para a classificação de textos ou segmentação de usuários.

Aprendizado não-supervisionado

A título de intuição, a principal característica de aprendizado não-supervisionado é a de não termos um target, ou \(Y\), a ser predito a partir de uma matriz ou conjunto de features – pela mesma razão, toda a noção de validação, que passa pelo uso de amostras de treino e de teste e de métricas de validação, não necessariamente se aplica.1 Ao contrário, o que queremos aprender é a estrutura dos dados, isto é, como eles são organizados e quais são suas propriedades. Um exemplo trivial: saber a média de uma variável contínua é uma forma de saber algo sobre a propriedade dessa amostra.

  • 1 Em algumas tarefas, como a detecção de anomalias, é sempre interessante separar parte dos dados para inspeção humana, mas isso é algo que não cobriremos neste curso.

  • Embora existam diferentes tipos de problema em aprendizado não-supervisionado, os três mais comuns são os seguintes:

    • redução de dimensionalidade, cujo objetivo é descobrir se é possível resumir a variação de um número de features em um número menor delas (pense, por exemplo, em um problema em que temos milhares de features e usá-las para treinar um modelo pode não ser viável computacionalmente)

    • clustering, cujo objetivo é agrupar, geralmente de forma exclusiva, ocorrências ou observações similares em função da distância/proximidade entre elas (e.g., clientes com o mesmo perfil de consumo, ou textos que tratam de determinados temas)

    • detecção de anomalias e outliers, cujo objetivo é apontar ocorrências ou observações que destoam de determinados padrões em uma amostra

    Dos três, o primeiro frequentemente é utilizado em conjunto com métodos supervisionados como forma de pré-processar features, especialmente quando há muitas delas; junto com o primeiro, o segundo é usado principalmente em análises exploratórias (James et al. 2013).

    Redução de dimensionalidade

    Redução de dimensionalidade significa fazer o que seu nome sugere: obter uma base de dados com menor número de dimensões do que aquela utilizada inicialmente. As vantagens de se fazer isso são várias: é possível visualizar melhor relações no dados, além de ser acabarmos com uma base menor, mais fácil de ser manipulada. Quando a redução de dimensionalidade funciona adequadamente, além disso, conseguimos reter variação importante nos dados, o que significa que seu uso não necessariamente traz perda de informação.

    Das técnicas de redução, uma das mais populares é o Principal component analysis (PCA) (análise de componentes principais) que, na prática, projeta variáveis de um banco com muitas features em um novo sistema de coordenadas, reduzindo assim o número de dimensões (novas features). Em outras palavras, cada nova dimensão encontrada pelo PCA é uma combinação linear das dimensões originais nos dados – ou, para usar a notação de James et al. (2013):

    \(Z_{1} = \delta_{1} X_{1} + \delta_{2} X_{2} + · · · + \delta_{p} X_{p}\)

    onde \(Z_{1}\) é a primeira dimensão que resulta da combinação de um conjunto \(X_{1}, X_{2}, ...X_{p}\) de features e onde cada \(\delta_{p}\) são equivalentes de coeficientes (loadings).2 Cada nova dimensão, finalmente, é ortogonal, não correlacionada às demais, o que significa que devem captar variações diferentes nos dados. Como dizem James et al. (2013):

  • 2 A ideia aqui é maximizar a variância captada pela dimensão ao mesmo tempo que a soma dos quadrados dos loadings não exceda 1, o que pode ser resolvido com álgebra linear.

  • When faced with a large set of correlated variables, principal components allow us to summarize this set with a smaller number of representative variables that collectively explain most of the variability in the original set (p. 498)

    Implementar PCA com nossos frameworks é algo simples. Os códigos abaixo fazem isso no nosso toy example de dados climáticos de São Bernardo do Campo:3

  • 3 Para quem usa R ou Python, note que o mlr3 ou o sklearn não estandardizam variáveis no PCA, o é que recomendado para evitar que as escalas das variáveis afetem os loadings.

  • # Carrega pacotes
    library(tidyverse)
    library(mlr3verse)
    
    # Carrega dados
    link <- "https://raw.githubusercontent.com/jacobwright32/Web_Scraper_AI_Core_Project/bb4865ae568e23ab8fadb6ea58cf117df2164ef3/web%20scraping/Cleaned%20Data/Brazil_Sao%20Bernardo%20Do%20Campo_Cleaned.csv"
    
    dados <- readr::read_csv(link) %>%
      select_if(is.numeric)
    
    # Define a task
    tsk <- as_task_regr(maximum_temprature ~ ., data = dados)
    
    # Exibe os dados originais
    tsk$data()
    
    # Cria uma pipeline com PCA
    gr <- po("scale") %>>%
      po("pca")
    
    # Treina a pipeline e exibe os dados transformados
    gr$train(tsk)
    gr$state
    from sklearn.decomposition import PCA
    from sklearn.preprocessing import StandardScaler
    import pandas as pd
    
    # Carrega dados e separa as amostras
    link = 'https://raw.githubusercontent.com/jacobwright32/Web_Scraper_AI_Core_Project/bb4865ae568e23ab8fadb6ea58cf117df2164ef3/web%20scraping/Cleaned%20Data/Brazil_Sao%20Bernardo%20Do%20Campo_Cleaned.csv'
    dados = pd.read_csv(link).select_dtypes(['number'])
    
    Y = dados['maximum_temprature']
    X = dados.loc[:, dados.columns != 'maximum_temprature']
    
    # Reduz dimensionalidade
    scaler = StandardScaler()
    X_scl = scaler.fit(X).transform(X)
    pca = PCA()
    pca.fit(X_scl)
    pca.transform(X_scl)

    Como dito, PCA e variantes são úteis principalmente em etapas exploratórias de análise, para plotar muitas features e, também, como etapa de pré-processamento. Vamos focar aqui neste último ponto e usar PCA em pipelines supervisionadas:

    # Carrega pacotes
    library(tidyverse)
    library(mlr3verse)
    
    # Carrega dados
    link <- "https://raw.githubusercontent.com/jacobwright32/Web_Scraper_AI_Core_Project/bb4865ae568e23ab8fadb6ea58cf117df2164ef3/web%20scraping/Cleaned%20Data/Brazil_Sao%20Bernardo%20Do%20Campo_Cleaned.csv"
    
    dados <- readr::read_csv(link) %>%
      select_if(is.numeric)
    
    # Define a task
    tsk <- as_task_regr(maximum_temprature ~ ., data = dados)
    
    
    # Cria uma pipeline com PCA e SVM
    gr <- po("scale") %>>%
      po("pca") %>>%
      po("learner", learner = lrn("regr.svm")) %>%
      as_learner()
    
    
    # Treina a pipeline
    design <- benchmark_grid(
      tasks = tsk,
      learners = list(gr),
      resamplings = rsmp("holdout", ratio = 0.7)
    )
    
    resultados <- benchmark(design)
    resultados$score(msr("regr.rmse"))
    from sklearn.model_selection import train_test_split
    from sklearn.preprocessing import StandardScaler
    from sklearn.pipeline import Pipeline
    from sklearn.svm import SVR
    from sklearn.metrics import mean_squared_error
    from sklearn.decomposition import PCA
    import pandas as pd
    
    # Carrega dados e separa as amostras
    link = 'https://raw.githubusercontent.com/jacobwright32/Web_Scraper_AI_Core_Project/bb4865ae568e23ab8fadb6ea58cf117df2164ef3/web%20scraping/Cleaned%20Data/Brazil_Sao%20Bernardo%20Do%20Campo_Cleaned.csv'
    dados = pd.read_csv(link).select_dtypes(['number'])
    
    Y = dados['maximum_temprature']
    X = dados.loc[:, dados.columns != 'maximum_temprature']
    
    X_treino, X_teste, Y_treino, Y_teste = train_test_split(X, Y)
    
    # Cria uma pipeline
    pipe = Pipeline([('scale', StandardScaler()), ('pca', PCA()), ('svr', SVR())])
    
    # Treina a pipeline
    pipe.fit(X_treino, Y_treino)
    mean_squared_error(Y_teste, pipe.predict(X_teste), squared=False)

    Uma escolha importante aqui é a de quantas dimensões reter – algo que, em análise exploratória, normalmente é decido lenvando-se em conta scree plot, métricas, considerações substantivas, entre outros. Em aprendizado de máquina, no entanto, o importante é o quanto o PCA contribui para o desempenho de uma pipeline, isto é, qual o seu impacto na redução/aumento de métricas de validação. Nesse sentido, o número de dimensões a reter deve ser pautada por considerações de desempenho.

    Outras técnicas

    PCA é produtivo para reduzir dimensionalidade de bancos com várias features contínuas, mas, às vezes, alternativas podem ser úteis em determinados contextos. Quando há a possibilidade de combinações não-lineares entre features, por exemplo, kernel PCA é uma ótima opção4, enquanto que, em matrizes esparsas (como em análise de bag of words), truncated SDV é mais indicado. Como em outras aplicações de aprendizado de máquina supervisionadas, a melhor forma de testar outras possibilidades é sempre investigar seus efeitos sobre o desempenho dos modelos ou pipelines.

  • 4 Pode ser implementador tanto no mlr3 quanto no sklearn com kernelpca e KernelPCA, respectivamente.

  • Clustering

    Outra tarefa comum não-supervisionada é a de agrupar observações de acordo com suas proximidades ou distâncias. Normalmente, isso significa descobrir grupos internamente homogêneos e externamente heterogêneos. Nesse sentido, a tarefa de clustering é a de identificar \(N\) grupos que, em conjunto, isolem de forma excludente observações próximas de outras mais distantes.

    Dentre as técnicas mais populares de clustering, três são particularmente úteis (James et al. 2013): K-Means, Hierachical clustering e dbscan. Na primeira, a ideia é simples: definimos \(K\) clusters que deverão ter a menor variância intra-cluster possível, isto é, grupos cujas observações sejam internamente o mais similar.5 Cluster hierárquico, ao contrário, não define um \(K\) inicial, mas sim organiza observações mais próximas de forma indutiva, o que resulta em uma árvore ou dendograma por meio da agregação progressiva de clusters similares. Por fim, dbscan usa densidade das áreas com mais observações para definir clusters, o que tende a captar melhor clusters com configurações arbitrárias.

  • 5 Na prática, a métrica de interesse em K-Means é a soma da distância euclidiana de cada observação de um cluster em relação a todas as demais observações, isto para cada feature e para cada cluster. Por conta disso, o problema de encontrar a melhor disposição de observações-cluster é difícil e envolve um algoritmo iterativo que depende de um sorteio inicial, seed, que pode afetar os resultados obtidos.

  • Podemos implementar os três algoritmos de clustering em R ou Python usando:

    # Carrega pacotes
    library(tidyverse)
    library(mlr3cluster)
    
    # Carrega dados
    link <- "https://raw.githubusercontent.com/jacobwright32/Web_Scraper_AI_Core_Project/bb4865ae568e23ab8fadb6ea58cf117df2164ef3/web%20scraping/Cleaned%20Data/Brazil_Sao%20Bernardo%20Do%20Campo_Cleaned.csv"
    
    dados <- readr::read_csv(link) %>%
      select_if(is.numeric)
    
    # Define a task
    tsk <- as_task_clust(dados)
    
    # Cluster K-means
    clust <- lrn("clust.kmeans", centers = 4)
    
    clust$train(tsk)
    dados$cluster <- clust$assignments
    
    # Cluster hierarquico
    clust <- lrn("clust.hclust")
    
    clust$train(tsk)
    dados$cluster <- clust$assignments
    
    # DBscan
    clust <- lrn("clust.dbscan", eps = 3)
    
    clust$train(tsk)
    dados$cluster <- clust$assignments
    from sklearn.cluster import AgglomerativeClustering
    from sklearn.cluster import DBSCAN
    from sklearn.cluster import KMeans
    import pandas as pd
    
    # Carrega dados e separa as amostras
    link = 'https://raw.githubusercontent.com/jacobwright32/Web_Scraper_AI_Core_Project/bb4865ae568e23ab8fadb6ea58cf117df2164ef3/web%20scraping/Cleaned%20Data/Brazil_Sao%20Bernardo%20Do%20Campo_Cleaned.csv'
    dados = pd.read_csv(link).select_dtypes(['number'])
    
    Y = dados['maximum_temprature']
    X = dados.loc[:, dados.columns != 'maximum_temprature']
    
    # Kmeans
    kmeans = KMeans(n_clusters=4, random_state=0, init='k-means++')
    kmeans.fit(X)
    dados = dados.assign(kmeans = kmeans.labels_)
    
    # Hierarquico
    hierarq = AgglomerativeClustering()
    hierarq.fit(X)
    dados = dados.assign(hierarq = hierarq.labels_)
    
    # DBscan
    dbscan = DBSCAN(eps=2.5)
    dbscan.fit(X)
    dados = dados.assign(dbscan = dbscan.labels_)

    Aprendizado semi-supervisionado

    Antes de passarmos para o próximo conteúdo, é importante destacar a existência de algoritmos semi-supervisionados, isto é, algoritmos que procuram descobrir a estrutura de uma amostra usando intervenção humana para auxiliar no processo. Dois exemplos interessantes:

    • seed-LDA, para modelos de tópicos a partir de bag of words que usam algumas palavras (seeds) para ancorar tópicos (disponível, com métodos diferentes, em R e em Python)

    • positive unlabeled learning, para problemas de classificação onde só sabemos que uma parte pequena das observações é positiva (\(1\)) (algumas implementações em Python e R)

    Referências

    James, Gareth, Daniela Witten, Trevor Hastie, e Robert Tibshirani. 2013. An introduction to statistical learning. Vol. 112. Springer.