Zaktualizuj aplikację, aby używać modelu systemów uczących się do filtrowania spamu

1. Zanim zaczniesz

W tym ćwiczeniu z programowania zaktualizujesz aplikację utworzoną w poprzednich ćwiczeniach z programowania z serii „Rozpocznij korzystanie z mobilnej klasyfikacji tekstu”.

Wymagania wstępne

  • Te warsztaty zostały opracowane z myślą o doświadczonych programistach, którzy dopiero zaczynają przygodę z uczeniem maszynowym.
  • Codelab jest częścią ścieżki szkoleniowej. Jeśli nie masz jeszcze za sobą samouczków Tworzenie podstawowej aplikacji do przesyłania wiadomości i Tworzenie modelu systemu uczącego się do wykrywania spamu w komentarzach, przerwij i zrób to teraz.

Co [utworzysz lub czego się nauczysz]

  • Dowiesz się, jak zintegrować model niestandardowy z aplikacją utworzoną w poprzednich krokach.

Czego potrzebujesz

  • Android Studio lub CocoaPods w przypadku iOS.

2. Otwieranie istniejącej aplikacji na Androida

Kod możesz uzyskać, wykonując ćwiczenie 1, lub sklonować to repozytorium i wczytać aplikację z TextClassificationStep1.

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

Znajdziesz go na ścieżce TextClassificationOnMobile->Android.

Kod finished jest też dostępny jako TextClassificationStep2.

Gdy się otworzy, możesz przejść do kroku 2.

3. Importowanie pliku modelu i metadanych

W module Build a comment spam machine learning model (Tworzenie modelu systemu uczącego się do wykrywania spamu w komentarzach) utworzyliśmy model .TFLITE.

Plik modelu powinien zostać pobrany. Jeśli go nie masz, możesz go pobrać z repozytorium tego laboratorium. Model jest dostępny tutaj.

Dodaj go do projektu, tworząc katalog zasobów.

  1. W nawigatorze projektu sprawdź, czy u góry jest wybrana opcja Android.
  2. Kliknij prawym przyciskiem myszy folder app. Kliknij Nowy > Katalog.

d7c3e9f21035fc15.png

  1. W oknie New Directory (Nowy katalog) wybierz src/main/assets.

2137f956a1ba4ef0.png

W aplikacji pojawi się nowy folder assets.

ae858835e1a90445.png

  1. Kliknij prawym przyciskiem myszy zasoby.
  2. W menu, które się otworzy, zobaczysz (na komputerze Mac) Pokaż w Finderze. Wybierz ją. (W systemie Windows będzie to Pokaż w Eksploratorze, a w Ubuntu – Pokaż w plikach).

e61aaa3b73c5ab68.png

Uruchomi się Finder, który wyświetli lokalizację plików (Eksplorator plików w systemie Windows, Pliki w systemie Linux).

  1. Skopiuj do tego katalogu pliki labels.txt, model.tflitevocab.

14f382cc19552a56.png

  1. Wróć do Android Studio. Zobaczysz, że są dostępne w folderze assets.

150ed2a1d2f7a10d.png

4. Aktualizacja pliku build.gradle w celu używania TensorFlow Lite

Aby korzystać z TensorFlow Lite i bibliotek zadań TensorFlow Lite, które go obsługują, musisz zaktualizować plik build.gradle.

Projekty na Androida często mają więcej niż jeden plik build.gradle, więc znajdź ten na poziomie aplikacji. W eksploratorze projektu w widoku Androida znajdź go w sekcji Skrypty Gradle. Prawidłowa wersja będzie oznaczona etykietą .app, jak pokazano poniżej:

6426051e614bc42f.png

W tym pliku musisz wprowadzić 2 zmiany. Pierwsza znajduje się na dole w sekcji dependencies. Dodaj tekst implementation do biblioteki zadań TensorFlow Lite, np. tak:

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

Numer wersji mógł się zmienić od czasu napisania tego artykułu, więc sprawdź najnowszą wersję na stronie https://www.tensorflow.org/lite/inference_with_metadata/task_library/nl_classifier.

Biblioteki zadań wymagają też minimalnej wersji pakietu SDK 21. To ustawienie znajdziesz w sekcji android > default config. Zmień je na 21:

c100b68450b8812f.png

Masz już wszystkie zależności, więc możesz zacząć pisać kod.

5. Dodawanie klasy pomocniczej

Aby oddzielić logikę wnioskowania, w której aplikacja korzysta z modelu, od interfejsu użytkownika, utwórz inną klasę do obsługi wnioskowania modelu. Nazwij ją klasą „pomocniczą”.

  1. Kliknij prawym przyciskiem myszy nazwę pakietu, w którym znajduje się kod MainActivity.
  2. Kliknij Nowy > Pakiet.

