Cet exemple est inspiré du projet chargpt par Andrey Karpathy. Nous allons créer un modèle de langage au niveau caractère sur des textes shakespeariens.
Tout d’abord, nous chargeons les bibliothèques que nous comptons utiliser :
Ensuite, nous définissons le jeu de données torch qui prépare les données pour le modèle. Il divise le texte en un vecteur de caractères, chaque élément contenant exactement un caractère.
Puis il liste tous les caractères uniques dans l’attribut
vocab
. L’ordre des caractères dans le vocabulaire est
utilisé pour encoder chaque caractère sous forme d’une valeur entière,
qui sera utilisée dans la couche d’embeddings.
La méthode .getitem()
peut prendre des blocs de
block_size
caractères et les encoder en leur représentation
sous forme d’entiers.
url <- "https://raw.githubusercontent.com/karpathy/char-rnn/master/data/tinyshakespeare/input.txt"
dataset <- torch::dataset(
initialize = function(data, block_size = 128) {
self$block_size <- block_size
self$data <- stringr::str_split_1(data, "")
self$data_size <- length(self$data)
self$vocab <- unique(self$data)
self$vocab_size <- length(self$vocab)
},
.getitem = function(i) {
chunk <- self$data[i + seq_len(self$block_size + 1)]
idx <- match(chunk, self$vocab)
list(
x = head(idx, self$block_size),
y = tail(idx, self$block_size)
)
},
.length = function() {
self$data_size - self$block_size - 1L # cela permet de tenir compte de la dernière valeur
}
)
dataset <- char_dataset(readr::read_file(url))
dataset[1] # visualisons un élément du jeu de données
Nous définissons ensuite le réseau de neurones que nous allons entraîner. Définir un modèle GPT-2 est assez verbeux, donc on va utiliser directement l’implémentation {minhub}. Vous pouvez trouver la définition complète du modèle ici, avec un code tout à fait autonome, vous n’avez pas besoin d’installer minhub si vous ne le souhaitez pas.
Nous avons également implémenté la méthode generate
pour
le modèle, qui permet de générer des auto-complétions en utilisant le
modèle. Il s’agit d’appliquer le modèle en boucle, à chaque itération,
il prédit quel est le caractère suivant.
fitted <- model |>
setup(
loss = nn_cross_entropy_loss(),
optimizer = optim_adam
) |>
set_opt_hparams(lr = 5e-4) |>
set_hparams(vocab_size = dataset$vocab_size) |>
fit(
dataset,
dataloader_options = list(batch_size = 128, shuffle = TRUE),
epochs = 1,
callbacks = list(
display_cb(iter = 500),
luz_callback_gradient_clip(max_norm = 1)
)
)
Un entrâinement d’une époque est raisonnable pour ce jeu de données et prend ~1h sur le M1 MBP. Vous pouvez générer des nouveaux textes avec :
context <- "O Dieu, O Dieu !"
text <- generate(fitted$model, dataset$vocab, context, iter = 100)
cat(text)