스팸 필터링 머신러닝 모델을 사용하도록 앱 업데이트

1. 시작하기 전에

이 Codelab에서는 이전 앱 모바일 텍스트 분류 시작하기 Codelab에서 빌드한 앱을 업데이트합니다.

기본 요건

  • 이 Codelab은 머신러닝을 처음 접하는 숙련된 개발자를 위해 설계되었습니다.
  • Codelab은 시퀀스된 경로의 일부입니다. 아직 기본적인 메시지 스타일 앱을 빌드하지 않았거나 댓글 스팸 머신러닝 모델을 구축하지 않았다면 지금 그만두세요.

[빌드 또는 학습] 내용

  • 이전 단계에서 빌드한 커스텀 모델을 앱에 통합하는 방법을 알아봅니다.

준비물

2 기존 Android 앱을 엽니다.

이 코드는 Codelab 1을 따르거나 저장소를 클론하고 TextClassificationStep1에서 앱을 로드하여 가져올 수 있습니다.

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

TextClassificationOnMobile->Android 경로에서 찾을 수 있습니다.

finished 코드도 TextClassificationStep2로 사용할 수 있습니다.

계정이 열리면 2단계로 넘어갈 수 있습니다.

3. 모델 파일 및 메타데이터 가져오기

댓글 스팸 머신러닝 모델 빌드 Codelab에서는 .TFLITE 모델을 만들었습니다.

모델 파일을 다운로드했어야 합니다. 인증서가 없다면 이 Codelab의 repo에서 다운로드할 수 있으며 여기에서 모델을 사용할 수 있습니다.

애셋 디렉터리를 만들어 프로젝트에 추가하세요.

  1. 프로젝트 탐색기를 사용하여 상단에서 Android가 선택되었는지 확인합니다.
  2. app 폴더를 마우스 오른쪽 버튼으로 클릭합니다. 새로 만들기 > 디렉터리를 선택합니다.

d7c3e9f21035fc15.png

  1. 새 디렉터리 대화상자에서 src/main/assets를 선택합니다.

2137f956a1ba4ef0.png

이제 앱에 새로운 assets 폴더를 사용할 수 있음을 확인할 수 있습니다.

AE858835E1a90445.png

  1. assets를 마우스 오른쪽 버튼으로 클릭합니다.
  2. 메뉴가 열리면 Mac의 경우 Finder에서 표시가 표시됩니다. 항목을 선택합니다. Windows에서는 Show in Explorer, Ubuntu에서는 Show in Files라고 표시됩니다.

E61aaa3b73c5ab68.png

Finder가 실행되고 파일 위치가 표시됩니다 (Windows의 경우 File Explorer, Linux의 경우 Files).

  1. labels.txt, model.tflite, vocab 파일을 이 디렉터리에 복사합니다.

14f382cc19552a56.png

  1. Android 스튜디오로 돌아가면 assets 폴더에서 사용 가능한 폴더를 확인할 수 있습니다.

150ed2a1d2f7a10d.png

4. TensorFlow Lite를 사용하도록 build.gradle 업데이트하기

TensorFlow Lite와 이를 지원하는 TensorFlow Lite 작업 라이브러리를 사용하려면 build.gradle 파일을 업데이트해야 합니다.

Android 프로젝트에는 항목이 두 개 이상 있으므로 수준 프로젝트를 찾아야 합니다. Android 뷰의 프로젝트 탐색기에서 Gradle Scripts 섹션으로 이동합니다. 아래와 같이 올바른 형식은 .app으로 표시됩니다.

6426051e614bc42f.png

이 파일을 두 가지 변경해야 합니다. 첫 번째는 하단의 종속 항목 섹션에 있습니다. 다음과 같이 TensorFlow Lite 작업 라이브러리의 텍스트 implementation를 추가합니다.

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

작성한 버전은 버전 번호가 다를 수 있으므로 https://www.tensorflow.org/lite/inference_with_metadata/task_library/nl_classifier에서 최신 버전을 확인해야 합니다.

작업 라이브러리에는 최소 SDK 버전 21도 필요합니다. android > default config에서 이 설정을 찾아 21로 변경합니다.

C100b68450b8812f.png

이제 모든 종속 항목이 있으므로 코딩을 시작할 차례입니다.

5 도우미 클래스 추가

앱이 모델을 사용하는 추론 로직을 사용자 인터페이스에서 분리하려면 모델 추론을 처리할 다른 클래스를 만듭니다. 이를 '도우미' 클래스라고 합니다.

  1. MainActivity 코드가 있는 패키지 이름을 마우스 오른쪽 버튼으로 클릭합니다.
  2. New > Package를 선택합니다.

