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

1. Zanim zaczniesz

W ramach tego ćwiczenia w Codelabs zaktualizujesz aplikację utworzoną w poprzednim szkoleniu „Pierwsze kroki z klasyfikacją tekstu mobilnego”.

Wymagania wstępne

  • To ćwiczenie w Codelabs zostało stworzone z myślą o doświadczonych programistach, którzy nie mają doświadczenia z systemami uczącymi się.
  • Ćwiczenia z programowania są częścią ścieżki sekwencyjnej. Jeśli nie masz jeszcze ukończonych sekcji „Tworzenie podstawowego stylu aplikacji” lub „Tworzenie modelu spamu w komentarzach”, zatrzymaj się i zrób to teraz.

Czego się dowiesz?

  • W poprzednich krokach dowiesz się, jak zintegrować model niestandardowy z aplikacją.

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

Możesz go znaleźć w ścieżce TextClassificationOnMobile->Android.

Gotowy kod jest również dostępny dla Ciebie jako TextClassificationStep2.

Po otwarciu możesz przejść do kroku 2.

3. Importuj plik modelu i metadane

W ramach ćwiczenia z programowania dotyczącego modelu uczenia maszynowego „Budowanie spamu w komentarzach” udało Ci się utworzyć model .TFLITE.

Plik modelu powinien być pobierany. Jeśli jej nie masz, możesz ją pobrać z repozytorium tego ćwiczenia z programowania. Model jest dostępny tutaj.

Aby dodać go do projektu, utwórz katalog zasobów.

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

d7c3e9f21035fc15.png

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

2137f956a1ba4ef0.png

Pojawi się nowy folder assets w aplikacji.

ae858835e1a90445.png

  1. Kliknij Zasoby prawym przyciskiem myszy.
  2. W wyświetlonym menu zobaczysz (na Macu) Pokaż w Finderze. Wybierz go. (w systemie Windows będzie to Pokaż w Eksploratorze, a w Ubuntu – Pokaż w plikach).

e61aaa3b73c5ab68.png

Uruchomi się Finder, by pokazać lokalizację plików (Eksplorator plików w systemie Windows, Pliki w systemie Linux).

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

14f382cc19552a56.png

  1. Wróć do Android Studio. Zobaczysz je 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 projektów w widoku Androida znajdź go w sekcji Skrypty Gradle. Właściwy będzie miał nazwę .app, jak w tym przykładzie:

6426051e614bc42f.png

Musisz wprowadzić 2 zmiany w tym pliku. Pierwsza znajduje się w sekcji Zależności na dole. Dodaj tekst implementation do biblioteki zadań TensorFlow Lite, na przykład:

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

Numer wersji mógł się zmienić od czasu napisania tego tekstu, więc najnowsze informacje znajdziesz na stronie https://www.tensorflow.org/lite/inference_with_metadata/task_library/nl_classifier.

Biblioteki zadań wymagają też pakietu SDK w wersji 21 lub nowszej. Aby znaleźć to ustawienie, kliknij android > default config i zmień ją 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 inną klasę do obsługi wnioskowania dotyczącego modelu. Nazwij tego pomocnika zajęcia.

  1. Kliknij prawym przyciskiem myszy nazwę pakietu, w którym znajduje się Twój kod MainActivity.
  2. Wybierz 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 będzie to tzw. pomocnicy).

3b9f1f822f99b371.png

  1. Gdy to zrobisz, w eksploratorze projektów kliknij prawym przyciskiem myszy folder helpers.
  2. Wybierz Nowy > Class i nazwać 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 przy użyciu 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ępni kod interpretera TensorFlow Lite, który wczyta model i ułatwi złożoność zarządzania zmianą 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 „Budowanie spamu w komentarzach”.

Klasyfikacja jest wykonywana przy użyciu metody classify, w której przekazujesz ciąg znaków, i zwraca ona List. Gdy używasz modeli uczenia maszynowego do klasyfikowania treści, w przypadku których chcesz określić, czy ciąg znaków zawiera spam, często zwracane są wszystkie odpowiedzi z przypisanym prawdopodobieństwem. Jeśli na przykład przekażesz wiadomość, która wygląda jak spam, otrzymasz listę 2 odpowiedzi. jedno z prawdopodobieństwem, że jest to spam, i drugie, że nim nie jest. Elementy typu Spam/Nie spam to kategorie, więc zwrócone dane List będą zawierać te prawdopodobieństwa. Później sobie to przeanalizujesz.

