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 poprzednim ćwiczeniu z programowania z klasyfikacją tekstu na urządzeniu mobilnym.

Wymagania wstępne

  • To laboratorium programistyczne zostało przygotowane dla doświadczonych programistów, którzy dopiero zaczynają swoją przygodę z uczenie maszynowe.
  • Codelab jest częścią sekwencyjnego ścieżki. Jeśli nie masz jeszcze za sobą poprzedniego samouczka na temat tworzenia aplikacji w stylu podstawowej aplikacji do przesyłania wiadomości lub tworzenia modelu systemu uczącego się do wykrywania spamu w komentarzach, zrealizuj je teraz.

Czego się dowiesz?

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

Czego potrzebujesz

2. Otwórz istniejącą aplikację na Androida

Aby uzyskać odpowiedni kod, wykonaj instrukcje z Codelab 1 lub skopiuj to repozytorium i wczytaj aplikację ze strony TextClassificationStep1.

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

Znajdziesz ją na ścieżce TextClassificationOnMobile->Android.

Kod gotowy jest też dostępny jako TextClassificationStep2.

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

3. Importowanie pliku modelu i metadanych

W ramach ćwiczenia Create a comment spam machine learning model (Tworzenie modelu systemów uczących się do wykrywania spamu w komentarzach) utworzyłeś/utworzyłaś model .TFLITE.

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

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

  1. W nawigatorze projektów wybierz Android u góry.
  2. Kliknij prawym przyciskiem myszy folder aplikacji. Kliknij Nowy > Katalog.

d7c3e9f21035fc15.png

  1. W oknie Nowy katalog wybierz src/main/assets.

2137f956a1ba4ef0.png

Pojawi się nowy folder assets w aplikacji.

ae858835e1a90445.png

  1. Kliknij prawym przyciskiem myszy zasoby.
  2. W menu, które się pojawi, zobaczysz (na Macu) 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, aby wyświetlić lokalizację plików (Eksplorator plików w systemie Windows, Files w systemie Linux).

  1. Skopiuj do tego katalogu pliki labels.txt, model.tflite i vocab.

14f382cc19552a56.png

  1. Wróć do Android Studio, a pliki te będą dostępne w folderze assets.

150ed2a1d2f7a10d.png

4. Zaktualizuj plik build.gradle, aby używać TensorFlow Lite

Aby używać TensorFlow Lite i obsługujących go bibliotek zadań TensorFlow Lite, musisz zaktualizować plik build.gradle.

Projekty na Androida często mają więcej niż jeden projekt, dlatego pamiętaj, aby znaleźć pierwszą aplikację. W eksploratorze projektu w widoku Androida znajdź go w sekcji Skrypty Gradle. Prawidłowy plik będzie oznaczony jako .app, jak tutaj:

6426051e614bc42f.png

W tym pliku musisz wprowadzić 2 zmiany. Pierwszy znajduje się w sekcji zależność na dole. Dodaj tekst implementation dla biblioteki zadań TensorFlow Lite, na przykład:

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

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

Biblioteki zadań wymagają też co najmniej wersji 21 pakietu SDK. Znajdź to ustawienie w sekcji android > default config i zmień je na 21:

c100b68450b8812f.png

Masz już wszystkie zależności, więc czas zacząć kodować.

5. Dodaj klasę pomocniczą

Aby oddzielić logikę wnioskowania, w której aplikacja używa modelu, od interfejsu użytkownika, utwórz kolejną klasę do obsługi wnioskowania modelu. Nazwij to klasą „pomocną”.

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

d5911ded56b5df35.png

  1. W środku ekranu pojawi się okno z prośbą o wpisanie nazwy pakietu. Dodaj go na końcu bieżącej nazwy pakietu. (tutaj nazywa się helpers).

3b9f1f822f99b371.png

  1. Następnie w eksploratorze projektu kliknij prawym przyciskiem folder helpers.
  2. Kliknij Nowy > Klasa Java i nazwij ją TextClassificationClient. W następnym kroku zmodyfikujesz plik.

Twoja klasa pomocnicza TextClassificationClient będzie wyglądać tak (choć 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 udostępnia zewnętrzną implementację dla interpretera TensorFlow Lite, wczytuje model i ukrywa złożoność zarządzania wymianą danych między aplikacją a modelem.

W metodzie load() zostanie utworzony nowy typ 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 konwertować ciąg znaków na tokeny przy użyciu właściwej długości sekwencji, przekazywać go do modelu i analizować wyniki.

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

Klasyfikacja jest wykonywana w ramach metody classify, do której przekazujesz ciąg znaków. Metoda zwraca wartość List. Gdy używasz modeli systemów uczących się do klasyfikowania treści, aby określić, czy ciąg znaków jest spamem, zwykle zwracane są wszystkie odpowiedzi z przypisanymi prawdopodobieństwami. Jeśli na przykład przekażesz wiadomość, która wygląda jak spam, otrzymasz listę 2 odpowiedzi: jedną z prawdopodobieństwem, że jest to spam, i druga, która według Ciebie prawdopodobnie nie jest spamem. Elementy typu Spam/Nie spam to kategorie, więc zwrócone dane List będą zawierać te prawdopodobieństwa. Dowiesz się tego później.

Gdy już utworzysz klasę pomocniczą, wróć do funkcji MainActivity i zaktualizuj ją, aby wykorzystywać ją do klasyfikowania tekstu. W następnym kroku zobaczysz to.

6. Klasyfikowanie tekstu

W MainActivity musisz najpierw zaimportować utworzone przez siebie pomocnicze pliki.

  1. U góry MainActivity.kt, razem z innymi importami, dodaj:
import com.google.devrel.textclassificationstep2.helpers.TextClassificationClient
import org.tensorflow.lite.support.label.Category
  1. Teraz musisz wczytać pomocnicze pliki pomocniczych. W pliku onCreate, tuż po wierszu setContentView, dodaj te wiersze, aby utworzyć instancję klasy pomocniczej i ją załadować:
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 w ten sposób:
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()
}