D5911ded56b5df35.png

  1. 화면 중앙에 패키지 이름을 입력하라는 대화상자가 표시됩니다. 현재 패키지 이름의 끝에 추가합니다. 여기서는 helper라고 합니다.

3b9f1f822f99b371.png

  1. 이 작업이 완료되면 프로젝트 탐색기에서 helpers 폴더를 마우스 오른쪽 버튼으로 클릭합니다.
  2. New > Java Class를 선택하고 이름을 TextClassificationClient로 지정합니다. 다음 단계에서 파일을 수정합니다.

TextClassificationClient 도우미 클래스는 다음과 같습니다 (패키지 이름은 다를 수 있음).

package com.google.devrel.textclassificationstep1.helpers;

public class TextClassificationClient {
}
  1. 다음 코드를 사용하여 파일을 업데이트합니다.
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;
    }

}

이 클래스는 앱과 모델 간의 데이터 교환 관리 복잡성을 추상화하고 모델을 로드하며 TensorFlow Lite 인터프리터에 래퍼를 제공합니다.

load() 메서드의 경우에는 모델 경로에서 새로운 NLClassifier 유형을 인스턴스화합니다. 모델 경로는 모델의 이름인 model.tflite입니다. NLClassifier 유형은 텍스트 작업 라이브러리의 일부이며, 문자열을 토큰으로 변환하고, 올바른 시퀀스 길이를 사용하여 모델에 전달하며, 결과를 파싱하는 데 도움이 됩니다.

(자세한 내용은 댓글 스팸 머신러닝 모델 구축을 다시 참고하세요.)

분류는 문자열에서 전달되고 문자열을 전달한 후 List를 반환합니다. 머신러닝 모델을 사용하여 문자열을 스팸으로 분류할지 여부를 판단할 때 할당된 확률로 모든 답변을 반환하는 것이 일반적입니다. 예를 들어 스팸으로 보이는 메일을 전달하면 2개의 답변 목록이 다시 표시됩니다. 하나는 스팸일 확률이고 다른 하나는 스팸이 아닐 확률입니다. 스팸/스팸이 아닌 카테고리이므로 반환되는 List에 이러한 가능성이 포함됩니다. 나중에 파싱합니다.

이제 도우미 클래스를 만들었으므로 MainActivity로 돌아가서 이 클래스를 사용하여 텍스트를 분류하도록 업데이트합니다. 다음 단계에서 확인할 수 있습니다.

6. 텍스트 분류

MainActivity에서 방금 만든 도우미를 가져와야 합니다.

  1. MainActivity.kt 상단에서 다른 가져오기와 함께 다음을 추가합니다.
import com.google.devrel.textclassificationstep2.helpers.TextClassificationClient
import org.tensorflow.lite.support.label.Category
  1. 다음으로, 도우미를 로드합니다. onCreatesetContentView 줄 바로 뒤에 다음 줄을 추가하여 도우미 클래스를 인스턴스화하고 로드합니다.
val client = TextClassificationClient(applicationContext)
client.load()

현재 버튼의 onClickListener는 다음과 같이 표시됩니다.

btnSendText.setOnClickListener {
     var toSend:String = txtInput.text.toString()
     txtOutput.text = toSend
 }
  1. 다음과 같이 업데이트합니다.
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()
}

그러면 사용자 입력이 출력되는 것에서부터 먼저 분류되는 기능이 변경됩니다.

  1. 다음 코드 줄에서 사용자가 입력한 문자열을 가져와 모델에 전달하고 결과를 다시 얻을 수 있습니다.
var results:List<Category> = client.classify(toSend)

FalseTrue의 두 가지 카테고리만 있습니다.

. (TensorFlow가 알파벳순으로 정렬됨에 따라 False는 항목 0, True는 항목 1임)

  1. 값이 True인 확률을 얻기 위해 다음과 같은 results[1].점수를 확인할 수 있습니다.
    val score = results[1].score
  1. 임곗값 (이 경우 0.8)을 선택했습니다. 여기서 True 카테고리의 점수가 기준 값 (0.8)보다 높으면 메시지가 스팸입니다. 그렇지 않은 경우 스팸이 아니기 때문에 메일을 안전하게 전송할 수 있습니다.
    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. 여기에서 실제로 작동하는 모델을 확인하세요. '내 블로그를 방문하여 콘텐츠를 구매하세요.'라는 메시지 스팸일 가능성이 높은 것으로 신고되었습니다.

1fb0b5de9e566e.png

반대로 '재미있는 튜토리얼!' 스팸일 가능성이 매우 낮은 것으로 나타났습니다.

