Aprendizado de máquina em R: técnicas especializadas para análise preditiva

imagemOlá, habrozhiteli! A linguagem R oferece um conjunto poderoso de métodos de aprendizado de máquina que permitem realizar rapidamente análises não triviais de seus dados. O livro é um guia que ajudará a aplicar métodos de aprendizado de máquina para resolver problemas do dia a dia. Brett Lanz ensinará tudo o que você precisa para análise, previsão e visualização de dados. Aqui você encontrará informações sobre bibliotecas novas e aprimoradas, dicas sobre os aspectos éticos do aprendizado de máquina e questões de preconceito, além de treinamento aprofundado.

Neste livro - Fundamentos de aprendizado de máquina e recursos de treinamento em informática em exemplos. - Preparação de dados para uso em aprendizado de máquina por meio da linguagem R. - Classificação da significância dos resultados. - Previsão de eventos usando árvores de decisão, regras e vetores de referência. - Previsão de dados numéricos e avaliação de dados financeiros usando métodos de regressão. - Modelar processos complexos usando redes neurais é a base do aprendizado profundo. - Avaliação de modelos e melhoria de desempenho. - As mais recentes tecnologias para processamento de big data, em particular R 3.6, Spark, H2O e TensorFlow.

Para quem é o livro?


O livro é destinado a quem espera usar dados em uma área específica. Você já deve estar um pouco familiarizado com o aprendizado de máquina, mas nunca trabalhou com a linguagem R; ou, inversamente, você sabe um pouco sobre R, mas quase não sabe sobre aprendizado de máquina. De qualquer forma, este livro ajudará você a começar rapidamente. Seria útil atualizar um pouco os conceitos básicos de matemática e programação, mas nenhuma experiência anterior seria necessária. Tudo que você precisa é um desejo de aprender.

O que você lerá na publicação
1 « » , , , , .

2 « » R. , , .

3 « : » , : .

4 « : » , . , .

5 « : » , , . , .

6 « : » , . , , .

7 « “ ”: » , . , , .

8 « : » , , . - , , , .

9 « : k-» . -.

10 « » , .

11 « » , , . , .

12 « » : R. , , R.


Exemplo: modelando a resistência do concreto usando uma rede neural


No campo da engenharia civil, é extremamente importante ter estimativas precisas da eficácia dos materiais de construção. Essas avaliações são necessárias para desenvolver regras de segurança que regem o uso de materiais na construção de edifícios, pontes e estradas.

De particular interesse é a avaliação da resistência do concreto. O concreto é usado em praticamente qualquer construção, as características de desempenho do concreto são muito diferentes, pois consiste em um grande número de ingredientes que interagem em um complexo. Como resultado, é difícil dizer exatamente qual será a força do produto acabado. Um modelo que permita determinar com certeza a resistência do concreto, levando em consideração a composição dos materiais de partida, poderia fornecer um nível mais alto de segurança para os canteiros de obras.

Etapa 1. Coleta de Dados


