Como usar Random Forest, SVM e outros algoritmos de Shallow Machine Learning na GPU? (NVIDIA RAPIDS) #1

Rafael Faria de Azevedo
10 min readJan 13, 2023

--

Trabalho com aprendizagem de máquina (Machine Learning (ML)) desde 2018. Um problema que tive neste ano me fez escrever este post. Nesta época, não era possível usar a placa gráfica (GPU) para treinar modelos de ML (também conhecidos como Shallow Machine Learning). Até aquele momento, só era possível usar GPU para treinar modelos de Deep Learning (DL).

A questão é que naquela época e mesmo atualmente, há problemas cujos dados negociais (realidade da indústria) não possuem os requisitos técnicos ou legais (IA Explicável [Explainable AI — XAI]) para serem usados no treinamento de um algoritmo de DL. Porém, esses dados possuem as características para serem treinados em algoritmos de ML. Há algum tempo descobri uma maneira fácil e rápida de fazer o que queria e precisava, treinar diferentes algoritmos de ML usando GPU. E qual o ganho disso? O tempo de treinamento de alguns modelos diminuiu drasticamente. No final desta matéria, há um exemplo em que o treinamento de um modelo de SVM (Support Vector Machines) usando GPU foi mais de 300 vezes mais rápido que usando a CPU. É esta descoberta que quero compartilhar e divulgar.

Foto de árvore e rochas!
Uma foto da natureza para diminuir a tensão do tema! 😎😎😎

Apesar do assunto não ser novo na comunidade de inteligência artificial de língua inglesa, fiz buscas no Google pelo assunto sendo tratado em português e não encontrei nada (isso me surpreendeu). E qual é esse assunto? Na verdade é um framework ou API (tu decide como chamar) cujo nome é RAPIDS, desenvolvido pela NVIDIA. Para incentivar as pessoas a conhecerem o RAPIDS, resolvi escrever posts sobre o assunto, basicamente fazendo benchmark com alguns de seus algoritmos concorrentes que rodam em CPU. Afinal, o que seria de mim sem o conhecimento de computação e inteligência artificial compartilhado na Internet 🙏🏻. Resolvi começar a pagar minha conta 😁!

O RAPIDS possui vários componentes, para treinamento de modelos, para análise de dados, para geração de gráficos entre outros. Em relação ao treinamento de modelos, existe o CUML (provavelmente é um acrônimo para CUDA Machine Learning), que equivale ao consagrado scikit-learn (sklearn). Vou apresentar e comentar o código fonte Python que usei para fazer as comparações, vou explicar os experimentos e no final apresentar uma tabela com os cenários testados.

A título de curiosidade, CUDA (Compute Unified Device Architecture) é o nome da tecnologia que permitiu treinar modelos de Inteligência Artificial(IA) em placas de vídeo da NVIDIA.

Nota: Por favor, se encontrou qualquer coisa errada ou inadequada, me avise! Para que a contribuição com a comunidade possa ser a melhor possível!

Tópicos deste post

1. Funções
2. Dataset 1
3. RandomForestClassifier
4. LinearSVC
5. Dataset 2
6. SVC
7. Decisões
8. Tabela com os resultados
9. Teaser! Quais algoritmos de classificação, regressão e agrupamento existem no CUML?
10. Como instalar o RAPIDS?
11. Notebook e log (GitHub)
12. Referências
13. Contato

Inicio mostrando a configuração da máquina e das bibliotecas utilizadas.

Driver version: 470.161.03
Modelo da GPU: 1 x NVIDIA GeForce RTX 2060 (laptop)
Memória total da GPU: 6 GB
CUDA version: 11.4
Python version: 3.9.15
Sistema operacional: Ubuntu 18.04.6 LTS (Bionic Beaver)
Memória RAM total disponível: 16 GB
Padrão da memória RAM: DDR4
Tipo de storage: NVMe
Modelo do processador: Intel(R) Core(TM) i7-9750H CPU @ 2.60GHz
Total de núcleos virtuais do processador: 12
IDE utilizada: VSCode + Anaconda
import time
import datetime
import psutil
from cuml.model_selection import train_test_split as tts_cuml
from sklearn.metrics import accuracy_score as acc_sk
from cuml.metrics import accuracy_score as acc_cuml
import cuml
import cupy
import sklearn
from cupy import asnumpy
from sklearn.ensemble import RandomForestClassifier
import cudf
from loguru import logger
import platform

