Mettre à jour votre application pour utiliser un modèle de machine learning avec filtrage du spam

1. Avant de commencer

Dans cet atelier de programmation, vous allez mettre à jour l'application que vous avez créée dans les précédents ateliers de programmation sur la classification de texte pour mobile.

Prérequis

  • Cet atelier de programmation s'adresse aux développeurs expérimentés qui ne connaissent pas le machine learning.
  • Cet atelier de programmation fait partie d'un parcours séquentiel. Si vous n'avez pas encore terminé de créer une application basique de style de messagerie ou de création d'un modèle de machine learning en tant que spam dans les commentaires, veuillez vous arrêter.

Objectifs de l'atelier

  • Vous apprendrez à intégrer votre modèle personnalisé à votre application, conçu lors des étapes précédentes.

Prérequis

2. Ouvrir l'application Android existante

Vous pouvez obtenir le code en suivant l'atelier de programmation 1 ou en clonant ce dépôt et en chargeant l'application à partir de TextClassificationStep1.

git clone https://github.com/googlecodelabs/odml-pathways

Vous pouvez trouver ce lien dans le chemin TextClassificationOnMobile->Android.

Le code finalisé est également disponible sous la forme TextClassificationStep2.

Une fois l'application ouverte, vous pouvez passer à l'étape 2.

3. Importer le fichier de modèle et les métadonnées

Dans l'atelier de programmation "Créer un modèle de machine learning avec des commentaires de type spam", vous avez créé un modèle .TFLITE.

Vous devriez avoir téléchargé le fichier de modèle. Si vous ne l'avez pas, vous pouvez la récupérer dans le dépôt de cet atelier de programmation. Cliquez ici pour obtenir le modèle.

Ajoutez-le à votre projet en créant un répertoire d'éléments.

  1. Dans le navigateur de projets, assurez-vous qu'Android est sélectionné en haut.
  2. Effectuez un clic droit sur le dossier app. Sélectionnez New > Directory (Nouveau > Annuaire).

D7c3e9f21035fc15.png

  1. Dans la boîte de dialogue New Directory (Nouveau répertoire), sélectionnez src/main/assets.

2137f956a1ba4ef0.png

Un nouveau dossier assets est désormais disponible dans l'application.