73f38bdb488b29b3.png

7 TensorFlow Lite 모델을 사용하도록 iOS 앱 업데이트

이 코드는 Codelab 1을 따르거나 저장소를 클론하고 TextClassificationStep1에서 앱을 로드하여 가져올 수 있습니다. TextClassificationOnMobile->iOS 경로에서 찾을 수 있습니다.

finished 코드도 TextClassificationStep2로 사용할 수 있습니다.

빌드 주석 댓글 머신러닝 모델 Codelab에서는 사용자가 UITextView에 메시지를 입력하여 필터링 없이 출력으로 전달하도록 하는 매우 간단한 앱을 만들었습니다.

이제 전송하기 전에 TensorFlow Lite 모델을 사용하여 텍스트에서 댓글 스팸을 감지하도록 앱을 업데이트합니다. 앱에서 출력 라벨의 텍스트를 렌더링하여 전송을 시뮬레이션하면 됩니다 (실제 앱에는 게시판이나 채팅 등이 있을 수 있음).

시작하려면 저장소에서 클론할 수 있는 1단계의 앱이 필요합니다.

TensorFlow Lite를 통합하려면 CocoaPods를 사용해야 합니다. 아직 설치하지 않았다면 https://cocoapods.org/의 안내에 따라 설치할 수 있습니다.

  1. CocoaPods를 설치한 다음 TextPod 앱의 .xcproject와 동일한 디렉터리에 Podfile이라는 이름의 파일을 만듭니다. 이 파일의 내용은 다음과 같습니다.
target 'TextClassificationStep2' do
  use_frameworks!

  # Pods for NLPClassifier
    pod 'TensorFlowLiteSwift'

end

앱 이름은 'TextClassificationStep2'가 아닌 첫 번째 줄에 있어야 합니다.

터미널을 사용하여 그 디렉터리로 이동하고 pod install를 실행합니다. 성공적으로 완료되면 pods라는 새 디렉터리와 새 .xcworkspace 파일이 생성됩니다. 앞으로는 .xcproject 대신 사용합니다.

실패한 경우 .xcproject이 있는 디렉터리와 동일한 디렉터리에 Podfile이 있는지 확인하세요. 잘못된 디렉터리 또는 잘못된 대상 이름의 podfile이 일반적으로 주 원인입니다.

8 모델 및 어휘 파일 추가

TensorFlow Lite Model Maker로 모델을 만들 때 모델을model.tflite ) 및 어휘로 표현됩니다.vocab.txt 또는 저장용량 버킷).

  1. Finder에서 프로젝트 창으로 드래그 앤 드롭하여 프로젝트에 추가합니다. 대상에 추가가 선택되어 있는지 확인합니다.

1ee9eaa00ee79859.png

완료되면 프로젝트에 다음과 같이 표시됩니다.

b63502b23911fd42.png

  1. 프로젝트 (위 스크린샷에서 파란색 아이콘임)를 선택하여 번들에 추가되었는지 다시 확인합니다 (기기에 배포되도록).TextClassificationStep2 ) 및빌드 단계 탭:

20b7cb603d49b457.png

9. 어휘 로드

NLP 분류를 실행할 때 모델은 벡터로 인코딩된 단어로 학습됩니다. 모델은 모델이 학습할 때 학습된 특정 이름 및 값 집합으로 단어를 인코딩합니다. 대부분의 모델은 다른 어휘를 사용하며, 학습 당시 생성된 모델에 어휘를 사용하는 것이 중요합니다. 방금 앱에 추가한 vocab.txt 파일입니다.

Xcode에서 파일을 열어 인코딩을 볼 수 있습니다. 'song'과 같은 단어는 6으로 인코딩되고 'love'는 12로 인코딩됩니다. 순서는 실제로 빈도 순서이므로 데이터 세트에서 가장 일반적인 단어인 'I'는 'check'로 이어집니다.

사용자가 단어를 입력할 때는 분류하도록 모델에 보내기 전에 이러한 어휘로 인코딩하는 것이 좋습니다.

그 코드를 살펴보겠습니다. 먼저 어휘를 로드합니다.

  1. 사전을 저장할 클래스 수준 변수를 정의합니다.
var words_dictionary = [String : Int]()
  1. 그런 다음 클래스에서 func을 만들어 단어를 이 사전에 로드합니다.
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. viewDidLoad 내에서 이 메서드를 호출하여 실행할 수 있습니다.
override func viewDidLoad() {
    super.viewDidLoad()
    txtInput.delegate = self
    loadVocab()
}

10. 문자열을 토큰 시퀀스로 전환