logger.add("medium1-cuml-loguru.log")
logger.info(f"psutil.__version__: {psutil.__version__}")
logger.info(f"cuml.__version__: {cuml.__version__}")
logger.info(f"cudf.__version__: {cudf.__version__}")
logger.info(f"cupy.__version__: {cupy.__version__}")
logger.info(f"sklearn.__version__: {sklearn.__version__}")

from pynvml import *
nvmlInit()
psutil.__version__: 5.9.4
cuml.__version__: 22.12.00
cudf.__version__: 22.12.01
cupy.__version__: 11.4.0
sklearn.__version__: 1.2.0

1. Funções

Para a tarefa, foram criadas algumas funções. As duas primeiras são utilizadas na contagem do tempo de processamento de cada experimento.

def time_init(inductor):
logger.info(f"INDUTOR: {inductor}")
time_start = time.time()
datetime_start = datetime.datetime.now()
return datetime_start, time_start
def time_print(datetime_start, time_start):
time_end = time.time()
datetime_end = datetime.datetime.now()
elapsed_datetime = (datetime_end - datetime_start)
elapsed_time = (time_end - time_start)
logger.warning(f"Tempo de execução (datetime): {str(elapsed_datetime)} segundos")
logger.warning(f"Tempo de execução (time): {str(elapsed_time)} segundos")
return elapsed_time

A próxima função serve para obter as métricas de cada experimento. Utilizei a medição da acurácia através do CUML e do SKLearn.

def metrics_cuml_sklearn(framework, inductor, y_test_cuml, predictions_cuml, y_test_sk, predictions_sk):
cu_score = acc_cuml(y_test_cuml, predictions_cuml)
sk_score = acc_sk(y_test_sk, predictions_sk)
logger.debug(f"{framework} - cuml accuracy: {cu_score}")
logger.debug(f"{framework} - sklearn accuracy: {sk_score}")
logger.trace(f"PARAMETROS: {str(inductor.get_params())}")

A função print_performance é utilizada para calcular a diferença de tempo entre dois experimentos.

def print_performance(rapids, sklearn):
exp1 = 'SKLEARN' if sklearn < rapids else 'CUML'
exp2 = 'SKLEARN' if sklearn > rapids else 'CUML'
exp3 = (sklearn/rapids if sklearn > rapids else rapids/sklearn)
exp4 = exp3 >= 2
exp5 = ('vezes' if exp4 else 'vez')
logger.success(f"RESULTADO: {exp1} foi mais rápido que o {exp2} {exp3} {exp5}")

A última função serve para carregar os diferentes datasets utilizados.

def load_prepare_dataset(filename):
'''
Carregando os dados usando o CUDF.
'''
logger.info(f"filename: {filename}")
cu_df = cudf.read_csv(filename)
logger.info(f"cu_df.shape: {cu_df.shape}")
y = cu_df['class']
X = cu_df.drop(columns=['class'])
logger.info(f"X.shape: {X.shape}")
logger.info(f"y.shape: {y.shape}")
return tts_cuml( X, y, random_state = 3, test_size=0.3)

2. Dataset 1

Esse é o dataset utilizado na maioria dos experimentos.

filename = "medium1_dt1.csv"
X_train, X_test, y_train, y_test = load_prepare_dataset(filename)
OUTPUT:
filename: medium1_dt1.csv
cu_df.shape: (250000, 102)
X.shape: (250000, 101)
y.shape: (250000,)