AE858835E1a90445.png

  1. Effectuez un clic droit sur éléments.
  2. Dans le menu qui s'affiche, l'option Afficher dans le Finder est disponible sous Mac. Sélectionnez-la. Sous Windows, la commande est Show in Explorer (Afficher dans l'explorateur) sur Ubuntu, elle indique Show in Files (Afficher dans les fichiers).

e61aaa3b73c5ab68.png

Le Finder s'ouvre pour afficher l'emplacement des fichiers (Explorateur de fichiers sous Windows, Fichiers sous Linux).

  1. Copiez les fichiers labels.txt, model.tflite et vocab dans ce répertoire.

14F382cc19552a56.png

  1. Revenez dans Android Studio et retrouvez-les dans le dossier assets.

150ed2a1d2f7a10d.png

4. Mettre à jour build.gradle pour utiliser TensorFlow Lite

Pour utiliser TensorFlow Lite et les bibliothèques de tâches TensorFlow Lite compatibles, vous devez mettre à jour votre fichier build.gradle.

Les projets Android en ont souvent plusieurs. Assurez-vous donc de rechercher la version app. Dans l'explorateur de projets, accédez à la section Scripts Gradle de l'explorateur de projets. Le bon libellé portera le libellé .app, comme indiqué ci-dessous:

6426051e614bc42f.png

Vous devrez apporter deux modifications à ce fichier. Le premier se trouve dans la section dependencies au bas de l'écran. Ajoutez un texte implementation pour la bibliothèque de tâches TensorFlow Lite, comme suit:

implementation 'org.tensorflow:tensorflow-lite-task-text:0.1.0'

Il est possible que le numéro de version ait été modifié depuis sa création. Vérifiez donc l'état de la dernière version sur https://www.tensorflow.org/lite/inference_with_metadata/task_library/nl_classifier.

Les bibliothèques de tâches nécessitent également une version minimale du SDK 21. Recherchez ce paramètre dans android > default config, puis définissez-le sur 21:

C100b68450b8812f.png

Vous disposez à présent de toutes vos dépendances. Il est donc temps de commencer à coder.

5. Ajouter une classe d'assistance

Pour séparer la logique d'inférence, où votre application utilise le modèle, créez une autre classe dans l'interface utilisateur pour gérer l'inférence de modèle. C'est ce qu'on appelle une classe "Helper".

  1. Effectuez un clic droit sur le nom du package dans lequel se trouve votre code MainActivity.
  2. Sélectionnez New > Package (Nouveau > Package).

D5911ded56b5df35.png

  1. Une boîte de dialogue s'affiche au centre de l'écran, vous invitant à saisir le nom du package. Ajoutez-le à la fin du nom du package actuel. (Dans cet exemple, il s'agit des aides.)

3b9f1f822f99b371.png

  1. Ensuite, faites un clic droit sur le dossier helpers dans l'explorateur de projets.
  2. Sélectionnez New > Java Class (Nouveau > Classe Java), puis nommez-le TextClassificationClient. Vous modifierez le fichier à l'étape suivante.

Votre classe d'assistance TextClassificationClient ressemblera à ceci (même si le nom de votre package peut être différent).

package com.google.devrel.textclassificationstep1.helpers;

public class TextClassificationClient {
}
  1. Modifiez le fichier avec le code suivant:
package com.google.devrel.textclassificationstep2.helpers;

import android.content.Context;
import android.util.Log;
import java.io.IOException;
import java.util.List;

import org.tensorflow.lite.support.label.Category;
import org.tensorflow.lite.task.text.nlclassifier.NLClassifier;

public class TextClassificationClient {
    private static final String MODEL_PATH = "model.tflite";
    private static final String TAG = "CommentSpam";
    private final Context context;

    NLClassifier classifier;

    public TextClassificationClient(Context context) {
        this.context = context;
    }

    public void load() {
        try {
            classifier = NLClassifier.createFromFile(context, MODEL_PATH);
        } catch (IOException e) {
            Log.e(TAG, e.getMessage());
        }
    }

    public void unload() {
        classifier.close();
        classifier = null;
    }

    public List<Category> classify(String text) {
        List<Category> apiResults = classifier.classify(text);
        return apiResults;
    }

}

Cette classe fournira un wrapper à l'interpréteur TensorFlow Lite, permettant de charger le modèle et d'abstraire la complexité de la gestion de l'échange de données entre votre application et le modèle.

Dans la méthode load(), il instancie un nouveau type NLClassifier à partir du chemin du modèle. Le chemin d'accès du modèle est simplement le nom du modèle, model.tflite. Le type NLClassifier fait partie des bibliothèques de tâches de texte. Il vous permet de convertir votre chaîne en jetons en utilisant la longueur de séquence correcte, en la transmettant au modèle et en analysant les résultats.

(Pour plus d'informations à ce sujet, consultez à nouveau l'article "Créer un modèle de machine learning pour le spam dans les commentaires".)

La classification est effectuée dans la méthode de classification (où vous lui transmettez une chaîne) et renvoie une List. Lorsque vous utilisez des modèles de machine learning pour classer des contenus dont vous souhaitez déterminer si une chaîne correspond ou non à du spam, il est courant que toutes les réponses soient renvoyées, avec des probabilités attribuées. Par exemple, si vous lui transmettez un message qui ressemble à du spam, vous obtiendrez une liste de deux réponses. un avec la probabilité qu'il s'agisse de spam et un avec la probabilité qu'il ne le soit pas. Les éléments Spam/Non-spam sont des catégories. Par conséquent, le champ List contiendra ces probabilités. Vous l'analyserez plus tard.

Maintenant que vous disposez de la classe auxiliaire, revenez à votre MainActivity et mettez-la à jour pour utiliser cette méthode afin de classer votre texte. Vous le verrez à l'étape suivante !

6. Classer le texte

Dans votre MainActivity, vous devez d'abord importer les assistants que vous venez de créer.

  1. En haut de MainActivity.kt, ajoutez les autres importations en procédant comme suit:
import com.google.devrel.textclassificationstep2.helpers.TextClassificationClient
import org.tensorflow.lite.support.label.Category
  1. Ensuite, vous devez charger les assistants. Dans onCreate, immédiatement après la ligne setContentView, ajoutez ces lignes pour instancier et charger la classe auxiliaire:
val client = TextClassificationClient(applicationContext)
client.load()

Pour le moment, le onClickListener de votre bouton doit se présenter comme suit:

btnSendText.setOnClickListener {
     var toSend:String = txtInput.text.toString()
     txtOutput.text = toSend
 }
  1. Modifiez-le comme suit:
btnSendText.setOnClickListener {
    var toSend:String = txtInput.text.toString()
    var results:List<Category> = client.classify(toSend)
    val score = results[1].score
    if(score>0.8){
        txtOutput.text = "Your message was detected as spam with a score of " + score.toString() + " and not sent!"
    } else {
        txtOutput.text = "Message sent! \nSpam score was:" + score.toString()
    }
    txtInput.text.clear()
}

Cette modification modifie la fonctionnalité en passant de la sortie de l'entrée utilisateur à la classification uniquement.

  1. Cette ligne vous permet de transmettre la chaîne saisie par l'utilisateur au modèle et d'obtenir des résultats:
var results:List<Category> = client.classify(toSend)

Il n'y a que deux catégories : False et True.

. (TensorFlow les trie par ordre alphabétique. Ainsi, False sera l'élément 0 et True sera l'élément 1.)

  1. Pour obtenir le score de la probabilité que la valeur soit True, vous pouvez consulter les résultats[1].score comme suit:
    val score = results[1].score
  1. Vous avez sélectionné une valeur de seuil (dans ce cas, 0,8), où vous indiquez que si le score de la catégorie True est supérieur à la valeur de seuil (0,8), le message est un spam. Sinon, il ne s'agit pas de spam et le message peut être envoyé en toute sécurité:
    if(score>0.8){
        txtOutput.text = "Your message was detected as spam with a score of " + score.toString() + " and not sent!"
    } else {
        txtOutput.text = "Message sent! \nSpam score was:" + score.toString()
    }
  1. Découvrez le modèle en action ici. Le message "Rendez-vous sur mon blog pour acheter des articles !" a été signalée comme une probabilité élevée de spam:

1fb0b5de9e566e.png

Et, à l'inverse, "Hey, tutoriel ludique, merci !" était très peu susceptible d'être du spam:

73f38bdb488b29b3.png

7. Mettre à jour votre application iOS pour utiliser le modèle TensorFlow Lite

Vous pouvez obtenir le code en suivant l'atelier de programmation 1 ou en clonant ce dépôt et en chargeant l'application à partir de TextClassificationStep1. Vous pouvez trouver ce lien dans le chemin TextClassificationOnMobile->iOS.

Le code finalisé est également disponible sous la forme TextClassificationStep2.

Dans l'atelier de programmation "Créer un modèle de machine learning dédié au spam dans les commentaires", vous avez créé une application très simple qui permet à l'utilisateur de saisir un message dans un UITextView et de le transmettre à une sortie sans aucun filtre.

Vous allez maintenant mettre à jour cette application pour qu'elle utilise un modèle TensorFlow Lite afin de détecter le spam dans les commentaires avant l'envoi. Il suffit de simuler l'envoi dans cette application en affichant le texte dans un libellé de sortie (mais une application réelle peut contenir un tableau d'affichage, un chat ou autre).

Pour commencer, vous aurez besoin de l'application de l'étape 1, que vous pouvez cloner à partir du dépôt.

Pour intégrer TensorFlow Lite, vous devez utiliser CocoaPods. Si ce n'est pas déjà fait, suivez les instructions de la page https://cocoapods.org/.

  1. Une fois que CocoaPods est installé, créez un fichier portant le nom Podfile dans le même répertoire que .xcproject pour l'application TextClassification. Le contenu de ce fichier doit se présenter comme suit:
target 'TextClassificationStep2' do
  use_frameworks!

  # Pods for NLPClassifier
    pod 'TensorFlowLiteSwift'

end

Indiquez le nom de votre application sur la première ligne et non dans "TextClassificationStep2".

À l'aide de Terminal, accédez à ce répertoire et exécutez pod install. Si c'est le cas, vous disposez d'un répertoire nommé Pods et d'un fichier .xcworkspace créé pour vous. Vous l'utiliserez à l'avenir au lieu de .xcproject.

En cas d'échec, assurez-vous que le fichier Podfile se trouve dans le répertoire où se trouvait .xcproject. Le fichier Podfile dans le mauvais répertoire ou le mauvais nom de cible est généralement le principal problème.

8. Ajouter les fichiers de modèle et de vol

Lorsque vous avez créé le modèle avec TensorFlow Lite Model Maker, vous pouvez le produire (model.tflite) et le vocabulaire (vocab.txt).

  1. Ajoutez-les à votre projet en les faisant glisser depuis le Finder vers la fenêtre de votre projet. Assurez-vous que l'option Ajouter aux cibles est cochée:

1ee9eaa00ee79859.png

Lorsque vous avez terminé, elles doivent s'afficher dans votre projet.

B63502b23911fd42.png

  1. Vérifiez qu'ils ont bien été ajoutés au package (afin d'être déployés sur un appareil) en sélectionnant votre projet (l'icône bleue est représentée sur la capture d'écran ci-dessus).Étape de classification de texte 2 ) et en regardant lePhases de compilation onglet:

