Aprendizagem profunda para classificação de texto com Keras


O conjunto de dados IMDB

Neste exemplo, trabalharemos com o conjunto de dados IMDB: um conjunto de 50.000 críticas altamente polarizadas do banco de dados de filmes da Web. Eles estão divididos em 25.000 revisões para treinamento e 25.000 revisões para testes, cada conjunto que consiste em 50% negativo e 50% de revisões positivas.

Por que usar conjuntos de treinamento e teste separados? Porque você nunca deve testar um modelo de aprendizado de máquina nos mesmos dados que você usou para treiná-los! Só porque um modelo tem um bom desempenho em seus dados de treinamento não significa que ele terá um bom desempenho nos dados que nunca viu; E o que você se importa é o desempenho do seu modelo em novos dados (porque você já conhece os rótulos dos seus dados de treinamento – obviamente você não precisa do seu modelo para prever isso). Por exemplo, é possível que seu modelo possa acabar apenas memorização Um mapeamento entre suas amostras de treinamento e suas metas, o que seria inútil para a tarefa de prever metas para dados que o modelo nunca viu antes. Examinaremos esse ponto com muito mais detalhes no próximo capítulo.

Assim como o conjunto de dados MNIST, o conjunto de dados IMDB vem embalado com Keras. Já foi pré -processado: as revisões (sequências de palavras) foram transformadas em sequências de números inteiros, onde cada número inteiro significa uma palavra específica em um dicionário.

O código a seguir carregará o conjunto de dados (quando você o executar pela primeira vez, cerca de 80 MB de dados serão baixados na sua máquina).

library(keras)
imdb <- dataset_imdb(num_words = 10000)
train_data <- imdb$practice$x
train_labels <- imdb$practice$y
test_data <- imdb$take a look at$x
test_labels <- imdb$take a look at$y

O argumento num_words = 10000 Significa que você manterá apenas as 10.000 principais palavras que ocorrem com mais frequência nos dados de treinamento. Palavras raras serão descartadas. Isso permite que você trabalhe com dados vetoriais de tamanho gerenciável.

As variáveis train_data e test_data são listas de revisões; Cada revisão é uma lista de índices de palavras (codificando uma sequência de palavras). train_labels e test_labels são listas de 0s e 1s, onde 0 significa negativo e 1 significa positivo:

int (1:218) 1 14 22 16 43 530 973 1622 1385 65 ...
(1) 1

Como você está se restringindo às 10.000 palavras mais frequentes, nenhum índice de palavras excederá 10.000:

(1) 9999

Para chutes, veja como você pode decodificar rapidamente uma dessas críticas às palavras em inglês:

# Named checklist mapping phrases to an integer index.
word_index <- dataset_imdb_word_index()  
reverse_word_index <- names(word_index)
names(reverse_word_index) <- word_index

# Decodes the assessment. Be aware that the indices are offset by 3 as a result of 0, 1, and 
# 2 are reserved indices for "padding," "begin of sequence," and "unknown."
decoded_review <- sapply(train_data((1)), operate(index) {
  phrase <- if (index >= 3) reverse_word_index((as.character(index - 3)))
  if (!is.null(phrase)) phrase else "?"
})
cat(decoded_review)
? this movie was simply good casting location surroundings story route
everybody's actually suited the half they performed and you could possibly simply think about
being there robert ? is an incredible actor and now the identical being director
? father got here from the identical scottish island as myself so i cherished the very fact
there was an actual reference to this movie the witty remarks all through
the movie have been nice it was simply good a lot that i purchased the movie
as quickly because it was launched for ? and would suggest it to everybody to 
watch and the fly fishing was superb actually cried on the finish it was so
unhappy and  what they are saying when you cry at a movie it will need to have been 
good and this positively was additionally ? to the 2 little boy's that performed'
the ? of norman and paul they have been simply good kids are sometimes left
out of the ? checklist i feel as a result of the celebrities that play all of them grown up
are such an enormous profile for the entire movie however these kids are superb
and needs to be praised for what they've completed do not you suppose the entire
story was so pretty as a result of it was true and was somebody's life in spite of everything
that was shared with us all

Preparando os dados