Olhando para a função load_prepare_dataset e sua saída acima, vemos que o dataset tem 250.000 amostras e 102 features (colunas). A última coluna é a classe. A proposta foi uma tarefa multiclasse com 5 variáveis alvo (targets/classes).

Para fazer o benchmark escolhi os algoritmos RandomForestClassifier, LinearSVC e SVC. Os detalhes do código de cada um será apresentado na sequência. Cada experimento usou os mesmos parâmetros. Foram todos realizados em janeiro/2023.

3. RandomForestClassifier

params_rf = {'max_features':'sqrt', 'max_depth':16, 'random_state':7, 'verbose':0}
logger.info(f"params_rf: {params_rf}")

Começamos com a versão CUML e sua respectiva saída (output).

from cuml.ensemble import RandomForestClassifier as cuRFC
datetime_start, time_start = time_init(cuRFC)
cuml_model = cuRFC(**params_rf)
cuml_model.fit(X_train,y_train)
predictions = cuml_model.predict (X_test)
metrics_cuml_sklearn('CUML', cuml_model, y_test, predictions, asnumpy(y_test), asnumpy(predictions))
rap = time_print(datetime_start, time_start)
OUTPUT:
INDUTOR: <class 'cuml.ensemble.randomforestclassifier.RandomForestClassifier'>
CUML - cuml accuracy: 0.7810800075531006
CUML - sklearn accuracy: 0.78108
Tempo de execução (datetime): 0:00:03.628787 segundos
Tempo de execução (time): 3.6287872791290283 segundos

Agora a versão do SKLearn usando 11 dos 12 núcleos do processador disponíveis na máquina (n_jobs=11). Na sequência temos a saída (output).

from sklearn.ensemble import RandomForestClassifier as skRF_n_jobs
datetime_start, time_start = time_init(skRF_n_jobs)
clf = skRF_n_jobs(**params_rf, n_jobs=11)
clf.fit(X_train.to_numpy(), y_train.to_numpy())
predictions = clf.predict (X_test.to_numpy())
metrics_cuml_sklearn('SKLEARN', clf, y_test.to_numpy(), predictions, y_test.to_numpy(), predictions)
skl = time_print(datetime_start, time_start)
print_performance(rap, skl)
OUTPUT:
INDUTOR: <class 'sklearn.ensemble._forest.RandomForestClassifier'>
SKLEARN - cuml accuracy: 0.8966266512870789
SKLEARN - sklearn accuracy: 0.8966266666666667
Tempo de execução (datetime): 0:00:33.472368 segundos
Tempo de execução (time): 33.47236895561218 segundos

Eis o primeiro resultado da comparação CUML e SKLearn

RESULTADO: CUML foi mais rápido que o SKLEARN 9.22411990036686 vezes

No próximo experimento temos a versão do SKLearn usando apenas 1 dos 12 núcleos do processador disponíveis na máquina (n_jobs=1) (configuração padrão do algoritmo). Segue também sua saída.

from sklearn.ensemble import RandomForestClassifier as skRF_single_job
datetime_start, time_start = time_init(skRF_single_job)
clf = skRF_single_job(**params_rf)
clf.fit(X_train.to_numpy(), y_train.to_numpy())
predictions = clf.predict (X_test.to_numpy())
metrics_cuml_sklearn('SKLEARN', clf, y_test.to_numpy(), predictions, y_test.to_numpy(), predictions)
skl = time_print(datetime_start, time_start)
print_performance(rap, skl)
OUTPUT:
INDUTOR: <class 'sklearn.ensemble._forest.RandomForestClassifier'>
SKLEARN - cuml accuracy: 0.8966266512870789
SKLEARN - sklearn accuracy: 0.8966266666666667
Tempo de execução (datetime): 0:03:00.267529 segundos
Tempo de execução (time): 180.2675290107727 segundos

Eis o segundo resultado da comparação CUML e SKLearn

RESULTADO: CUML foi mais rápido que o SKLEARN 49.677072571208974 vezes

4. LinearSVC (uma versão do SVM)

Esses são os parâmetros utilizados nos 2 experimentos com LinearSVC.