Dzięki temu funkcja nie tylko wyświetla dane wejściowe użytkownika, ale najpierw je klasyfikuje.

  1. Dzięki temu wierszowi możesz pobrać ciąg znaków wpisany przez użytkownika i przekazać go do modelu, aby uzyskać wyniki:
var results:List<Category> = client.classify(toSend)

Są tylko 2 kategorie: False i True

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

  1. Aby uzyskać wynik prawdopodobieństwa, że wartość jest True, możesz sprawdzić element results[1].score w ten sposób:
    val score = results[1].score
  1. Wybrana wartość progowa (w tym przypadku 0,8), która określa, że jeśli wynik dla kategorii Prawda jest wyższy niż wartość progowa (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 praktyce. Wiadomość „Odwiedź mój blog, aby kupić coś dla siebie” została oznaczona jako spam:

1fb0b5de9e566e.png

Z kolei „Hej, fajny samouczek, dzięki!” został uznany za mało prawdopodobny spam:

73f38bdb488b29b3.png

7. Aktualizowanie aplikacji na iOS, aby używać modelu TensorFlow Lite

Aby uzyskać odpowiedni kod, wykonaj instrukcje z Codelab 1 lub skopiuj to repozytorium i wczytaj aplikację ze strony TextClassificationStep1. Możesz go znaleźć w ścieżce TextClassificationOnMobile->iOS.

Kod gotowy jest też dostępny jako TextClassificationStep2.

W ramach ćwiczenia w Codelab Tworzenie modelu uczenia maszynowego do wykrywania spamu w komentarzach utworzysz bardzo prostą aplikację, która pozwala użytkownikowi wpisać wiadomość w polu UITextView i przekazać ją do wyjścia bez żadnego filtrowania.

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

Aby rozpocząć, musisz skopiować aplikację z repozytorium z etapu 1.

Aby włączyć TensorFlow Lite, użyj CocoaPods. Jeśli nie masz jeszcze zainstalowanych tych pakietów, możesz to zrobić, postępując zgodnie z instrukcjami pod adresem https://cocoapods.org/.

  1. Po zainstalowaniu CocoaPods utwórz plik o nazwie Podfile w tym samym katalogu, w którym znajduje się plik .xcproject aplikacji TextClassification. Treść tego pliku powinna wyglądać tak:
target 'TextClassificationStep2' do
  use_frameworks!

  # Pods for NLPClassifier
    pod 'TensorFlowLiteSwift'

end

W pierwszym wierszu powinno znaleźć się nazwę aplikacji, a nie „TextClassificationStep2”.

Używając Terminala, przejdź do tego katalogu i uruchom pod install. Jeśli się powiedzie, utworzy się nowy katalog o nazwie Pods i nowy plik .xcworkspace. W przyszłości będziesz używać tego operatora zamiast operatora .xcproject.

Jeśli się nie powiedzie, sprawdź, czy plik Podfile znajduje się w tym samym katalogu, w którym był plik .xcproject. Zwykle główną przyczyną problemów jest plik podfile znajdujący się w niewłaściwym katalogu lub nieprawidłowa nazwa miejsca docelowego.

8. Dodawanie plików modelu i słownika

Po utworzeniu modelu za pomocą Kreatora modeli TensorFlow Lite można go wyświetlić (jako model.tflite) i wokab (jako vocab.txt).

  1. Dodaj je do projektu, przeciągając je z Findera do okna projektu. Upewnij się, że zaznaczone jest pole Dodaj do listy docelowych:

1ee9eaa00ee79859.png

Gdy skończysz, powinny pojawić się w projekcie:

b63502b23911fd42.png

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

20b7cb603d49b457.png

9. Wczytaj słownictwo

Podczas klasyfikacji NLP model jest trenowany za pomocą słów zakodowanych w wektory. Model koduje słowa za pomocą określonego zbioru nazw i wartości, które są uczone podczas trenowania modelu. Pamiętaj, że większość modeli ma różne słowniki, dlatego ważne jest, aby używać słownika dla modelu wygenerowanego podczas trenowania. To plik vocab.txt, który właśnie dodałeś/dodałaś do aplikacji.

Aby zobaczyć kodowanie, otwórz plik w Xcode. Słowa takie jak „piosenka” są kodowane na 6, a „miłość” na 12. Kolejność jest w rzeczywistości kolejność występowania, więc „I” było najczęstszym słowem w zbiorze danych, a za nim następowało „check”.

Gdy użytkownik wpisze słowa, przed wysłaniem ich do modelu do klasyfikacji należy je zakodować za pomocą tej słownictwa.

Przyjrzyjmy się temu kodowi. Zacznij od załadowania słownictwa.

  1. Zdefiniuj zmienną na poziomie klasy, w której będziesz przechowywać słownik:
var words_dictionary = [String : Int]()
  1. Następnie utwórz func na zajęciach, 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 wpisują słowa w postaci zdania, które staje się ciągiem znaków. Każde słowo w zrzucie, jeśli występuje w słowniku, zostanie zakodowane w kluczu wartości dla tego słowa zgodnie z definicją w słowniku.

Model NLP zwykle akceptuje stałą długość sekwencji. W przypadku modeli utworzonych za pomocą funkcji ragged tensors mogą wystąpić wyjątki, ale w większości przypadków jest to stała wartość. Podczas tworzenia modelu określono tę długość. Upewnij się, że w aplikacji na iOS używasz tej samej długości.

Domyślna wartość w narzędzie Colab do tworzenia modeli TensorFlow Lite, którego używaliśmy wcześniej, wynosiła 20, więc ustaw ją tutaj:

let SEQUENCE_LENGTH = 20

Dodaj funkcję func, która przyjmie ciąg znaków, przekształci go na małe litery i usunie wszystkie 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
}