Você não pode alimentar listas de números inteiros em uma rede neural. Você tem que transformar suas listas em tensores. Existem duas maneiras de fazer isso:

  • Ponha suas listas para que todas tenham o mesmo comprimento, transforme -as em um tensor inteiro de forma (samples, word_indices)e, em seguida, use como a primeira camada da sua rede, uma camada capaz de lidar com esses tensores inteiros (a camada “incorporando”, que abordaremos detalhadamente no remaining do livro).
  • Um único codifica suas listas para transformá-las em vetores de 0s e 1s. Isso significaria, por exemplo, girando a sequência (3, 5) em um vetor 10.000 dimensional que seria todos 0s, exceto os índices 3 e 5, que seriam 1s. Em seguida, você pode usar como a primeira camada da sua rede uma camada densa, capaz de lidar com dados vetoriais de ponto flutuante.

Vamos com a última solução para vetorizar os dados, que você fará manualmente para obter a máxima clareza.

vectorize_sequences <- operate(sequences, dimension = 10000) {
  # Creates an all-zero matrix of form (size(sequences), dimension)
  outcomes <- matrix(0, nrow = size(sequences), ncol = dimension) 
  for (i in 1:size(sequences))
    # Units particular indices of outcomes(i) to 1s
    outcomes(i, sequences((i))) <- 1 
  outcomes
}

x_train <- vectorize_sequences(train_data)
x_test <- vectorize_sequences(test_data)

Aqui está como são as amostras agora:

 num (1:10000) 1 1 0 1 1 1 1 1 1 0 ...

Você também deve converter seus rótulos de inteiro em numérico, o que é direto:

Agora, os dados estão prontos para serem alimentados em uma rede neural.

Construindo sua rede

Os dados de entrada são vetores e os rótulos são escalares (1s e 0s): esta é a configuração mais fácil que você já encontrará. Um tipo de rede que tem um bom desempenho nesse problema é uma pilha simples de camadas totalmente conectadas (“densas”) com relu Ativações: layer_dense(items = 16, activation = "relu").

O argumento passado para cada camada densa (16) é o número de unidades ocultas da camada. UM unidade oculta é uma dimensão no espaço de representação da camada. Você deve se lembrar do capítulo 2 de que cada uma dessas camadas densas com um relu A ativação implementa a seguinte cadeia de operações tensoras:

output = relU (Dot (W, entrada) + B)

Ter 16 unidades ocultas significa a matriz de peso W terá forma (input_dimension, 16): o produto DOT com W projetará os dados de entrada em um espaço de representação 16-dimensional (e então você adicionará o vetor de polarização b e aplique o relu operação). Você pode entender intuitivamente a dimensionalidade do seu espaço de representação como “quanta liberdade está permitindo que a rede tenha ao aprender representações internas”. Ter unidades mais ocultas (um espaço de representação superior dimensional) permite que sua rede aprenda representações mais complexas, mas torna a rede mais cara computacionalmente e pode levar a aprender padrões indesejados (padrões que melhorarão o desempenho nos dados de treinamento, mas não nos dados de teste).

Existem duas decisões principais de arquitetura a serem tomadas sobre essa pilha de camadas densas:

  • Quantas camadas para usar
  • Quantas unidades ocultas para escolher para cada camada

No capítulo 4, você aprenderá princípios formais para guiá -lo a fazer essas escolhas. Por enquanto, você terá que confiar em mim com a seguinte opção de arquitetura:

  • Duas camadas intermediárias com 16 unidades ocultas cada
  • Uma terceira camada que produzirá a previsão escalar em relação ao sentimento da revisão atual

As camadas intermediárias usarão relu Como sua função de ativação, e a camada remaining usará uma ativação sigmóide para produzir uma probabilidade (uma pontuação entre 0 e 1, indicando qual a probabilidade de a amostra ter o alvo “1”: qual a probabilidade de a revisão ser positiva). UM relu (unidade linear retificada) é uma função destinada a zero valores negativos.

Aprendizagem profunda para classificação de texto com Keras

Um sigmóide “squashes” valores arbitrários no (0, 1) intervalo, produzindo algo que pode ser interpretado como uma probabilidade.

Aqui está a aparência da rede.

Aqui está a implementação do Keras, semelhante ao exemplo mnist que você viu anteriormente.

library(keras)

mannequin <- keras_model_sequential() %>% 
  layer_dense(items = 16, activation = "relu", input_shape = c(10000)) %>% 
  layer_dense(items = 16, activation = "relu") %>% 
  layer_dense(items = 1, activation = "sigmoid")

Funções de ativação