params_linear_svc = {'loss':'squared_hinge', 'penalty':'l2', 'C':1, 'verbose':0}
logger.info(f"params_linear_svc: {params_linear_svc}")

Abaixo temos o experimento usando o CUML e sua saída.

from cuml.svm import LinearSVC as cuLinearSVC
datetime_start, time_start = time_init(cuLinearSVC)
cu_linear_SVC = cuLinearSVC(**params_linear_svc)
cu_linear_SVC.fit(X_train, y_train)
predictions = cu_linear_SVC.predict (X_test)
metrics_cuml_sklearn('CUML', cu_linear_SVC , y_test, predictions, y_test.to_numpy(), predictions.to_numpy())
rap = time_print(datetime_start, time_start)
OUTPUT:
INDUTOR: <class 'cuml.svm.linear_svc.LinearSVC'>
CUML - cuml accuracy: 0.873520016670227
CUML - sklearn accuracy: 0.87352
Tempo de execução (datetime): 0:00:00.737249 segundos
Tempo de execução (time): 0.7372500896453857 segundos

Nas próximas duas células são apresentado o código na versão SKLearn e seu output.

from sklearn.svm import LinearSVC as skLinearSVC
datetime_start, time_start = time_init(skLinearSVC)
sk_linear_SVC = skLinearSVC(**params_linear_svc)
sk_linear_SVC.fit(X_train.to_numpy(), y_train.to_numpy())
predictions = sk_linear_SVC.predict (X_test.to_numpy())
metrics_cuml_sklearn('SKLEARN', sk_linear_SVC, y_test.to_numpy(), predictions, y_test.to_numpy(), predictions)
skl = time_print(datetime_start, time_start)
print_performance(rap, skl)
OUTPUT:
INDUTOR: <class 'sklearn.svm._classes.LinearSVC'>
SKLEARN - cuml accuracy: 0.8750799894332886
SKLEARN - sklearn accuracy: 0.87508
Tempo de execução (datetime): 0:04:05.759948 segundos
Tempo de execução (time): 245.75994777679443 segundos

Eis o terceiro resultado da comparação CUML e SKLearn, é o mais surpreendente!

RESULTADO: CUML foi mais rápido que o SKLEARN 333.3467858851044 vezes

5. Dataset 2

Para os experimentos com o algoritmo SVC, foi utilizado um dataset menor, devido ao tempo de processamento da versão SKLearn.

filename = "medium1_dt_100k.csv"
X_train, X_test, y_train, y_test = load_prepare_dataset(filename)
filename: medium1_dt_100k.csv
cu_df.shape: (100000, 102)
X.shape: (100000, 101)
y.shape: (100000,)

Ele possui as mesmas características do dataset 1, exceto por possuir apenas 100.000 amostras.

6. SVC (outra versão do SVM)

Os parâmetros usados nos experimentos com o SVC são:

params_svc = {'kernel':'poly', 'degree':2, 'gamma':'scale', 'C':1, 'random_state':7, 'verbose':0}
logger.info(f"params_svc: {params_svc}")

O código e a saída do SVC do CUML estão abaixo.

from cuml.svm import SVC as cumlSVC
datetime_start, time_start = time_init(cumlSVC)
cuml_model = cumlSVC(**params_svc)
cuml_model.fit(X_train, y_train)
predictions = cuml_model.predict (X_test)
metrics_cuml_sklearn('CUML', cuml_model, y_test, predictions, asnumpy(y_test), asnumpy(predictions))
rap = time_print(datetime_start, time_start)
OUTPUT:
INDUTOR: <class 'cuml.svm.svc.SVC'>
CUML - cuml accuracy: 0.6628999710083008
CUML - sklearn accuracy: 0.6629
Tempo de execução (datetime): 0:00:57.697369 segundos
Tempo de execução (time): 57.69736957550049 segundos

E para podermos fazer a comparação, temos o código do SKLearn e sua saída.

