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 "Premiers pas avec la classification de texte sur mobile".

Prérequis

  • Cet atelier de programmation s'adresse aux développeurs expérimentés qui débutent avec le machine learning.
  • L'atelier de programmation fait partie d'un parcours séquentiel. Si vous n'avez pas encore suivi le cours "Créer une application de messagerie de base" ou "Créer un modèle de machine learning pour le spam dans les commentaires", veuillez arrêter pour le faire maintenant.

Ce que vous allez [construire ou apprendre]

  • Vous découvrirez comment intégrer votre modèle personnalisé créé lors des étapes précédentes dans votre application.

Prérequis

2. Ouvrir l'application Android existante

Pour obtenir le code nécessaire, suivez l'atelier de programmation 1, ou clonez ce dépôt et chargez l'application à partir de TextClassificationStep1.

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

Vous le trouverez dans le chemin d'accès TextClassificationOnMobile->Android.

Le code finished est également disponible en tant que TextClassificationStep2.

Une fois qu'elle est ouverte, vous pouvez passer à l'étape 2.

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

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

Vous devez avoir téléchargé le fichier de modèle. Si vous ne l'avez pas, vous pouvez le récupérer dans le dépôt de cet atelier de programmation. Le modèle est disponible sur cette page.

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

  1. À l'aide du navigateur de projets, assurez-vous que Android est sélectionné en haut de la page.
  2. Effectuez un clic droit sur le dossier app. Sélectionnez Nouveau > Annuaire.

d7c3e9f21035fc15.png

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

2137f956a1ba4ef0.png

Vous constaterez qu'un nouveau dossier assets est désormais disponible dans l'application.

ae858835e1a90445.png

  1. Effectuez un clic droit sur Composants.
  2. Dans le menu qui s'ouvre, l'option Révéler dans le Finder (sur Mac) s'affiche. Sélectionnez-la. (Sous Windows, il s'agit de l'option Afficher dans l'Explorateur. Sous Ubuntu, l'option est Afficher dans les fichiers.)

e61aaa3b73c5ab68.png

Le Finder est lancé pour afficher l'emplacement des fichiers (Explorateur de fichiers sous Windows et Fichiers sous Linux).

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

14f382cc19552a56.png

  1. Retournez dans Android Studio. Ils sont disponibles dans votre dossier assets.

150ed2a1d2f7a10d.png

4. Mettre à jour le fichier 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 comportent souvent plusieurs projets. Assurez-vous donc de trouver le niveau 1 correspondant à l'application. Dans l'explorateur de projets de la vue Android, accédez à la section Gradle Scripts (Scripts Gradle). L'application correcte sera libellée .app, comme dans l'exemple ci-dessous:

6426051e614bc42f.png

Vous devez apporter deux modifications à ce fichier. La première se trouve dans la section dépendances en bas de la page. Ajoutez du texte implementation pour la bibliothèque de tâches TensorFlow Lite, comme ceci:

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

Il est possible que le numéro de version ait changé depuis que vous avez écrit ce code. Consultez https://www.tensorflow.org/lite/inference_with_metadata/task_library/nl_classifier pour obtenir les dernières informations.

Les bibliothèques de tâches nécessitent également une version minimale du SDK 21. Vous trouverez ce paramètre dans android > default config, puis remplacez-la par 21:

c100b68450b8812f.png

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

5. Ajouter une classe Helper

Pour séparer la logique d'inférence, où votre application utilise le modèle, de l'interface utilisateur, créez une autre classe pour gérer l'inférence du modèle. Appeler cela un "assistant" .

  1. Effectuez un clic droit sur le nom du package contenant votre code MainActivity.
  2. Sélectionnez Nouveau > Package.