사용자는 문자열이 될 문장으로 단어를 입력합니다. 문장의 각 단어는 어휘에 정의된 단어에 대한 키 값으로 인코딩됩니다.

NLP 모델은 일반적으로 고정된 시퀀스 길이를 허용합니다. ragged tensors를 사용하여 빌드된 모델에는 예외가 있지만 대부분의 경우 고정됩니다. 모델을 만들 때 이 길이를 지정했습니다. iOS 앱에서 동일한 길이를 사용해야 합니다.

이전에 사용한 TensorFlow Lite Model Maker용 Colab의 기본값은 20이므로 여기서도 설정합니다.

let SEQUENCE_LENGTH = 20

문자열을 가져와 소문자로 변환하고 구두점을 제거하는 func를 추가합니다.

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
}

시퀀스는 Int32가 됩니다. 값을 TensorFlow Lite에 전달할 때는 의도적으로 하위 수준 메모리를 처리하고, TensorFlow Lite는 문자열 시퀀스의 정수를 32비트 정수로 취급하기 때문에 의도적으로 선택됩니다. 이렇게 하면 모델에 문자열을 전달할 때 조금이라도 더 도움이 됩니다.

1일 분류

문장을 분류하려면 먼저 문장의 단어를 기반으로 하여 토큰의 시퀀스로 변환해야 합니다. 9단계에서 실행합니다.

이제 문장을 가져와 모델에 전달하고 모델이 문장을 추론하도록 하고 결과를 파싱합니다.

그러면 다음을 가져와야 하는 TensorFlow Lite 인터프리터가 사용됩니다.

import TensorFlowLite

Int32 유형의 배열인 시퀀스를 사용하는 func로 시작합니다.

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
  }

번들에서 모델 파일을 로드하고 인터프리터를 호출합니다.

다음 단계는 시퀀스에 저장된 기본 메모리를 myData,라는 버퍼로 복사하여 텐서에 전달될 수 있도록 하는 것입니다. 인터프리터뿐 아니라 TensorFlow Lite 포드를 구현할 때 텐서 유형에 액세스할 수 있습니다.

다음과 같이 코드를 시작합니다 (여전히 func 분류).

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

copyingBufferOf에서 오류가 발생해도 걱정하지 마세요. 이는 나중에 확장 프로그램으로 구현됩니다.

이제 인터프리터에 텐서를 할당하고 방금 만든 데이터 버퍼를 입력 텐서에 복사한 다음 인터프리터를 호출하여 추론을 수행합니다.

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

호출이 완료되면 인터프리터의 출력을 확인하여 결과를 확인할 수 있습니다.

이 값은 원시 값 (뉴런당 4바이트)이며, 읽은 후 변환해야 합니다. 이 모델에는 출력 뉴런이 2개 있으므로 파싱을 위해 Float32로 변환될 8바이트를 읽어야 합니다. 낮은 수준의 메모리를 처리하므로 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) ?? []

이제 데이터를 파싱하여 스팸 품질을 판단할 수 있습니다. 모델에 2개의 출력이 있습니다. 첫 번째는 메일이 스팸이 아닐 확률이고 두 번째는 스팸일 확률이 있습니다. 따라서 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

편의를 위해 전체 메서드는 다음과 같습니다.

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. Swift 확장 프로그램 추가

위의 코드에서는 Int32 배열의 원시 비트를 Data로 복사할 수 있도록 데이터 유형의 확장 프로그램을 사용했습니다. 이 확장 프로그램의 코드는 다음과 같습니다.

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

낮은 수준의 메모리를 처리할 때 '안전하지 않은' 데이터를 사용하며 위의 코드에서는 안전하지 않은 데이터의 배열을 초기화해야 합니다. 이 확장 프로그램을 사용하면 다음 작업이 가능합니다.

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. iOS 앱 실행

앱을 실행하고 테스트합니다.

모두 완료되었다면 기기에 앱이 다음과 같이 표시됩니다.

74cbd28d9b1592ed.png

'온라인 구매를 위해 내 책을 구입하세요'라는 메시지 전송되면 앱에서 0.99%의 확률로 스팸 감지 알림을 전송합니다.

14. 축하합니다.

이제 스팸 블로그에 사용되는 데이터를 대상으로 학습된 모델을 사용하여 댓글 스팸에 대해 텍스트를 필터링하는 매우 간단한 앱을 만들었습니다.

전형적인 개발자 수명 주기의 다음 단계는 여러분의 커뮤니티에서 얻은 데이터를 기반으로 모델을 맞춤설정하는 방법을 살펴보는 것입니다. 다음 과정 활동에서 이를 처리하는 방법을 확인할 수 있습니다.