d5911ded56b5df35.png

  1. Na środku ekranu pojawi się okno z prośbą o wpisanie nazwy pakietu. Dodaj ją na końcu bieżącej nazwy pakietu. (W tym przypadku są to pomocnicy).

3b9f1f822f99b371.png

  1. Gdy to zrobisz, kliknij prawym przyciskiem myszy folder helpers w eksploratorze projektu.
  2. Wybierz New > Java Class i nadaj mu nazwę TextClassificationClient. W następnym kroku zmodyfikujesz ten plik.

Twoja TextClassificationClientklasa pomocnicza będzie wyglądać tak (chociaż nazwa pakietu może być inna):

package com.google.devrel.textclassificationstep1.helpers;

public class TextClassificationClient {
}
  1. Zaktualizuj plik za pomocą tego kodu:
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;
    }

}

Ta klasa będzie zawierać otokę interpretera TensorFlow Lite, która wczytuje model i ukrywa złożoność zarządzania wymianą danych między aplikacją a modelem.

W metodzie load() utworzy nową instancję typu NLClassifier na podstawie ścieżki modelu. Ścieżka modelu to po prostu nazwa modelu, model.tflite. Typ NLClassifier jest częścią bibliotek zadań tekstowych. Pomaga on w przekształcaniu ciągu znaków w tokeny, używaniu prawidłowej długości sekwencji, przekazywaniu jej do modelu i parsowaniu wyników.

(Więcej informacji na ten temat znajdziesz w artykule Tworzenie modelu systemu uczącego się do wykrywania spamu w komentarzach).

Klasyfikacja jest przeprowadzana w metodzie classify, do której przekazujesz ciąg znaków, a ona zwraca obiekt List. Gdy używasz modeli uczenia maszynowego do klasyfikowania treści, w przypadku których chcesz określić, czy ciąg znaków jest spamem, czy nie, zwykle zwracane są wszystkie odpowiedzi z przypisanymi prawdopodobieństwami. Jeśli na przykład przekażesz mu wiadomość, która wygląda jak spam, otrzymasz listę 2 odpowiedzi: jedną z prawdopodobieństwem, że jest to spam, a drugą z prawdopodobieństwem, że nie jest to spam. Spam/Nie spam to kategorie, więc zwrócona wartość List będzie zawierać te prawdopodobieństwa. Zrobisz to później.

Teraz, gdy masz już klasę pomocniczą, wróć do MainActivity i zaktualizuj ją, aby używać jej do klasyfikowania tekstu. Zobaczysz to w następnym kroku.

6. Klasyfikowanie tekstu

MainActivity najpierw zaimportuj utworzone przed chwilą funkcje pomocnicze.

  1. U góry pliku MainActivity.kt dodaj te instrukcje importu:
import com.google.devrel.textclassificationstep2.helpers.TextClassificationClient
import org.tensorflow.lite.support.label.Category
  1. Następnie wczytaj pomocników. W onCreate, bezpośrednio po wierszu setContentView, dodaj te wiersze, aby utworzyć instancję klasy pomocniczej i ją wczytać:
val client = TextClassificationClient(applicationContext)
client.load()

Obecnie przycisk onClickListener powinien wyglądać tak:

btnSendText.setOnClickListener {
     var toSend:String = txtInput.text.toString()
     txtOutput.text = toSend
 }
  1. Zmień go tak, aby wyglądał tak:
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()
}

Zmienia to funkcję z zwracania danych wejściowych użytkownika na ich klasyfikowanie.

  1. Ten wiersz kodu pobiera ciąg znaków wpisany przez użytkownika i przekazuje go do modelu, który zwraca wyniki:
var results:List<Category> = client.classify(toSend)

Istnieją tylko 2 kategorie: FalseTrue.

. (TensorFlow sortuje je alfabetycznie, więc False będzie elementem 0, a True – elementem 1).

  1. Aby uzyskać wynik prawdopodobieństwa, że wartość to True, możesz sprawdzić wartość results[1].score w ten sposób:
    val score = results[1].score
  1. Wybrana wartość progowa (w tym przypadku 0,8), która oznacza, że jeśli wynik dla kategorii „Prawda” jest powyżej wartości progowej (0,8), wiadomość jest spamem. W przeciwnym razie nie jest to spam i wiadomość można bezpiecznie wysłać:
    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. Zobacz model w akcji Wiadomość „Odwiedź mojego bloga, aby kupić produkty!” została oznaczona jako wysoce prawdopodobny spam:

1fb0b5de9e566e.png

Z kolei komentarz „Hej, fajny samouczek, dzięki!” miał bardzo niskie prawdopodobieństwo bycia spamem:

73f38bdb488b29b3.png