d5911ded56b5df35.png

  1. Une boîte de dialogue s'affiche au centre de l'écran et vous invite à saisir le nom du package. Ajoutez-le à la fin du nom du package actuel. (ici, elle s'appelle helpers).

3b9f1f822f99b371.png

  1. Une fois cette opération effectuée, effectuez un clic droit sur le dossier helpers dans l'explorateur de projets.
  2. Sélectionnez Nouveau > Classe Java et appelez-la TextClassificationClient. Vous modifierez le fichier à l'étape suivante.

Votre classe d'assistance TextClassificationClient se présente comme suit (bien que le nom de votre package puisse être différent).

package com.google.devrel.textclassificationstep1.helpers;

public class TextClassificationClient {
}
  1. Remplacez le fichier par 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 fournit un wrapper à l'interpréteur TensorFlow Lite, charge le modèle et élimine la complexité liée à la gestion de l'échange de données entre votre application et le modèle.

Dans la méthode load(), un nouveau type NLClassifier est instancié à partir du chemin d'accès du modèle. Le chemin d'accès au modèle correspond simplement au nom du modèle, model.tflite. Le type NLClassifier fait partie des bibliothèques de tâches textuelles et vous aide à convertir votre chaîne en jetons, à utiliser la longueur de séquence correcte, à la transmettre au modèle et à analyser les résultats.

(Pour en savoir plus, consultez à nouveau "Créer un modèle de machine learning pour le spam dans les commentaires".)

La classification s'effectue dans la méthode "classifier", dans laquelle vous lui transmettez une chaîne, qui renvoie un List. Lorsque vous utilisez des modèles de machine learning pour classer du contenu afin de déterminer si une chaîne est un spam ou non, 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 ressemblant à du spam, vous obtenez une liste de 2 réponses en retour : l'un avec la probabilité qu'il s'agisse d'un spam et l'autre avec la probabilité que ce ne soit pas le cas. Les éléments "Spam" et "Non spam" sont des catégories. Le List renvoyé contiendra donc ces probabilités. Vous l'analyserez plus tard.

Maintenant que vous disposez de la classe d'assistance, revenez à votre MainActivity et mettez-la à jour afin de l'utiliser pour 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, avec les autres importations, ajoutez:
import com.google.devrel.textclassificationstep2.helpers.TextClassificationClient
import org.tensorflow.lite.support.label.Category
  1. Ensuite, vous devez charger les assistants. Dans onCreate, juste après la ligne setContentView, ajoutez les lignes suivantes pour instancier et charger la classe d'assistance:
val client = TextClassificationClient(applicationContext)
client.load()

À l'heure actuelle, le onClickListener de votre bouton devrait se présenter comme suit:

btnSendText.setOnClickListener {
     var toSend:String = txtInput.text.toString()
     txtOutput.text = toSend
 }
  1. Modifiez-la 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()
}

La fonctionnalité ne se contente plus de générer l'entrée utilisateur, mais elle est classée en premier.

  1. Avec cette ligne, vous allez prendre la chaîne saisie par l'utilisateur et la transmettre au modèle, ce qui obtiendra les 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 : "False" correspond donc à l'élément 0 et "True" à l'élément 1).

  1. Pour obtenir le score de la probabilité que la valeur soit True, vous pouvez examiner les résultats[1].score comme suit:
    val score = results[1].score
  1. Vous avez choisi une valeur seuil (dans ce cas, 0,8), où vous indiquez que si le score de la catégorie "True" est supérieur à la valeur seuil (0,8), le message est un spam. Sinon, il ne s'agit pas d'un 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. Le message "Visitez mon blog pour faire des achats !" a été signalé comme présentant un risque élevé de spam:

1fb0b5de9e566e.png

Et inversement, "C'est un super tutoriel, merci !" dont la probabilité d'être du spam était très faible:

73f38bdb488b29b3.png

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

Pour obtenir le code nécessaire, suivez l'atelier de programmation 1, ou clonez ce dépôt et chargez l'application à partir de TextClassificationStep1. Vous le trouverez dans le chemin d'accès TextClassificationOnMobile->iOS.

Le code finished est également disponible en tant que TextClassificationStep2.