Para esta análise, usaremos os dados concretos da resistência à compressão fornecidos por I-Cheng Yeh ao UCI Machine Learning Repository (http://archive.ics.uci.edu/ml). Como Ai-Cheng Ye usou com sucesso redes neurais para modelar esses dados, tentaremos reproduzir seu trabalho aplicando um modelo simples de rede neural em R.

A julgar pelo site, este conjunto de dados contém 1030 registros sobre diferentes marcas de concreto com oito características que descrevem os componentes usados ​​na composição da mistura de concreto. Acredita-se que essas características afetem a resistência à compressão final. Isso inclui: a quantidade (em quilogramas por metro cúbico) de cimento, água, vários aditivos, agregados grandes e pequenos, como pedras e areia trituradas, usados ​​no produto acabado, bem como o tempo de endurecimento (em dias).

Para executar este exemplo, faça o download do arquivo Concrete.csv e salve-o no diretório de trabalho do R.

Etapa 2. Pesquisa e preparação de dados


Como de costume, iniciamos a análise carregando os dados no objeto R usando a função read.csv () e certifique-se de que o resultado corresponda à estrutura esperada:

> concrete <- read.csv("concrete.csv")
> str(concrete)
'data.frame':        1030 obs. of 9 variables:
$ cement       : num 141 169 250 266 155 ...
$ slag            : num 212 42.2 0 114 183.4 ...
$ ash             : num 0 124.3 95.7 0 0 ...
$ water          : num 204 158 187 228 193 ...
$ superplastic : num 0 10.8 5.5 0 9.1 0 0 6.4 0 9 ...
$ coarseagg    : num 972 1081 957 932 1047 ...
$ fineagg        : num 748 796 861 670 697 ...
$ age             : int 28 14 28 28 28 90 7 56 28 28 ...
$ strength      : num 29.9 23.5 29.2 45.9 18.3 ...

Nove variáveis ​​no quadro de dados correspondem a oito características e um resultado esperado, mas ficou óbvio que há um problema. As redes neurais funcionam melhor quando os dados de entrada são redimensionados para um intervalo estreito centrado em torno de 0, e aqui vemos valores no intervalo de 0 a mais de 1000.

Normalmente, a solução para esse problema é dimensionar os dados usando a função de normalização ou padronização. Se a distribuição de dados corresponder a uma curva em forma de sino (distribuição normal, consulte o capítulo 2), poderá fazer sentido usar a padronização usando a função de escala interna (). Se a distribuição dos dados for quase uniforme ou muito diferente do normal, a normalização para o intervalo de 0 a 1. Pode ser mais adequada.Neste caso, usaremos a última opção.

No capítulo 3, criamos nossa própria função normalize ():

> normalize <- function(x) {
       return((x - min(x)) / (max(x) — min(x)))
}

Após a execução desse código, você pode aplicar a função normalize () a todas as colunas do quadro de dados selecionado usando a função lapply ():

> concrete_norm <- as.data.frame(lapply(concrete, normalize))

Para verificar se a normalização funcionou, é possível verificar se os valores mínimo e máximo do atributo de força são 0 e 1, respectivamente:

> summary(concrete_norm$strength)
       Min.     1st Qu.         Median     Mean      3rd Qu.      Max.
   0.0000     0.2664         0.4001  0.4172      0.5457   1.0000

Para comparação: os valores mínimo e máximo iniciais deste atributo foram 2,33 e 82,60, respectivamente:

> summary(concrete$strength)
     Min.       1st Qu.     Median       Mean      3rd Qu.       Max.
    2.33         23.71       34.44      35.82        46.14      82.60

Qualquer conversão aplicada aos dados antes de treinar o modelo deve ser aplicada posteriormente na ordem inversa para converter o atributo novamente nas unidades originais. Para facilitar o dimensionamento, é recomendável salvar os dados de origem ou pelo menos um resumo das estatísticas dos dados de origem.

Seguindo o cenário descrito por Ye no artigo original, dividiremos os dados em um conjunto de treinamento, que inclui 75% de todos os exemplos, e um conjunto de testes, composto por 25%. O arquivo CSV usado é classificado em ordem aleatória, portanto, podemos apenas dividi-lo em duas partes: Usaremos um conjunto de dados de treinamento para construir uma rede neural e um conjunto de dados de teste para avaliar quão bem o modelo se generaliza para resultados futuros. Como a rede neural é facilmente levada a um estado de reciclagem, essa etapa é muito importante.

> concrete_train <- concrete_norm[1:773, ]
> concrete_test <- concrete_norm[774:1030, ]




Etapa 3. Treinando o Modelo em Dados


Para modelar a relação entre os ingredientes utilizados na produção de concreto e a resistência do produto acabado, construiremos uma rede neural de distribuição direta multicamada. O pacote neuralnet, desenvolvido por Stefan Fritsch e Frauke Guenther, fornece uma implementação padrão e fácil de usar dessas redes. Este pacote também inclui uma função para criar uma topologia de rede. A implementação da neuralnet é uma boa maneira de obter informações adicionais sobre redes neurais, embora isso não signifique que também não possa ser usado para realizar um trabalho real - como você verá em breve, é uma ferramenta bastante poderosa.

R , , . nnet R, , , . , . — RSNNS, , , .

Como o pacote neuralnet não está incluído na base R, será necessário instalá-lo digitando install.packages ("neuralnet") e faça o download usando o comando library (neuralnet). A função neuralnet () no pacote pode ser usada para treinar redes neurais na previsão numérica usando a seguinte sintaxe.

Sintaxe de rede neural


Usando a função neuralnet () do pacote neuralnet

Construindo um modelo:

m <- neuralnet(target ~ predictors, data = mydata,
                       hidden = 1, act.fct = "logistic")

• target - um modelo que será construído como resultado do treinamento no quadro de dados mydata;

• preditores - fórmula R que determina as características do quadro de dados mydata a serem usadas na previsão;

• dados - quadro de dados ao qual o alvo e os preditores pertencem;

• oculto - o número de neurônios na camada oculta (o padrão é 1). Nota: para descrever várias camadas ocultas, um vetor de números inteiros é usado, por exemplo, c (2, 2);

• act.fct - função de ativação: "logistic" ou "tanh". Nota: qualquer outra função diferenciável também pode ser usada.

A função retorna um objeto de rede neural que pode ser usado para previsão.

Previsão:

p <- computação (m, teste)

• m - modelo treinado usando a função neuralnet ();

• teste - um quadro de dados contendo dados de teste com as mesmas características que os dados de treinamento usados ​​para construir o classificador.

A função retorna uma lista que consiste em dois componentes: $ neurônios, onde os neurônios são armazenados para cada camada de rede, e $ net.result, onde os valores previstos usando este modelo são armazenados.

Exemplos:



concrete_model <- neuralnet(strength ~ cement + slag + ash,
      data = concrete, hidden = c(5, 5), act.fct = "tanh")
model_results <- compute(concrete_model, concrete_data)
strength_predictions <- model_results$net.result

Vamos começar treinando a rede de distribuição direta multinível mais simples com parâmetros padrão, que possui apenas um nó oculto:

> concrete_model <- neuralnet(strength ~ cement + slag
         + ash + water + superplastic + coarseagg + fineagg + age,
         data = concrete_train)

Então, como mostrado na fig. 7.11, você pode visualizar a topologia de rede usando a função plot () e passando o objeto de modelo resultante:

> plot(concrete_model)

imagem

Nesse modelo simples, há um nó de entrada para cada um dos oito recursos, depois há um nó oculto e um nó de saída, o que fornece uma previsão da resistência do concreto. O diagrama também mostra os pesos para cada link e o valor do deslocamento indicado para os nós marcados com o número 1. O valor do deslocamento é uma constante numérica que permite alterar o valor no nó especificado para cima ou para baixo, aproximadamente como uma mudança em uma equação linear.

Uma rede neural com um nó oculto pode ser considerada a “prima” dos modelos de regressão linear discutidos no Capítulo 6. Os pesos entre os nós de entrada e o nó oculto são semelhantes aos coeficientes beta, e o peso do deslocamento é como uma mudança.

Na parte inferior da figura, o número de etapas do treinamento e a magnitude do erro são exibidos - o erro quadrático médio total (Soma dos erros quadrados, SSE), que, como esperado, é a soma das diferenças quadráticas entre os valores previstos e reais. Quanto menor o SSE, mais precisamente o modelo corresponde aos dados de treinamento, o que indica a eficácia desses dados, mas diz pouco sobre como o modelo funcionará com dados desconhecidos.

Etapa 4. Avaliando a eficácia do modelo


O diagrama de topologia de rede oferece uma oportunidade de examinar a "caixa preta" de uma rede neural, mas não fornece muitas informações sobre o quão bem o modelo corresponde aos dados futuros. Para gerar previsões em um conjunto de dados de teste, você pode usar a

> model_results <- compute(concrete_model, concrete_test[1:8])

função compute (): A função compute () funciona de maneira um pouco diferente das funções predição () que usamos até agora. Ele retorna uma lista composta por dois componentes: $ neurônios, onde os neurônios são armazenados para cada camada da rede, e $ net.result, onde os valores previstos são armazenados. É $ net.result que precisamos:

> predicted_strength <- model_results$net.result

Como temos a tarefa de previsão numérica, e não de classificação, não podemos usar a matriz de inconsistências para verificar a precisão do modelo. Medimos a correlação entre o valor previsto e o verdadeiro valor da resistência do concreto. Se os valores previstos e reais se correlacionarem fortemente, provavelmente o modelo será útil para determinar a resistência do concreto.

Deixe-me lembrá-lo que, para obter a correlação entre dois vetores numéricos, a função cor () é usada:

> cor(predicted_strength, concrete_test$strength)
                    [,1]
[1,] 0.8064655576

Não se assuste se o resultado for diferente do nosso. Como a rede neural começa a trabalhar com pesos aleatórios, as previsões apresentadas no livro podem ser diferentes para diferentes modelos. Se você deseja corresponder com precisão os resultados, tente o comando set.seed (12345) antes de começar a construir uma rede neural.

Se a correlação for próxima de 1, isso indica uma forte relação linear entre as duas variáveis. Portanto, uma correlação de aproximadamente 0,806 indica uma relação bastante forte. Isso significa que o modelo funciona muito bem, mesmo com um único nó oculto. Como usamos apenas um nó oculto, é provável que possamos melhorar a eficiência do modelo, o que tentaremos fazer.

Etapa 5. Melhorando a eficiência do modelo


Como redes com uma topologia mais complexa são capazes de estudar conceitos mais complexos, vamos ver o que acontece se você aumentar o número de nós ocultos para cinco. Usaremos a função neuralnet (), como antes, mas adicionaremos o parâmetro hidden = 5:

> concrete_model2 <- neuralnet(strength ~ cement + slag +
                                               ash + water + superplastic +
                                               coarseagg + fineagg + age,
                                               data = concrete_train, hidden = 5)

Tendo construído o diagrama de rede novamente (Fig. 7.12), veremos um aumento acentuado no número de conexões. Como isso afetou a eficiência?

> plot(concrete_model2)

Observe que o erro resultante (novamente medido como SSE) diminuiu de 5,08 no modelo anterior para 1,63. Além disso, o número de estágios de treinamento aumentou de 4882 para 86.849 - o que não é surpreendente, dada a complexidade do modelo. Quanto mais complexa a rede, mais iterações são necessárias para encontrar os pesos ideais.

Aplicando as mesmas etapas para comparar os valores previstos com os verdadeiros, obtemos uma correlação de cerca de 0,92, o que é muito melhor em comparação com o resultado anterior de 0,80 para uma rede com um nó oculto:

> model_results2 <- compute(concrete_model2, concrete_test[1:8])
> predicted_strength2 <- model_results2$net.result
> cor(predicted_strength2, concrete_test$strength)
                  [,1]
[1,] 0.9244533426

imagem

Apesar das melhorias significativas, você pode ir ainda mais longe para aumentar a eficácia do modelo. Em particular, é possível introduzir camadas ocultas adicionais e alterar a função de ativação da rede. Ao fazer essas mudanças, estamos lançando as bases para a construção de uma rede neural profunda simples.

A escolha da função de ativação é muito importante para o aprendizado profundo. A melhor função para uma tarefa de aprendizado específica geralmente é encontrada experimentalmente e, em seguida, é amplamente utilizada pela comunidade de pesquisadores de aprendizado de máquina.

Recentemente, a função de ativação, chamada função de destilação, ou retificador, tornou-se muito popular devido à sua aplicação bem-sucedida em tarefas complexas, como o reconhecimento de imagens. Um nó de rede neural no qual um retificador é usado como uma função de ativação é chamado de Unidade Linear Retificada (ReLU). Como mostrado na fig. 7.13, a função de ativação do tipo retificador é descrita de maneira que retorne x se x for maior que ou igual a 0 e 0 caso contrário. A importância dessa função é que, por um lado, é não linear e, por outro, possui propriedades matemáticas simples que a tornam computacionalmente barata e altamente eficiente para a descida do gradiente. Infelizmente, para x = 0, a derivada retificadora não está definida,portanto, o retificador não pode ser usado em conjunto com a função neuralnet ().

Em vez disso, você pode usar uma aproximação suavizada da ReLU chamada softplus ou SmoothReLU, uma função de ativação definida como log (1 + ex). Como mostrado na fig. 7.13, a função softplus é próxima de zero para valores de x menores que 0 e aproximadamente igual a x para x maior que 0.

imagem

Para definir a função softplus () em R, usamos o seguinte código:

> softplus <- function(x) { log(1 + exp(x)) }

Essa função de ativação pode ser fornecida à entrada neuralnet () usando o parâmetro act.fct. Além disso, adicionamos uma segunda camada oculta composta por cinco nós, atribuindo ao parâmetro oculto o valor do vetor inteiro c (5, 5). Como resultado, obtemos uma rede de duas camadas, cada uma das quais possui cinco nós, e todas elas usam a função de ativação do softplus:

> set.seed(12345)
> concrete_model3 <- neuralnet(strength ~ cement + slag +
                                               ash + water + superplastic +
                                               coarseagg + fineagg + age,
                                               data = concrete_train,
                                               hidden = c(5, 5),
                                               act.fct = softplus)

Como antes, a rede pode ser visualizada (Fig. 7.14):

> plot(concrete_model3)

imagem

A correlação entre a resistência prevista e real do concreto pode ser calculada da seguinte forma:

> model_results3 <- compute(concrete_model3, concrete_test[1:8])
> predicted_strength3 <- model_results3$net.result
> cor(predicted_strength3, concrete_test$strength)
                  [,1]
[1,] 0.9348395359

A correlação entre a força prevista e a força real foi de 0,935, que é o melhor indicador obtido até o momento. Curiosamente, na publicação original, Ye relatou uma correlação de 0,885. Isso significa que, com relativamente pouco esforço, fomos capazes de obter um resultado comparável e até superar os resultados de um especialista nesse campo. É verdade que os resultados de Ye foram publicados em 1998, o que nos deu uma vantagem em mais de 20 anos de pesquisas adicionais no campo das redes neurais!

Outro detalhe importante deve ser levado em consideração: como normalizamos os dados antes de treinar o modelo, as previsões também estão no intervalo normalizado de 0 a 1. Por exemplo, o código a seguir mostra um quadro de dados que compara linha a linha os valores de resistência do concreto do conjunto de dados inicial com as previsões correspondentes:

> strengths <- data.frame(
      actual = concrete$strength[774:1030],
      pred = predicted_strength3
   )
> head(strengths, n = 3)
      actual        pred
774 30.14 0.2860639091
775 44.40 0.4777304648
776 24.50 0.2840964250


Examinando a correlação, vemos que a escolha de dados normalizados ou anormais não afeta as estatísticas de desempenho calculadas - assim como antes, a correlação é de 0,935: mas se calculamos outro indicador de desempenho, por exemplo, a diferença absoluta entre os valores previstos e reais, a escolha da escala seria muito importante. Com isso em mente, você pode criar a função unnormalize () que executaria o inverso da normalização do minimax e permitiria converter previsões normalizadas para a escala original:

> cor(strengths$pred, strengths$actual)
[1] 0.9348395359






> unnormalize <- function(x) {
     return((x * (max(concrete$strength)) -
           min(concrete$strength)) + min(concrete$strength))
   }

Depois de aplicar a função unnormalize () que escrevemos nas previsões, fica claro que a escala das novas previsões é semelhante aos valores iniciais da resistência do concreto. Isso permite calcular o valor significativo do erro absoluto. Além disso, a correlação entre os valores de força anormais e iniciais permanece inalterada:

> strengths$pred_new <- unnormalize(strengths$pred)
> strengths$error <- strengths$pred_new — strengths$actual
> head(strengths, n = 3)
           actual                pred             pred_new                    error
774          30.14         0.2860639091               23.62887889      -6.511121108
775          44.40         0.4777304648               39.46053639      -4.939463608
776          24.50         0.2840964250               23.46636470      -1.033635298

> cor(strengths$pred_new, strengths$actual)
[1] 0.9348395359

Ao aplicar redes neurais aos seus projetos, você precisa seguir uma sequência semelhante de etapas para retornar os dados à sua escala original.

Você também pode descobrir que as redes neurais estão se tornando rapidamente mais complexas à medida que são usadas para tarefas de aprendizado cada vez mais difíceis. Por exemplo, você pode encontrar o chamado problema de gradiente pequeno "desaparecendo" e o problema de gradiente "explodindo" intimamente relacionado, quando o algoritmo de propagação traseira não encontra uma solução útil, pois não converge em um tempo razoável. Para resolver esses problemas, você pode tentar alterar o número de nós ocultos, aplicar várias funções de ativação, como ReLU, ajustar a velocidade de aprendizado etc. Na página de ajuda da função neuralnet, você encontrará informações adicionais sobre os vários parâmetros que podem ser configurados. No entanto, isso leva a outro problema,quando o gargalo na construção de um modelo altamente eficiente está verificando um grande número de parâmetros. Esse é o preço do uso de redes neurais e redes de aprendizado ainda mais profundas: seu enorme potencial requer muito tempo e poder de processamento.

, ML . , Amazon Web Services (AWS) Microsoft Azure, . 12.


O método Support Vector Machine (SVM) pode ser representado como uma superfície que forma o limite entre os pontos de dados plotados em um espaço multidimensional que descreve exemplos e valores de seus atributos. O objetivo do SVM é construir uma borda plana - um hiperplano que divide o espaço de maneira que grupos homogêneos se formem nos dois lados. Portanto, o treinamento SVM combina aspectos do treinamento do vizinho mais próximo com base nas instâncias descritas no Capítulo 3 e na modelagem de regressão linear, discutida no Capítulo 6. Essa é uma combinação extremamente poderosa que permite que os SVMs modelem relacionamentos muito complexos.

Apesar do fato de que a matemática básica subjacente ao SVM existe há décadas, o interesse por esses métodos aumentou significativamente depois que começaram a ser aplicados ao BC. A popularidade desses métodos aumentou após histórias de sucesso de alto perfil na solução de problemas complexos de aprendizado, bem como após o desenvolvimento de algoritmos SVM, que foram premiados e implementados em bibliotecas bem suportadas em muitas linguagens de programação, incluindo R. Depois disso, os métodos SVM foram aceitos por um amplo público. Caso contrário, provavelmente seria impossível aplicar a matemática complexa necessária para implementar o SVM. A boa notícia é que, embora a matemática seja possivelmente complexa, os conceitos básicos são compreensíveis.

Os métodos SVM podem ser adaptados para usar quase qualquer tipo de tarefa de treinamento, incluindo classificação e previsão numérica. Muitos dos principais sucessos desse algoritmo estão relacionados ao reconhecimento de padrões. Os aplicativos mais conhecidos para esses métodos incluem o seguinte:

  • classificação de dados sobre a expressão de genes de microarrays em bioinformática para a detecção de câncer e outras doenças genéticas;
  • categorização de texto, como determinar o idioma usado em um documento ou classificar documentos por tópico;
  • Detecção de eventos raros, mas importantes, como falha de um motor de combustão interna, violação de segurança ou terremoto.


Os métodos SVM são mais fáceis de entender usando a classificação binária como exemplo - é assim que eles geralmente são usados. Portanto, nas seções restantes, focaremos apenas nos classificadores SVM. Princípios semelhantes aos apresentados aqui também são usados ​​ao adaptar métodos SVM para previsão numérica.

Sobre o autor


Brett Lantz (@DataSpelunking) usa técnicas inovadoras de processamento de dados para estudar o comportamento humano há mais de uma década. Sendo sociólogo em treinamento, Brett ficou interessado no aprendizado de máquina enquanto pesquisava um grande banco de dados de perfis de adolescentes em redes sociais. Brett é professor no DataCamp e costuma fazer apresentações em conferências e seminários de aprendizado de máquina em todo o mundo. Ele é um entusiasta bem conhecido no campo da aplicação prática da ciência de dados no campo do esporte, veículos não tripulados, estudo de línguas e moda estrangeiras, bem como em muitas outras indústrias. Brett espera escrever um dia sobre tudo isso no site dataspelunking.com, dedicado ao compartilhamento de conhecimento sobre como encontrar padrões nos dados.

Sobre o Science Editor


Raghav Bali (Raghav Bali) - pesquisador sênior de uma das maiores organizações de assistência médica do mundo. Ele se dedica à pesquisa e desenvolvimento de soluções corporativas baseadas em aprendizado de máquina, aprendizado profundo e processamento de linguagem natural para uso em saúde e seguros. Em sua posição anterior na Intel, ele participou de iniciativas proativas no campo da tecnologia da informação baseadas em big data, usando processamento de linguagem natural, aprendizado profundo e métodos estatísticos tradicionais. Na American Express, ele trabalhou em engajamento digital e retenção de clientes.

Raghav é o autor de vários livros publicados pelos principais editores. Seu último livro é sobre o mais recente estudo de transferência.

Raghav se formou no Instituto Internacional de Tecnologia da Informação em Bangalore, possui um mestrado (honras). Nos raros momentos em que ele não está ocupado resolvendo problemas científicos, Raghav gosta de ler e fotografar tudo em sequência.

»Mais informações sobre o livro podem ser encontradas no site da editora
» Conteúdo
» Trecho

do cupom Khabrozhiteley de 25% de desconto no cupom - Aprendizado de máquina

Após o pagamento da versão impressa do livro, um livro eletrônico é enviado por e-mail.

All Articles