7. Aktualizowanie aplikacji na iOS, aby korzystać z modelu TensorFlow Lite

Kod możesz uzyskać, wykonując ćwiczenie 1, lub sklonować to repozytorium i wczytać aplikację z TextClassificationStep1. Znajdziesz go na ścieżce TextClassificationOnMobile->iOS.

Kod finished jest też dostępny jako TextClassificationStep2.

W samouczku Tworzenie modelu uczenia maszynowego do wykrywania spamu w komentarzach utworzyliśmy bardzo prostą aplikację, która umożliwiała użytkownikowi wpisanie wiadomości w UITextView i przekazanie jej do wyjścia bez filtrowania.

Teraz zaktualizujesz tę aplikację, aby używała modelu TensorFlow Lite do wykrywania spamu w komentarzach w tekście przed jego wysłaniem. W tej aplikacji wystarczy symulować wysyłanie, renderując tekst w etykiecie wyjściowej (ale prawdziwa aplikacja może mieć tablicę ogłoszeń, czat lub coś podobnego).

Na początek potrzebujesz aplikacji z kroku 1, którą możesz sklonować z repozytorium.

Aby zintegrować TensorFlow Lite, użyjesz CocoaPods. Jeśli nie masz jeszcze tych narzędzi, możesz je zainstalować, postępując zgodnie z instrukcjami na stronie https://cocoapods.org/.

  1. Po zainstalowaniu CocoaPods utwórz plik o nazwie Podfile w tym samym katalogu co plik .xcproject aplikacji TextClassification. Zawartość tego pliku powinna wyglądać tak:
target 'TextClassificationStep2' do
  use_frameworks!

  # Pods for NLPClassifier
    pod 'TensorFlowLiteSwift'

end

W pierwszym wierszu powinna znajdować się nazwa aplikacji, a nie „TextClassificationStep2”.

Za pomocą terminala przejdź do tego katalogu i uruchom polecenie pod install. Jeśli się to uda, utworzony zostanie nowy katalog o nazwie Pods i nowy plik .xcworkspace. W przyszłości będziesz używać tego symbolu zamiast .xcproject.

Jeśli się nie udało, sprawdź, czy plik Podfile znajduje się w tym samym katalogu co .xcproject. Zwykle główną przyczyną jest plik Podfile w nieprawidłowym katalogu lub nieprawidłowa nazwa projektu.

8. Dodawanie plików modelu i słownika

Gdy tworzysz model za pomocą narzędzia TensorFlow Lite Model Maker, możesz wyeksportować model (jako model.tflite) i słownik (jako vocab.txt).

  1. Dodaj je do projektu, przeciągając je z Findera do okna projektu. Sprawdź, czy jest zaznaczone pole Dodaj do miejsc docelowych:

1ee9eaa00ee79859.png

Gdy skończysz, powinny być widoczne w projekcie:

b63502b23911fd42.png

  1. Sprawdź, czy zostały dodane do pakietu (aby można było je wdrożyć na urządzeniu). W tym celu wybierz projekt (na powyższym zrzucie ekranu jest to niebieska ikona TextClassificationStep2) i otwórz kartę Fazy kompilacji:

20b7cb603d49b457.png

9. Wczytywanie słownictwa

W przypadku klasyfikacji NLP model jest trenowany przy użyciu słów zakodowanych w wektorach. Model koduje słowa za pomocą określonego zestawu nazw i wartości, które są wyznaczane podczas trenowania modelu. Pamiętaj, że większość modeli ma różne słowniki. Ważne jest, aby używać słownika modelu, który został wygenerowany w momencie trenowania. To plik vocab.txt, który został właśnie dodany do aplikacji.

Aby zobaczyć kodowanie, możesz otworzyć plik w Xcode. Słowo „song” jest kodowane jako 6, a „love” jako 12. Kolejność jest w rzeczywistości kolejnością częstotliwości, więc „I” było najczęstszym słowem w zbiorze danych, a za nim było „check”.

Gdy użytkownik wpisze słowa, przed wysłaniem ich do modelu w celu klasyfikacji musisz je zakodować za pomocą tego słownika.

Przyjrzyjmy się temu kodowi. Zacznij od wczytania słownictwa.

  1. Zdefiniuj zmienną na poziomie klasy, aby przechowywać słownik:
var words_dictionary = [String : Int]()
  1. Następnie utwórz func w klasie, aby załadować słownictwo do tego słownika:
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. Możesz uruchomić tę funkcję, wywołując ją z poziomu viewDidLoad:
override func viewDidLoad() {
    super.viewDidLoad()
    txtInput.delegate = self
    loadVocab()
}

10. Przekształcanie ciągu w sekwencję tokenów