20b7cb603d49b457.png

9. Charger le Vocabulaire

Lors de la classification du TLN, le modèle est entraîné avec des mots encodés en vecteurs. Le modèle encode des mots avec un ensemble spécifique de noms et de valeurs qui sont appris lors de l'entraînement du modèle. Veuillez noter que la plupart des modèles auront des vocabulaires différents. Il est donc important de vous en servir pour votre modèle généré au moment de l'entraînement. Il s'agit du fichier vocab.txt que vous venez d'ajouter à votre application.

Vous pouvez ouvrir le fichier dans Xcode pour afficher les encodages. Les mots tels que "titre" sont encodés en 6 et "love" en 12. L'ordre est l'ordre du nombre d'expositions. Ainsi, "I" était le mot le plus courant dans l'ensemble de données, suivi de "check".

Lorsque l'utilisateur saisit des mots, vous devez les coder avec ce vocabulaire avant de les envoyer au modèle afin de les classer.

Examinons ce code. Commencez par charger le vocabulaire.

  1. Définissez une variable de niveau classe pour stocker le dictionnaire:
var words_dictionary = [String : Int]()
  1. Créez ensuite un func dans la classe pour charger le vocabulaire dans ce dictionnaire:
func loadVocab(){
    // This func will take the file at vocab.txt and load it into a has table
    // called words_dictionary. This will be used to tokenize the words before passing them
    // to the model trained by TensorFlow Lite Model Maker
    if let filePath = Bundle.main.path(forResource: "vocab", ofType: "txt") {
        do {
            let dictionary_contents = try String(contentsOfFile: filePath)
            let lines = dictionary_contents.split(whereSeparator: \.isNewline)
            for line in lines{
                let tokens = line.components(separatedBy: " ")
                let key = String(tokens[0])
                let value = Int(tokens[1])
                words_dictionary[key] = value
            }
        } catch {
            print("Error vocab could not be loaded")
        }
    } else {
        print("Error -- vocab file not found")

    }
}
  1. Vous pouvez l'exécuter en l'appelant depuis viewDidLoad:
override func viewDidLoad() {
    super.viewDidLoad()
    txtInput.delegate = self
    loadVocab()
}

10. Transformer une chaîne en séquence de jetons

Vos utilisateurs saisiront des mots sous forme de phrase, qui deviendra une chaîne. Chaque mot de la phrase, s'il est présent dans le dictionnaire, est encodé dans la valeur de clé du mot, telle que définie dans le vocabulaire.

Un modèle de TLN accepte généralement une longueur de séquence fixe. Il existe des exceptions aux modèles créés à l'aide de ragged tensors, mais dans la plupart des cas, elles seront corrigées. Lorsque vous avez créé votre modèle, vous avez spécifié cette longueur. Veillez à utiliser la même durée dans votre application iOS.

La valeur par défaut dans Colab for TensorFlow Lite Model Maker que vous avez utilisée précédemment est de 20. Configurez-la également ici:

let SEQUENCE_LENGTH = 20

Ajoutez l'élément func suivant, qui permet de convertir la chaîne en minuscules et d'éliminer les signes de ponctuation:

func convert_sentence(sentence: String) -> [Int32]{
// This func will split a sentence into individual words, while stripping punctuation
// If the word is present in the dictionary it's value from the dictionary will be added to
// the sequence. Otherwise we'll continue

// Initialize the sequence to be all 0s, and the length to be determined
// by the const SEQUENCE_LENGTH. This should be the same length as the
// sequences that the model was trained for
  var sequence = [Int32](repeating: 0, count: SEQUENCE_LENGTH)
  var words : [String] = []
  sentence.enumerateSubstrings(
    in: sentence.startIndex..<sentence.endIndex,options: .byWords) {
            (substring, _, _, _) -> () in words.append(substring!) }
  var thisWord = 0
  for word in words{
    if (thisWord>=SEQUENCE_LENGTH){
      break
    }
    let seekword = word.lowercased()
    if let val = words_dictionary[seekword]{
      sequence[thisWord]=Int32(val)
      thisWord = thisWord + 1
    }
  }
  return sequence
}