Uwaga: sekwencja będzie miała typ Int32. Jest to celowo wybrane, ponieważ podczas przekazywania wartości do TensorFlow Lite będziesz mieć do czynienia z pamięcią niskiego poziomu, a TensorFlow Lite traktuje liczby całkowite w sekwencji ciągów jako 32-bitowe liczby całkowite. Ułatwi Ci to (trochę) przekazywanie ciągów znaków do modelu.

11. Klasyfikacja

Aby sklasyfikować zdanie, należy je najpierw przekształcić w sekwencję tokenów opartą na słowach w zdaniu. To zostało zrobione w kroku 9.

Teraz wystarczy wziąć zdania, przekazać je modelowi, skonfigurować wnioskowanie przez model do zdania, a następnie przeanalizować wyniki.

Spowoduje to użycie interpretera TensorFlow Lite, który musisz zaimportować:

import TensorFlowLite

Zacznij od funkcji 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 z nim interpretera.

Następnym krokiem będzie skopiowanie podstawowej pamięci zapisanej w sekwencji do bufora o nazwie myData,, aby można było ją przekazać tensorowi. Podczas implementowania modułu TensorFlow Lite oraz interpretera uzyskasz dostęp do typu tensora.

Rozpocznij kod w ten sposób (nadal w klasyfikacji func):

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

Nie martw się, jeśli pojawi się błąd w copyingBufferOf. Zostanie to zaimplementowane jako rozszerzenie w przyszłości.

Teraz czas na przydzielenie tensorów do interpretera, skopiowanie utworzonego właśnie bufora danych do tensora wejściowego i wywołanie interpretera do wykonania wnioskowania:

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 tłumacza, aby zobaczyć wyniki.

Będą to wartości nieprzetworzone (4 bajty na neuron), które musisz odczytać i przekształcić. 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 można stosunkowo łatwo przeanalizować dane, aby określić jakość spamu. Model ma 2 wyjścia: pierwsze z prawdopodobieństwom, że wiadomość nie jest spamem, a drugie z prawdopodobieństwom, że jest. Aby znaleźć wartość spamu, możesz sprawdzić 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

Poniżej znajdziesz 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 nieprzetworzonych bitów tablicy Int32 do zmiennej 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)
  }
}

Podczas pracy z pamięcią na niskim poziomie używasz danych „niebezpiecznych”, a powyższy kod wymaga zainicjowania tablicy danych niepewnych. 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. Uruchom aplikację na iOS

Uruchom i przetestuj aplikację.

Jeśli wszystko poszło dobrze, aplikacja na urządzeniu powinna wyglądać tak:

74cbd28d9b1592ed.png

Gdy wysłano wiadomość „Kup moją książkę, aby dowiedzieć się więcej o inwestowaniu online”, aplikacja wysyła alert o wykryciu spamu z prawdopodobieństwo 0,99%.

14. Gratulacje!

Utworzyłeś/utworzyłaś bardzo prostą aplikację, która filtruje tekst pod kątem spamu w komentarzach, korzystając z modelu wytrenowanego na danych pochodzących z blogów zawierających spam.

Kolejnym krokiem w typowym cyklu życia dewelopera jest sprawdzenie, co jest potrzebne do dostosowania modelu na podstawie danych znalezionych w Twojej społeczności. W następnej ścieżce dowiesz się, jak to zrobić.