Observe que sem uma função de ativação como relu (também chamado a não linearidade), a camada densa consistiria em duas operações lineares – um produto DOT e uma adição:

saída = ponto (w, entrada) + b

Então a camada só poderia aprender Transformações lineares (transformações afins) dos dados de entrada: o Espaço de hipótese da camada seria o conjunto de todas as transformações lineares possíveis dos dados de entrada em um espaço 16-dimensional. Esse espaço de hipótese é muito restrito e não se beneficiaria de várias camadas de representações, porque uma pilha profunda de camadas lineares ainda implementaria uma operação linear: adicionar mais camadas não estenderia o espaço de hipótese.

Para obter acesso a um espaço de hipótese muito mais rico que se beneficiaria de representações profundas, você precisa de uma não linearidade ou função de ativação. relu é a função de ativação mais widespread no aprendizado profundo, mas existem muitos outros candidatos, que vêm com nomes igualmente estranhos: preluAssim, elue assim por diante.

Função de perda e otimizador

Por fim, você precisa escolher uma função de perda e um otimizador. Como você está enfrentando um problema de classificação binária e a saída da sua rede é uma probabilidade (você termina sua rede com uma camada única com uma ativação sigmóide), é melhor usar o binary_crossentropy perda. Não é a única escolha viável: você pode usar, por exemplo, mean_squared_error. Mas o Crossentrepropia é geralmente a melhor opção quando você está lidando com modelos de probabilidades de saída. Cruzentropia é uma quantidade do campo da teoria da informação que mede a distância entre as distribuições de probabilidade ou, neste caso, entre a distribuição do solo e suas previsões.

Aqui está o passo em que você configura o modelo com o rmsprop otimizador e o binary_crossentropy função de perda. Observe que você também monitorará a precisão durante o treinamento.

mannequin %>% compile(
  optimizer = "rmsprop",
  loss = "binary_crossentropy",
  metrics = c("accuracy")
)

Você está passando seu otimizador, função de perda e métricas como cordas, o que é possível porque rmspropAssim, binary_crossentropye accuracy são embalados como parte de Keras. Às vezes, você pode configurar os parâmetros do seu otimizador ou passar em uma função de perda personalizada ou função métrica. O primeiro pode ser feito passando uma instância de otimizador como o optimizer argumento:

mannequin %>% compile(
  optimizer = optimizer_rmsprop(lr=0.001),
  loss = "binary_crossentropy",
  metrics = c("accuracy")
) 

As funções de perda e métricas personalizadas podem ser fornecidas pela passagem de objetos de função como o loss e/ou metrics argumentos

mannequin %>% compile(
  optimizer = optimizer_rmsprop(lr = 0.001),
  loss = loss_binary_crossentropy,
  metrics = metric_binary_accuracy
) 

Validando sua abordagem

Para monitorar durante o treinamento, a precisão do modelo nos dados que ele nunca viu antes, você criará uma validação definida separando 10.000 amostras dos dados originais de treinamento.

val_indices <- 1:10000

x_val <- x_train(val_indices,)
partial_x_train <- x_train(-val_indices,)

y_val <- y_train(val_indices)
partial_y_train <- y_train(-val_indices)

Agora você treinará o modelo para 20 épocas (20 iterações em todas as amostras no x_train e y_train tensores), em mini-lotes de 512 amostras. Ao mesmo tempo, você monitorará a perda e a precisão nas 10.000 amostras que você separou. Você faz isso passando os dados de validação como o validation_data argumento.

mannequin %>% compile(
  optimizer = "rmsprop",
  loss = "binary_crossentropy",
  metrics = c("accuracy")
)

historical past <- mannequin %>% match(
  partial_x_train,
  partial_y_train,
  epochs = 20,
  batch_size = 512,
  validation_data = checklist(x_val, y_val)
)

Na CPU, isso levará menos de 2 segundos por época – o treinamento terminou em 20 segundos. No remaining de cada época, há uma ligeira pausa, pois o modelo calcula sua perda e precisão nas 10.000 amostras dos dados de validação.

Observe que a chamada para match() retorna a historical past objeto. O historical past objeto tem um plot() Método que nos permite visualizar as métricas de treinamento e validação da Epoch:

A precisão é plotada no painel superior e a perda no painel inferior. Observe que seus próprios resultados podem variar um pouco devido a uma inicialização aleatória diferente da sua rede.