Notez que la séquence correspond à des valeurs de type int32. C'est délibérément choisi, car en ce qui concerne la transmission de valeurs à TensorFlow Lite, vous allez gérer la mémoire de bas niveau, et TensorFlow Lite traite les entiers d'une séquence de chaînes comme des entiers de 32 bits. Cela vous simplifiera la vie (un peu) en ce qui concerne la transmission de chaînes au modèle.

11. Classification

Pour classer une phrase, elle doit d'abord être convertie en séquence de jetons en fonction des mots de la phrase. Cette opération a été effectuée à l'étape 9.

À présent, vous allez transmettre la phrase et la transmettre au modèle, le faire inférence, puis analyser les résultats.

Vous utiliserez l'interpréteur TensorFlow Lite, que vous devrez importer:

import TensorFlowLite

Commencez par une func qui prend votre séquence, qui est un tableau de types Int32:

func classify(sequence: [Int32]){
  // Model Path is the location of the model in the bundle
  let modelPath = Bundle.main.path(forResource: "model", ofType: "tflite")
  var interpreter: Interpreter
  do{
    interpreter = try Interpreter(modelPath: modelPath!)
  } catch _{
    print("Error loading model!")
    return
  }

Cela permettra de charger le fichier de modèle à partir du package et d'appeler un interpréteur avec celui-ci.

L'étape suivante consiste à copier la mémoire sous-jacente stockée dans la séquence dans un tampon appelé myData, afin de le transmettre à un Tensor. Lorsque vous avez implémenté le pod TensorFlow Lite, ainsi que l'interpréteur, vous avez accès à un type de Tensor.

Démarrez le code comme suit (toujours dans la classification func):

let tSequence = Array(sequence)
let myData = Data(copyingBufferOf: tSequence.map { Int32($0) })
let outputTensor: Tensor

Ne vous inquiétez pas si vous obtenez une erreur sur copyingBufferOf. Cette option sera implémentée en tant qu'extension plus tard.

Vous devez maintenant allouer des Tensors à l'interpréteur : copiez le tampon de données que vous venez de créer dans le Tensor d'entrée, puis appelez l'interpréteur pour effectuer l'inférence :

do {
  // Allocate memory for the model's input `Tensor`s.
  try interpreter.allocateTensors()

  // Copy the data to the input `Tensor`.
  try interpreter.copy(myData, toInputAt: 0)

  // Run inference by invoking the `Interpreter`.
  try interpreter.invoke()

Une fois l'appel terminé, vous pouvez consulter le résultat de l'interpréteur pour afficher les résultats.

Il s'agit de valeurs brutes (4 octets par neurone), que vous devrez lire et convertir. Étant donné que ce modèle possède 2 neurons de sortie, vous devrez lire 8 octets qui seront convertis en Float32 pour les analyser. Vous gérez un manque de mémoire, d'où la valeur unsafeData.

// Get the output `Tensor` to process the inference results.
outputTensor = try interpreter.output(at: 0)
// Turn the output tensor into an array. This will have 2 values
// Value at index 0 is the probability of negative sentiment
// Value at index 1 is the probability of positive sentiment
let resultsArray = outputTensor.data
let results: [Float32] = [Float32](unsafeData: resultsArray) ?? []

Il est désormais facile d'analyser les données pour déterminer la qualité du spam. Le modèle comporte deux sorties : la première correspond à la probabilité que le message ne soit pas un spam et la seconde à la probabilité qu'elle soit identifiée. Pour trouver la valeur antispam, consultez results[1] :

let positiveSpamValue = results[1]
var outputString = ""
if(positiveSpamValue>0.8){
    outputString = "Message not sent. Spam detected with probability: " + String(positiveSpamValue)
} else {
    outputString = "Message sent!"
}
txtOutput.text = outputString

Pour plus de commodité, voici la méthode complète:

func classify(sequence: [Int32]){
  // Model Path is the location of the model in the bundle
  let modelPath = Bundle.main.path(forResource: "model", ofType: "tflite")
  var interpreter: Interpreter
  do{
    interpreter = try Interpreter(modelPath: modelPath!)
    } catch _{
      print("Error loading model!")
      Return
  }

  let tSequence = Array(sequence)
  let myData = Data(copyingBufferOf: tSequence.map { Int32($0) })
  let outputTensor: Tensor
  do {
    // Allocate memory for the model's input `Tensor`s.
    try interpreter.allocateTensors()

    // Copy the data to the input `Tensor`.
    try interpreter.copy(myData, toInputAt: 0)

    // Run inference by invoking the `Interpreter`.
    try interpreter.invoke()

    // Get the output `Tensor` to process the inference results.
    outputTensor = try interpreter.output(at: 0)
    // Turn the output tensor into an array. This will have 2 values
    // Value at index 0 is the probability of negative sentiment
    // Value at index 1 is the probability of positive sentiment
    let resultsArray = outputTensor.data
    let results: [Float32] = [Float32](unsafeData: resultsArray) ?? []

    let positiveSpamValue = results[1]
    var outputString = ""
    if(positiveSpamValue>0.8){
      outputString = "Message not sent. Spam detected with probability: " +
                      String(positiveSpamValue)
    } else {
      outputString = "Message sent!"
    }
    txtOutput.text = outputString

  } catch let error {
    print("Failed to invoke the interpreter with error: \(error.localizedDescription)")
  }
}

12. Ajouter les extensions Swift

Le code ci-dessus utilisait une extension du type de données pour vous permettre de copier les bits bruts d'un tableau Int32 dans un Data. Voici le code pour cette extension:

extension Data {
  /// Creates a new buffer by copying the buffer pointer of the given array.
  ///
  /// - Warning: The given array's element type `T` must be trivial in that it can be copied bit
  ///     for bit with no indirection or reference-counting operations; otherwise, reinterpreting
  ///     data from the resulting buffer has undefined behavior.
  /// - Parameter array: An array with elements of type `T`.
  init<T>(copyingBufferOf array: [T]) {
    self = array.withUnsafeBufferPointer(Data.init)
  }
}

Lorsque vous gérez la mémoire de faible niveau, vous utilisez des données "non sécurisées". Le code ci-dessus nécessite d'initialiser un tableau de données non sécurisées. Grâce à cette extension, vous pouvez:

extension Array {
  /// Creates a new array from the bytes of the given unsafe data.
  ///
  /// - Warning: The array's `Element` type must be trivial in that it can be copied bit for bit
  ///     with no indirection or reference-counting operations; otherwise, copying the raw bytes in
  ///     the `unsafeData`'s buffer to a new array returns an unsafe copy.
  /// - Note: Returns `nil` if `unsafeData.count` is not a multiple of
  ///     `MemoryLayout<Element>.stride`.
  /// - Parameter unsafeData: The data containing the bytes to turn into an array.
  init?(unsafeData: Data) {
    guard unsafeData.count % MemoryLayout<Element>.stride == 0 else { return nil }
    #if swift(>=5.0)
    self = unsafeData.withUnsafeBytes { .init($0.bindMemory(to: Element.self)) }
    #else
    self = unsafeData.withUnsafeBytes {
      .init(UnsafeBufferPointer<Element>(
        start: $0,
        count: unsafeData.count / MemoryLayout<Element>.stride
      ))
    }
    #endif  // swift(>=5.0)
  }
}

13. Exécuter l'application iOS

Exécutez et testez l'application.

Si tout s'est déroulé correctement, vous devriez voir l'application sur votre appareil comme suit:

74cbd28d9b15926.png

Où le message "Achetez mon livre pour en savoir plus sur le commerce en ligne" a été envoyée, l'application renvoie une alerte de spam détectée avec une probabilité de 0,99 %.

14. Félicitations !

Vous venez de créer une application très simple qui filtre le texte pour signaler le spam à l'aide d'un modèle entraîné sur les données utilisées pour envoyer du spam aux blogs.

L'étape suivante du cycle de vie habituel des développeurs est de découvrir ce qu'il faudrait pour personnaliser le modèle en fonction des données de votre propre communauté. Vous apprendrez comment procéder dans l'activité suivante du chemin.