Dans l'atelier de programmation "Créer un modèle de machine learning pour le spam dans les commentaires", vous avez créé une application très simple permettant à l'utilisateur de saisir un message dans un UITextView et de le transmettre à une sortie sans filtrage.

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 dans le texte avant de l'envoyer. Il vous suffit de simuler l'envoi dans cette application en affichant le texte dans une étiquette de sortie (mais une application réelle peut comporter un tableau d'affichage, un chat ou autre chose similaire).

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

Pour intégrer TensorFlow Lite, vous allez utiliser CocoaPods. Si vous ne les avez pas encore installés, suivez les instructions disponibles sur https://cocoapods.org/.

  1. Une fois CocoaPods installé, créez un fichier nommé Podfile dans le même répertoire que le .xcproject de 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

Le nom de votre application doit figurer sur la première ligne, et non "TextClassificationStep2".

À l'aide de Terminal, accédez à ce répertoire et exécutez pod install. Si l'opération réussit, vous disposerez d'un nouveau répertoire appelé Pods et d'un fichier .xcworkspace créé automatiquement. Vous l'utiliserez à la place de .xcproject.

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

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

Lorsque vous avez créé le modèle avec TensorFlow Lite Model Maker, vous avez pu générer le modèle (au format model.tflite) et le vocabulaire (au format vocab.txt).

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

1ee9eaa00ee79859.png

Une fois que vous avez terminé, vous devriez les voir dans votre projet:

b63502b23911fd42.png

  1. Vérifiez qu'ils ont bien été ajoutés au bundle (afin qu'ils soient déployés sur un appareil) en sélectionnant votre projet (dans la capture d'écran ci-dessus, il s'agit de l'icône bleue TextClassificationStep2), puis en consultant l'onglet Build Phases (Phases de compilation) :

20b7cb603d49b457.png

9. Charger le vocabulaire

Lors de la classification 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. Notez que la plupart des modèles ont des vocabulaires différents. Il est donc important que vous utilisiez le vocabulaire généré lors de l'entraînement pour votre modèle. Il s'agit du fichier vocab.txt que vous venez d'ajouter à votre application.

Vous pouvez ouvrir le fichier dans Xcode pour voir les encodages. Mots tels que "chanson" sont encodés avec le chiffre 6 et le mot "love" à 12. L'ordre est en fait un ordre de fréquence : "I" était le mot le plus courant dans le jeu de données, suivi de « vérifier ».

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

Explorons ce code. Commencez par charger le vocabulaire.

  1. Définissez une variable au niveau de la 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 une séquence de jetons

Vos utilisateurs saisiront les 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 clé du mot tel que défini dans le vocabulaire.

Un modèle de TLN accepte généralement une longueur de séquence fixe. Il existe des exceptions avec les modèles créés à l'aide de ragged tensors, mais dans la plupart des cas, vous constaterez que le problème est résolu. Vous avez spécifié cette longueur lors de la création de votre modèle. Veillez à utiliser la même durée dans votre application iOS.

La valeur par défaut de 20 dans le Colab pour TensorFlow Lite Model Maker utilisé précédemment était 20. Configurez-la ici également:

let SEQUENCE_LENGTH = 20

Ajoutez le code func qui récupère la chaîne, la convertit en minuscules et supprime toute 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 sera de Int32. Ce choix est délibéré, car lorsqu'il s'agit de transmettre des valeurs à TensorFlow Lite, vous avez affaire à une 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 facilitera (un peu) le processus de transmission des chaînes au modèle.

11. Effectuer la classification

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

À présent, vous allez transmettre la phrase au modèle, lui demander d'effectuer des inférences et d'analyser les résultats.

Cette opération utilisera l'interpréteur TensorFlow Lite, que vous devrez importer:

import TensorFlowLite

Commencez par un func qui reçoit votre séquence, qui était 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
  }

Le fichier de modèle à partir du bundle est alors chargé et un interpréteur est appelé.

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

Commencez le code comme suit (toujours dans le func de classification):

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

Ne vous inquiétez pas si une erreur s'affiche sur copyingBufferOf. Elle sera implémentée ultérieurement en tant qu'extension.

Il est maintenant temps d'allouer des Tensors à l'interpréteur, de copier le tampon de données que vous venez de créer dans le Tensor d'entrée, puis d'appeler 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 la sortie de l'interpréteur pour voir les résultats.

Il s'agit de valeurs brutes (4 octets par neurone) que vous devrez lire et convertir. Comme ce modèle comporte deux neurones de sortie, vous devez lire 8 octets qui seront convertis en Float32 pour l'analyse. Vous avez affaire à une mémoire de bas niveau, d'où le 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 relativement facile d'analyser les données pour déterminer la qualité du spam. Le modèle comporte deux sorties : la première avec la probabilité que le message ne soit pas un spam, la seconde avec sa probabilité. Vous pouvez donc examiner results[1] pour trouver la valeur du spam:

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 utilise une extension du type Data pour vous permettre de copier les bits bruts d'un tableau Int32 dans un Data. Voici le code de 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)
  }
}

En cas de mémoire de bas niveau, vous utilisez le mot "non sécurisé" et que le code ci-dessus nécessite l'initialisation d'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é comme prévu, l'application devrait s'afficher sur votre appareil comme suit:

74cbd28d9b1592ed.png

Où le message "Acheter mon livre pour apprendre le trading en ligne !" a été envoyée, l'application renvoie une alerte de spam détecté avec une probabilité de 0,99 %.

14. Félicitations !

Vous venez de créer une application très simple qui filtre le texte en vue d'éliminer le spam dans les commentaires à l'aide d'un modèle entraîné sur des données utilisées pour spammer les blogs.

L'étape suivante du cycle de vie typique d'un développeur consiste à explorer ce qu'il faudrait pour personnaliser le modèle en fonction des données trouvées dans votre propre communauté. Vous découvrirez comment procéder dans la prochaine activité du parcours.