from sklearn.svm import SVC as skSVC
datetime_start, time_start = time_init(skSVC)
clf = skSVC(**params_svc)
clf.fit(X_train.to_numpy(), y_train.to_numpy())
predictions = clf.predict (X_test.to_numpy())
metrics_cuml_sklearn('SKLEARN', clf, y_test.to_numpy(), predictions, y_test.to_numpy(), predictions)
skl = time_print(datetime_start, time_start)
print_performance(rap, skl)
OUTPUT:
INDUTOR: <class 'sklearn.svm._classes.SVC'>
SKLEARN - cuml accuracy: 0.6615666747093201
SKLEARN - sklearn accuracy: 0.6615666666666666
Tempo de execução (datetime): 0:17:06.957642 segundos
Tempo de execução (time): 1026.957641839981 segundos

Eis o quarto e último resultado da comparação CUML e SKLearn! Mais uma vez o CUML venceu!

RESULTADO: CUML foi mais rápido que o SKLEARN 17.799037449985395 vezes

7. Decisões

Talvez alguém esteja se perguntando por que escolhi o Random Forest e diferentes versões do SVM para fazer este benchmark. O primeiro foi escolhido por oferencer a possibilidade de ser executado com 1 ou mais núcleos do processador. Já o SVM foi o motivo do meu problema em 2018. Até hoje, não existem versões para GPU dele no SKLearn. Alguns podem dizer que foi trauma, kkk! Fiquei muito feliz em saber que finalmente existe opção para GPU.

8. Tabela com os resultados

A tabela com todos os experimentos é apresentada abaixo.

A primeira coisa que me chamou a atenção é a diferença na acurácia entre o Random Forest do CUML e do SKLearn. Olhando a documentação de ambos, percebi que existe diferença na abordagem dos parâmetros de cada implementação.

9. Teaser! Quais algoritmos de classificação, regressão e agrupamento existem no CUML?

O mundo RAPIDS é muito maior que o que apresentei aqui. Existem várias opções de algoritmos para classificação, regressão e clusterização. E muito mais!

Algoritmos de regressão e classificação existentes no CUML quando este post foi feito.
Algoritmos de agrupamento existentes no CUML quando este post foi feito.

Digo com certeza e empolgação, vale a pena vasculhar a documentação do CUML (e do RAPIDS). Há muitas ferramentas úteis para quem trabalha ou estuda ciência de dados. Veja as referências.

Inclusive (spoiler), farei outro post sobre o assunto em breve. Desta vez o tema será CUDF, o concorrente do Pandas para GPU. Não perca!

Atualização: Este é o post sobre o CUDF (aqui).

10. Como instalar o RAPIDS?

1. Execute o comando “nvidia-smi”, para saber qual o CUDA Version (pode ser CUDA 11.2, CUDA 11.4 e CUDA 11.5) da sua máquina com placa de vídeo dedicada da NVIDIA;

2. Execute o comando “python --version”, para saber qual a versão do Python instalada na máquina (pode ser Python 3.8 ou 3.9);

3. Preencha os parâmetros em https://rapids.ai/start.html (STEP 3: INSTALL RAPIDS), no final você terá o comando para montar um ambiente Conda com o RAPIDS;

4. Divirta-se!

11. Notebook e log (GitHub)

O notebook e log das execuções estão disponíveis no meu GitHub (aqui).

Não pude colocar os datasets, pois os arquivos são maiores que o Git aceita. O arquivo medium1_dt1.csv tem 313,4 MB e o arquivo medium1_dt_100k.csv tem 125,3 MB.

Porém, isso não prejudica o post, pois o objetivo dele é que você teste o CUML com o dataset que quiser, ou seu hardware aguentar 😁.

12. Referências

13. Contato

https://www.linkedin.com/in/rafael-faria-de-azevedo-msc-a9855246/

--

--

Rafael Faria de Azevedo

Estudo machine learning desde 2016. Trabalho com o tema desde 2018. Gosto de correr, tocar baixo, estudar inteligência artificial e filosofia.