Użytkownicy będą wpisywać słowa w formie zdania, które stanie się ciągiem znaków. Każde słowo w zdaniu, jeśli występuje w słowniku, zostanie zakodowane w wartości klucza dla tego słowa zgodnie z definicją w słowniku.

Model NLP zwykle akceptuje sekwencje o stałej długości. Istnieją wyjątki w przypadku modeli utworzonych za pomocą ragged tensors, ale w większości przypadków jest on stały. Długość została określona podczas tworzenia modelu. Upewnij się, że w aplikacji na iOS używasz tej samej długości.

W Colab dla TensorFlow Lite Model Maker, którego używasz, domyślna wartość to 20, więc ustaw ją też tutaj:

let SEQUENCE_LENGTH = 20

Dodaj ten kod func, który pobierze ciąg tekstowy, przekonwertuje go na małe litery i usunie z niego znaki interpunkcyjne:

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
}

Pamiętaj, że sekwencja będzie zawierać wartości Int32. Zostało to celowo wybrane, ponieważ w przypadku przekazywania wartości do TensorFlow Lite będziesz mieć do czynienia z pamięcią niskiego poziomu, a TensorFlow Lite traktuje liczby całkowite w ciągu znaków jako 32-bitowe liczby całkowite. Ułatwi Ci to (nieco) przekazywanie ciągów znaków do modelu.

11. Przeprowadź klasyfikację

Aby sklasyfikować zdanie, musisz najpierw przekonwertować je na sekwencję tokenów na podstawie słów w zdaniu. Zostało to zrobione w kroku 9.

Teraz weź zdanie i przekaż je do modelu, aby przeprowadzić wnioskowanie i przeanalizować wyniki.

W tym celu użyjemy interpretera TensorFlow Lite, który musisz zaimportować:

import TensorFlowLite

Zacznij od func, która przyjmuje sekwencję, czyli tablicę typów 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
  }

Spowoduje to wczytanie pliku modelu z pakietu i wywołanie interpretera.

Następnym krokiem będzie skopiowanie pamięci bazowej przechowywanej w sekwencji do bufora o nazwie myData,, aby można było przekazać ją do tensora. Podczas wdrażania poda TensorFlow Lite, a także interpretera, uzyskujesz dostęp do typu tensora.

Zacznij kod w ten sposób (nadal w funkcji classify func):

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

Nie martw się, jeśli na stronie copyingBufferOf pojawi się błąd. Wdrożymy to później jako rozszerzenie.

Teraz możesz przydzielić tensory w interpreterze, skopiować utworzony właśnie bufor danych do tensora wejściowego, a następnie wywołać interpreter, aby przeprowadzić wnioskowanie:

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()

Po zakończeniu wywołania możesz sprawdzić dane wyjściowe interpretera, aby zobaczyć wyniki.

Będą to wartości surowe (4 bajty na neuron), które musisz odczytać i przekonwertować. Ten model ma 2 neurony wyjściowe, więc musisz odczytać 8 bajtów, które zostaną przekonwertowane na liczby zmiennoprzecinkowe 32-bitowe na potrzeby analizy. Masz do czynienia z pamięcią niskiego poziomu, stąd 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) ?? []

Teraz stosunkowo łatwo jest przeanalizować dane, aby określić jakość spamu. Model ma 2 wartości wyjściowe: pierwsza to prawdopodobieństwo, że wiadomość nie jest spamem, a druga to prawdopodobieństwo, że jest. Wartość spamu możesz znaleźć w 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

Dla ułatwienia podajemy pełną metodę:

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. Dodawanie rozszerzeń Swift

Powyższy kod używa rozszerzenia typu danych, aby umożliwić kopiowanie surowych bitów tablicy Int32 do Data. Oto kod tego rozszerzenia:

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)
  }
}

W przypadku pamięci niskiego poziomu używasz „niebezpiecznych” danych, a powyższy kod wymaga zainicjowania tablicy niebezpiecznych danych. To rozszerzenie umożliwia:

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. Uruchamianie aplikacji na iOS

Uruchom i przetestuj aplikację.

Jeśli wszystko przebiegło pomyślnie, aplikacja powinna wyglądać na urządzeniu tak:

74cbd28d9b1592ed.png

Gdy wiadomość „Kup moją książkę, aby nauczyć się handlu online!” zostanie wysłana, aplikacja zwraca alert o wykryciu spamu z prawdopodobieństwem 0,99.

14. Gratulacje!

Utworzyliśmy bardzo prostą aplikację, która filtruje tekst pod kątem spamu w komentarzach za pomocą modelu wytrenowanego na danych używanych do spamowania blogów.

Kolejnym krokiem w typowym cyklu życia dewelopera jest sprawdzenie, jak dostosować model na podstawie danych znalezionych we własnej społeczności. W następnym ćwiczeniu dowiesz się, jak to zrobić.