Masz już klasę pomocniczą. Wróć do MainActivity i zaktualizuj je, aby używać tej klasy do klasyfikowania tekstu. W następnym kroku zobaczysz to.

6. Klasyfikowanie tekstu

Na urządzeniu MainActivity musisz najpierw zaimportować utworzone przed chwilą asystenty.

  1. Na górze MainActivity.kt wraz z pozostałymi importami dodaj:
import com.google.devrel.textclassificationstep2.helpers.TextClassificationClient
import org.tensorflow.lite.support.label.Category
  1. Teraz musisz wczytać asystenty. W narzędziu onCreate bezpośrednio po wierszu setContentView dodaj te wiersze, aby utworzyć i wczytać klasę pomocniczą:
val client = TextClassificationClient(applicationContext)
client.load()

Obecnie onClickListener przycisku 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()
}

Zmienia się sposób działania funkcji – nie tylko zapisujesz dane wejściowe użytkownika, ale też je najpierw sklasyfikujesz.

  1. Korzystając z tego wiersza, pobierasz ciąg wpisany przez użytkownika i przekazujesz go do modelu, uzyskując w ten sposób wyniki:
var results:List<Category> = client.classify(toSend)

Są tylko 2 kategorie: False i True

. (TensorFlow sortuje je alfabetycznie, więc wartość Fałsz oznacza element 0, a wartość Prawda – element 1).

  1. Aby uzyskać wynik dla prawdopodobieństwa, że wartość wynosi True, możesz spojrzeć na wyniki[1].score w ten sposób:
    val score = results[1].score
  1. Wybrano wartość progową (w tym przypadku 0,8), co oznacza, że jeśli wynik w kategorii Prawda jest powyżej wartości progowej (0,8), wiadomość jest spamem. W przeciwnym razie nie jest to spam i można ją 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. Komunikat „Odwiedź mojego bloga, aby coś kupić!” został oznaczony jako duże prawdopodobieństwo spamu:

1fb0b5de9e566e.png

I na odwrót: „Hej, zabawny samouczek, dziękuję!” została uznana za bardzo niskie prawdopodobieństwo, że to spam:

73f38bdb488b29b3.png

7. Zaktualizuj aplikację 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.

Gotowy kod jest również dostępny dla Ciebie jako TextClassificationStep2.

Ćwiczenie z programowania w ramach ćwiczenia dotyczącego modelu budowania spamu w komentarzach w ramach uczenia maszynowego stworzyliśmy bardzo prostą aplikację, która umożliwiła użytkownikowi wpisywanie wiadomości w polu UITextView i przekazywanie jej do danych wyjściowych bez konieczności filtrowania.

Teraz zaktualizujesz tę 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).

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

Aby wdrożyć TensorFlow Lite, użyjesz CocoaPods. Jeśli nie masz jeszcze zainstalowanych takich aplikacji, możesz to zrobić, postępując zgodnie z instrukcjami na stronie https://cocoapods.org/.

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

  # Pods for NLPClassifier
    pod 'TensorFlowLiteSwift'

end

Nazwa aplikacji powinna być w pierwszym wierszu, a nie „TextClassificationStep2”.

Używając Terminala, przejdź do tego katalogu i uruchom pod install. Jeśli się uda, utworzysz nowy katalog o nazwie Pods i nowy plik .xcworkspace. Będziesz go używać w przyszłości zamiast .xcproject.

Jeśli się nie udało, sprawdź, czy plik Podfile znajduje się w tym samym katalogu, w którym znajduje się 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. Dodaj pliki modelu i Vocab

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 i upuszczając je z programu Finder w oknie projektu. Upewnij się, że opcja Dodaj do celów jest zaznaczona:

1ee9eaa00ee79859.png

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

b63502b23911fd42.png

  1. Dokładnie sprawdź, czy zostały dodane do pakietu (aby zostały wdrożone na urządzeniu), wybierając projekt (na powyższym zrzucie ekranu jest to niebieska ikona TextClassificationStep2) i na karcie Build Phases (Etapy kompilacji):

20b7cb603d49b457.png

9. Wczytaj Vocab

Podczas klasyfikacji NLP model jest trenowany ze słowami zakodowanymi na wektory. Model koduje słowa z określonym zestawem nazw i wartości, które uczą się podczas trenowania. Pamiętaj, że większość modeli ma różne słowniki i ważne jest, aby używać nazwy w swoim modelu, który został wygenerowany podczas trenowania. To jest plik vocab.txt dodany przed chwilą do aplikacji.