Como você pode ver, a perda de treinamento diminui com cada época, e a precisão do treinamento aumenta com cada época. É isso que você esperaria ao executar uma otimização de subscente de gradiente-a quantidade que você está tentando minimizar deve ser menor a cada iteração. Mas esse não é o caso da perda e precisão da validação: eles parecem atingir o pico na quarta época. Este é um exemplo do que alertamos anteriormente: um modelo que tem um desempenho melhor nos dados de treinamento não é necessariamente um modelo que se sairá melhor nos dados que nunca viu antes. Em termos precisos, o que você está vendo é exagerado: Após a segunda época, você está otimizando sobre os dados de treinamento e acaba aprendendo representações específicas para os dados de treinamento e não generalizam para dados fora do conjunto de treinamento.

Nesse caso, para evitar o excesso de ajuste, você pode parar de treinar após três épocas. Em geral, você pode usar uma variedade de técnicas para mitigar o excesso de ajuste, que abordaremos no Capítulo 4.

Vamos treinar uma nova rede do zero para quatro épocas e avaliá -la nos dados do teste.

mannequin <- keras_model_sequential() %>% 
  layer_dense(items = 16, activation = "relu", input_shape = c(10000)) %>% 
  layer_dense(items = 16, activation = "relu") %>% 
  layer_dense(items = 1, activation = "sigmoid")

mannequin %>% compile(
  optimizer = "rmsprop",
  loss = "binary_crossentropy",
  metrics = c("accuracy")
)

mannequin %>% match(x_train, y_train, epochs = 4, batch_size = 512)
outcomes <- mannequin %>% consider(x_test, y_test)
$loss
(1) 0.2900235

$acc
(1) 0.88512

Essa abordagem bastante ingênua alcança uma precisão de 88%. Com abordagens de última geração, você poderá chegar perto de 95%.

Gerando previsões

Depois de ter treinado uma rede, você deve usá -la em um ambiente prático. Você pode gerar a probabilidade de as críticas serem positivas usando o predict método:

 (1,) 0.92306918
 (2,) 0.84061098
 (3,) 0.99952853
 (4,) 0.67913240
 (5,) 0.73874789
 (6,) 0.23108074
 (7,) 0.01230567
 (8,) 0.04898361
 (9,) 0.99017477
(10,) 0.72034937

Como você pode ver, a rede está confiante para algumas amostras (0,99 ou mais, ou 0,01 ou menos), mas menos confiante para outros (0,7, 0,2).

Outras experiências

Os experimentos a seguir ajudarão a convencê -lo de que as opções de arquitetura que você fez são bastante razoáveis, embora ainda haja espaço para melhorias.

  • Você usou duas camadas ocultas. Tente usar uma ou três camadas ocultas e veja como isso afeta a validação e a precisão do teste.
  • Tente usar camadas com unidades mais ocultas ou menos unidades ocultas: 32 unidades, 64 unidades e assim por diante.
  • Tente usar o mse função de perda em vez de binary_crossentropy.
  • Tente usar o tanh ativação (uma ativação que period widespread nos primeiros dias das redes neurais) em vez de relu.

Embrulhando

Aqui está o que você deve tirar deste exemplo:

  • Você geralmente precisa fazer um pouco de pré -processamento em seus dados brutos para poder alimentá -los – como tensores – em uma rede neural. Sequências de palavras podem ser codificadas como vetores binários, mas também existem outras opções de codificação.
  • Pilhas de camadas densas com relu As ativações podem resolver uma ampla gama de problemas (incluindo classificação de sentimentos) e você provavelmente os usará com frequência.
  • Em um problema de classificação binária (duas lessons de saída), sua rede deve terminar com uma camada densa com uma unidade e um sigmoid Ativação: a saída da sua rede deve ser um escalar entre 0 e 1, codificando uma probabilidade.
  • Com uma saída sigmóide tão escalar em um problema de classificação binária, a função de perda que você deve usar é binary_crossentropy.
  • O rmsprop O otimizador geralmente é uma escolha suficientemente boa, qualquer que seja o seu problema. Essa é uma coisa a menos para você se preocupar.
  • À medida que melhoram seus dados de treinamento, as redes neurais acabam começando a ajustar demais e acabam obtendo resultados cada vez mais piores nos dados que nunca viram antes. Certifique -se de monitorar sempre o desempenho em dados fora do conjunto de treinamento.

Deixe um comentário

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