Aby sprawdzić kodowanie, możesz otworzyć plik w Xcode. Słowa takie jak „utwór” są kodowane jako 6 i „miłość” do 12. Kolejność jest w rzeczywistości kolejnością częstotliwości, więc „I” było najczęściej występującym słowem w zbiorze danych, po którym pojawiało się słowo „check”.

Gdy użytkownik wpisuje słowa, musisz je zakodować w tym słowniku, zanim wyślesz je do modelu w celu sklasyfikowania.

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

  1. Zdefiniuj zmienną na poziomie klasy, w której będziesz przechowywać słownik:
var words_dictionary = [String : Int]()
  1. Następnie utwórz w klasie funkcję func, aby wczytać to słowo 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 to uruchomić, wywołując je z viewDidLoad:
override func viewDidLoad() {
    super.viewDidLoad()
    txtInput.delegate = self
    loadVocab()
}

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

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

Model NLP zwykle akceptuje zwykle stałą długość sekwencji. Istnieją wyjątki dotyczące modeli utworzonych za pomocą interfejsu ragged tensors, ale przeważnie jest on rozwiązany. Ta długość została określona podczas tworzenia modelu. Upewnij się, że używasz tej samej długości w aplikacji na iOS.

Wartość domyślna w używanym wcześniej Kreatorze modeli TensorFlow Lite w Colab to 20, możesz ją też skonfigurować tutaj:

let SEQUENCE_LENGTH = 20

Dodaj ten element func, który przekonwertuje go na małe litery i usunie wszelkie 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 będzie to sekwencja 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. To ułatwi (nieco) łatwiejsze przekazywanie ciągów znaków do modelu.

11. Przeprowadź klasyfikację

Aby sklasyfikować zdanie, trzeba je najpierw przekształcić w sekwencję tokenów opartą na słowach w zdaniu. Zrobisz to 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 obiektu func, który przyjmuje sekwencję, która jest 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 załadowanie pliku modelu z pakietu i wywołanie interpretera z nim.

Następnym krokiem będzie skopiowanie bazowej pamięci zapisanej w sekwencji do bufora o nazwie myData,, aby można było przekazać ją do tensora. Dzięki wdrożeniu poda TensorFlow Lite i interpretatora udało Ci się uzyskać dostęp do typu Tensor.

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 na urządzeniu copyingBufferOf pojawi się błąd. Zostanie ona później wdrożona jako rozszerzenie.

Teraz czas przydzielić tensory do interpretera, skopiować właśnie utworzony bufor danych do tensora wejściowego, a potem wywołać interpreter w celu przeprowadzenia 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ływania możesz wyświetlić dane wyjściowe interpretera, aby zobaczyć wyniki.

Będą to nieprzetworzone wartości (4 bajty na neuron), które trzeba potem odczytać i przekonwertować. Ten model ma 2 neurony wyjściowe, więc musisz odczytać z 8 bajtów, które zostaną przekształcone w obiekty Float32 do analizy. Masz do czynienia z pamięcią na niskim poziomie, dlatego 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 w celu określenia jakości spamu. Model ma 2 wyjściowe dane wyjściowe: pierwszy z prawdopodobieństwem, że wiadomość nie zawiera spamu, a druga z prawdopodobieństwem, że wiadomość nie jest spamem. Możesz więc sprawdzić wartość results[1], aby znaleźć wartość spamu:

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 poniżej przedstawiamy 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. Dodaj rozszerzenia Swift

W powyższym kodzie użyto rozszerzenia typu danych, aby umożliwić kopiowanie nieprzetworzonych bitów tablicy Int32 do klasy 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 nazwy „niebezpiecznej” 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. Uruchom aplikację na iOS

Uruchom i przetestuj aplikację.

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

74cbd28d9b1592ed.png

Gdzie wyświetla się komunikat „Kup moją książkę, aby dowiedzieć się, jak handlować online!”. aplikacja wysyła alert o wykryciu spamu z prawdopodobieństwem na poziomie 0, 99%.

14. Gratulacje!

Masz już bardzo prostą aplikację, która filtruje spam w komentarzach przy użyciu modelu wytrenowanego na danych używanych do spamowania blogów.

Następnym krokiem w typowym cyklu programowania aplikacji jest sprawdzenie, co trzeba byłoby zrobić, aby dostosować model na podstawie danych znalezionych w Twojej społeczności. W następnym ćwiczeniu na ścieżce dowiesz się, jak to zrobić.