텍스트 로드하기

TensorFlow.org에서 보기 Google Colab에서 실행 GitHub에서 소스 보기 노트북 다운로드

이 튜토리얼은 텍스트를 로드하고 전처리하는 두 가지 방법을 보여 줍니다.

pip install "tensorflow-text==2.8.*"
import collections
import pathlib

import tensorflow as tf

from tensorflow.keras import layers
from tensorflow.keras import losses
from tensorflow.keras import utils
from tensorflow.keras.layers import TextVectorization

import tensorflow_datasets as tfds
import tensorflow_text as tf_text

예제 1: 스택 오버플로 질문에 대한 태그 예측하기

첫 번째 예제로 스택 오버플로에서 프로그래밍 질문 데이터세트를 다운로드합니다. 각 질문("값으로 사전을 어떻게 정렬하나요?")마다 정확히 하나의 태그(Python, CSharp, JavaScript, 또는 Java)가 레이블로 지정됩니다. 여러분의 작업은 질문에 대한 태그를 예측하는 모델을 개발하는 것입니다. 이것은 중요하고 널리 적용 가능한 머신러닝 문제인 다중 클래스 분류 예제입니다.

데이터세트 다운로드 및 탐색하기

tf.keras.utils.get_file을 사용하여 스택 오버플로 데이터세트를 다운로드하고, 디렉터리 구조를 탐색하여 시작하도록 합니다.

data_url = 'https://storage.googleapis.com/download.tensorflow.org/data/stack_overflow_16k.tar.gz'

dataset_dir = utils.get_file(
    origin=data_url,
    untar=True,
    cache_dir='stack_overflow',
    cache_subdir='')

dataset_dir = pathlib.Path(dataset_dir).parent
Downloading data from https://storage.googleapis.com/download.tensorflow.org/data/stack_overflow_16k.tar.gz
6053888/6053168 [==============================] - 0s 0us/step
6062080/6053168 [==============================] - 0s 0us/step
list(dataset_dir.iterdir())
[PosixPath('/tmp/.keras/test'),
 PosixPath('/tmp/.keras/train'),
 PosixPath('/tmp/.keras/README.md'),
 PosixPath('/tmp/.keras/stack_overflow_16k.tar.gz')]
train_dir = dataset_dir/'train'
list(train_dir.iterdir())
[PosixPath('/tmp/.keras/train/csharp'),
 PosixPath('/tmp/.keras/train/javascript'),
 PosixPath('/tmp/.keras/train/python'),
 PosixPath('/tmp/.keras/train/java')]

train/csharp, train/java, train/pythontrain/javascript 디렉터리에는 많은 텍스트 파일이 포함되어 있으며, 각 텍스트 파일의 내용은 스택 오버플로 질문입니다.

다음과 같이 예제 파일을 출력하고 데이터를 검사합니다.

sample_file = train_dir/'python/1755.txt'

with open(sample_file) as f:
  print(f.read())
why does this blank program print true x=true.def stupid():.    x=false.stupid().print x

데이터세트 로드하기

다음으로 디스크로부터 데이터를 로드하고 데이터를 훈련에 적합한 형식으로 준비합니다. 이렇게 하기 위해 여러분은 tf.keras.utils.text_dataset_from_directory 유틸리티를 사용하여 레이블이 지정된 tf.data.Dataset를 생성합니다. tf.data는 입력 파이프라인을 구축하는 강력한 도구 모음입니다(tf.data: TensorFlow 입력 파이프라인 빌드 가이드에서 자세히 알아보세요).

tf.keras.utils.text_dataset_from_directory API는 다음과 같은 디렉터리 구조를 예상합니다.

train/
...csharp/
......1.txt
......2.txt
...java/
......1.txt
......2.txt
...javascript/
......1.txt
......2.txt
...python/
......1.txt
......2.txt

머신러닝 실험을 실행할 때 데이터세트를 훈련, 검증테스트의 세 부분으로 나누는 것이 가장 좋습니다.

스택 오버플로 데이터세트는 이미 훈련 세트와 테스트 세트로 나누어져 있지만 여기에는 검증 세트가 없습니다.

validation_split0.2(즉, 20%)로 설정된 tf.keras.utils.text_dataset_from_directory를 사용하여 훈련 데이터를 80:20의 비율로 분할하는 검증 세트를 생성합니다.

batch_size = 32
seed = 42

raw_train_ds = utils.text_dataset_from_directory(
    train_dir,
    batch_size=batch_size,
    validation_split=0.2,
    subset='training',
    seed=seed)
Found 8000 files belonging to 4 classes.
Using 6400 files for training.

이전 셀 출력을 통해 알 수 있듯이 훈련 폴더에는 8,000개의 예제가 있으며 여러분은 그 중 80%(또는 6,400개의 예제)를 훈련에 사용할 것입니다. 여러분은 tf.data.DatasetModel.fit에 직접 전달하여 모델을 훈련할 수 있다는 것을 곧 배우게 될 것입니다.

먼저 데이터세트를 반복하고 몇 가지 예제를 인쇄하여 데이터에 대한 감각을 익히세요.

참고: 분류 문제의 난이도를 높이기 위해 데이터세트 작성자는 프로그래밍 질문에서 Python, CSharp, JavaScript 또는 Java라는 단어를 blank로 대체했습니다.

for text_batch, label_batch in raw_train_ds.take(1):
  for i in range(10):
    print("Question: ", text_batch.numpy()[i])
    print("Label:", label_batch.numpy()[i])
Question:  b'"my tester is going to the wrong constructor i am new to programming so if i ask a question that can be easily fixed, please forgive me. my program has a tester class with a main. when i send that to my regularpolygon class, it sends it to the wrong constructor. i have two constructors. 1 without perameters..public regularpolygon().    {.       mynumsides = 5;.       mysidelength = 30;.    }//end default constructor...and my second, with perameters. ..public regularpolygon(int numsides, double sidelength).    {.        mynumsides = numsides;.        mysidelength = sidelength;.    }// end constructor...in my tester class i have these two lines:..regularpolygon shape = new regularpolygon(numsides, sidelength);.        shape.menu();...numsides and sidelength were declared and initialized earlier in the testing class...so what i want to happen, is the tester class sends numsides and sidelength to the second constructor and use it in that class. but it only uses the default constructor, which therefor ruins the whole rest of the program. can somebody help me?..for those of you who want to see more of my code: here you go..public double vertexangle().    {.        system.out.println(""the vertex angle method: "" + mynumsides);// prints out 5.        system.out.println(""the vertex angle method: "" + mysidelength); // prints out 30..        double vertexangle;.        vertexangle = ((mynumsides - 2.0) / mynumsides) * 180.0;.        return vertexangle;.    }//end method vertexangle..public void menu().{.    system.out.println(mynumsides); // prints out what the user puts in.    system.out.println(mysidelength); // prints out what the user puts in.    gotographic();.    calcr(mynumsides, mysidelength);.    calcr(mynumsides, mysidelength);.    print(); .}// end menu...this is my entire tester class:..public static void main(string[] arg).{.    int numsides;.    double sidelength;.    scanner keyboard = new scanner(system.in);..    system.out.println(""welcome to the regular polygon program!"");.    system.out.println();..    system.out.print(""enter the number of sides of the polygon ==> "");.    numsides = keyboard.nextint();.    system.out.println();..    system.out.print(""enter the side length of each side ==> "");.    sidelength = keyboard.nextdouble();.    system.out.println();..    regularpolygon shape = new regularpolygon(numsides, sidelength);.    shape.menu();.}//end main...for testing it i sent it numsides 4 and sidelength 100."\n'
Label: 1
Question:  b'"blank code slow skin detection this code changes the color space to lab and using a threshold finds the skin area of an image. but it\'s ridiculously slow. i don\'t know how to make it faster ?    ..from colormath.color_objects import *..def skindetection(img, treshold=80, color=[255,20,147]):..    print img.shape.    res=img.copy().    for x in range(img.shape[0]):.        for y in range(img.shape[1]):.            rgbimg=rgbcolor(img[x,y,0],img[x,y,1],img[x,y,2]).            labimg=rgbimg.convert_to(\'lab\', debug=false).            if (labimg.lab_l > treshold):.                res[x,y,:]=color.            else: .                res[x,y,:]=img[x,y,:]..    return res"\n'
Label: 3
Question:  b'"option and validation in blank i want to add a new option on my system where i want to add two text files, both rental.txt and customer.txt. inside each text are id numbers of the customer, the videotape they need and the price...i want to place it as an option on my code. right now i have:...add customer.rent return.view list.search.exit...i want to add this as my sixth option. say for example i ordered a video, it would display the price and would let me confirm the price and if i am going to buy it or not...here is my current code:..  import blank.io.*;.    import blank.util.arraylist;.    import static blank.lang.system.out;..    public class rentalsystem{.    static bufferedreader input = new bufferedreader(new inputstreamreader(system.in));.    static file file = new file(""file.txt"");.    static arraylist<string> list = new arraylist<string>();.    static int rows;..    public static void main(string[] args) throws exception{.        introduction();.        system.out.print(""nn"");.        login();.        system.out.print(""nnnnnnnnnnnnnnnnnnnnnn"");.        introduction();.        string repeat;.        do{.            loadfile();.            system.out.print(""nwhat do you want to do?nn"");.            system.out.print(""n                    - - - - - - - - - - - - - - - - - - - - - - -"");.            system.out.print(""nn                    |     1. add customer    |   2. rent return |n"");.            system.out.print(""n                    - - - - - - - - - - - - - - - - - - - - - - -"");.            system.out.print(""nn                    |     3. view list       |   4. search      |n"");.            system.out.print(""n                    - - - - - - - - - - - - - - - - - - - - - - -"");.            system.out.print(""nn                                             |   5. exit        |n"");.            system.out.print(""n                                              - - - - - - - - - -"");.            system.out.print(""nnchoice:"");.            int choice = integer.parseint(input.readline());.            switch(choice){.                case 1:.                    writedata();.                    break;.                case 2:.                    rentdata();.                    break;.                case 3:.                    viewlist();.                    break;.                case 4:.                    search();.                    break;.                case 5:.                    system.out.println(""goodbye!"");.                    system.exit(0);.                default:.                    system.out.print(""invalid choice: "");.                    break;.            }.            system.out.print(""ndo another task? [y/n] "");.            repeat = input.readline();.        }while(repeat.equals(""y""));..        if(repeat!=""y"") system.out.println(""ngoodbye!"");..    }..    public static void writedata() throws exception{.        system.out.print(""nname: "");.        string cname = input.readline();.        system.out.print(""address: "");.        string add = input.readline();.        system.out.print(""phone no.: "");.        string pno = input.readline();.        system.out.print(""rental amount: "");.        string ramount = input.readline();.        system.out.print(""tapenumber: "");.        string tno = input.readline();.        system.out.print(""title: "");.        string title = input.readline();.        system.out.print(""date borrowed: "");.        string dborrowed = input.readline();.        system.out.print(""due date: "");.        string ddate = input.readline();.        createline(cname, add, pno, ramount,tno, title, dborrowed, ddate);.        rentdata();.    }..    public static void createline(string name, string address, string phone , string rental, string tapenumber, string title, string borrowed, string due) throws exception{.        filewriter fw = new filewriter(file, true);.        fw.write(""nname: ""+name + ""naddress: "" + address +""nphone no.: ""+ phone+""nrentalamount: ""+rental+""ntape no.: ""+ tapenumber+""ntitle: ""+ title+""ndate borrowed: ""+borrowed +""ndue date: ""+ due+"":rn"");.        fw.close();.    }..    public static void loadfile() throws exception{.        try{.            list.clear();.            fileinputstream fstream = new fileinputstream(file);.            bufferedreader br = new bufferedreader(new inputstreamreader(fstream));.            rows = 0;.            while( br.ready()).            {.                list.add(br.readline());.                rows++;.            }.            br.close();.        } catch(exception e){.            system.out.println(""list not yet loaded."");.        }.    }..    public static void viewlist(){.        system.out.print(""n~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~"");.        system.out.print("" |list of all costumers|"");.        system.out.print(""~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~"");.        for(int i = 0; i <rows; i++){.            system.out.println(list.get(i));.        }.    }.        public static void rentdata()throws exception.    {   system.out.print(""n~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~"");.        system.out.print("" |rent data list|"");.        system.out.print(""~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~"");.        system.out.print(""nenter customer name: "");.        string cname = input.readline();.        system.out.print(""date borrowed: "");.        string dborrowed = input.readline();.        system.out.print(""due date: "");.        string ddate = input.readline();.        system.out.print(""return date: "");.        string rdate = input.readline();.        system.out.print(""rent amount: "");.        string ramount = input.readline();..        system.out.print(""you pay:""+ramount);...    }.    public static void search()throws exception.    {   system.out.print(""n~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~"");.        system.out.print("" |search costumers|"");.        system.out.print(""~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~"");.        system.out.print(""nenter costumer name: "");.        string cname = input.readline();.        boolean found = false;..        for(int i=0; i < rows; i++){.            string temp[] = list.get(i).split("","");..            if(cname.equals(temp[0])){.            system.out.println(""search result:nyou are "" + temp[0] + "" from "" + temp[1] + "".""+ temp[2] + "".""+ temp[3] + "".""+ temp[4] + "".""+ temp[5] + "" is "" + temp[6] + "".""+ temp[7] + "" is "" + temp[8] + ""."");.                found = true;.            }.        }..        if(!found){.            system.out.print(""no results."");.        }..    }..        public static boolean evaluate(string uname, string pass){.        if (uname.equals(""admin"")&&pass.equals(""12345"")) return true;.        else return false;.    }..    public static string login()throws exception{.        bufferedreader input=new bufferedreader(new inputstreamreader(system.in));.        int counter=0;.        do{.            system.out.print(""username:"");.            string uname =input.readline();.            system.out.print(""password:"");.            string pass =input.readline();..            boolean accept= evaluate(uname,pass);..            if(accept){.                break;.                }else{.                    system.out.println(""incorrect username or password!"");.                    counter ++;.                    }.        }while(counter<3);..            if(counter !=3) return ""login successful"";.            else return ""login failed"";.            }.        public static void introduction() throws exception{..        system.out.println(""                  - - - - - - - - - - - - - - - - - - - - - - - - -"");.        system.out.println(""                  !                  r e n t a l                  !"");.        system.out.println(""                   ! ~ ~ ~ ~ ~ !  =================  ! ~ ~ ~ ~ ~ !"");.        system.out.println(""                  !                  s y s t e m                  !"");.        system.out.println(""                  - - - - - - - - - - - - - - - - - - - - - - - - -"");.        }..}"\n'
Label: 1
Question:  b'"exception: dynamic sql generation for the updatecommand is not supported against a selectcommand that does not return any key i dont know what is the problem this my code : ..string nomtable;..datatable listeetablissementtable = new datatable();.datatable listeinteretstable = new datatable();.dataset ds = new dataset();.sqldataadapter da;.sqlcommandbuilder cmdb;..private void listeinterets_click(object sender, eventargs e).{.    nomtable = ""listeinteretstable"";.    d.cnx.open();.    da = new sqldataadapter(""select nome from offices"", d.cnx);.    ds = new dataset();.    da.fill(ds, nomtable);.    datagridview1.datasource = ds.tables[nomtable];.}..private void sauvgarder_click(object sender, eventargs e).{.    d.cnx.open();.    cmdb = new sqlcommandbuilder(da);.    da.update(ds, nomtable);.    d.cnx.close();.}"\n'
Label: 0
Question:  b'"parameter with question mark and super in blank, i\'ve come across a method that is formatted like this:..public final subscription subscribe(final action1<? super t> onnext, final action1<throwable> onerror) {.}...in the first parameter, what does the question mark and super mean?"\n'
Label: 1
Question:  b'call two objects wsdl the first time i got a very strange wsdl. ..i would like to call the object (interface - invoicecheck_out) do you know how?....i would like to call the object (variable) do you know how?..try to call (it`s ok)....try to call (how call this?)\n'
Label: 0
Question:  b"how to correctly make the icon for systemtray in blank using icon sizes of any dimension for systemtray doesn't look good overall. .what is the correct way of making icons for windows system tray?..screenshots: http://imgur.com/zsibwn9..icon: http://imgur.com/vsh4zo8\n"
Label: 0
Question:  b'"is there a way to check a variable that exists in a different script than the original one? i\'m trying to check if a variable, which was previously set to true in 2.py in 1.py, as 1.py is only supposed to continue if the variable is true...2.py..import os..completed = false..#some stuff here..completed = true...1.py..import 2 ..if completed == true.   #do things...however i get a syntax error at ..if completed == true"\n'
Label: 3
Question:  b'"blank control flow i made a number which asks for 2 numbers with blank and responds with  the corresponding message for the case. how come it doesnt work  for the second number ? .regardless what i enter for the second number , i am getting the message ""your number is in the range 0-10""...using system;.using system.collections.generic;.using system.linq;.using system.text;..namespace consoleapplication1.{.    class program.    {.        static void main(string[] args).        {.            string myinput;  // declaring the type of the variables.            int myint;..            string number1;.            int number;...            console.writeline(""enter a number"");.            myinput = console.readline(); //muyinput is a string  which is entry input.            myint = int32.parse(myinput); // myint converts the string into an integer..            if (myint > 0).                console.writeline(""your number {0} is greater than zero."", myint);.            else if (myint < 0).                console.writeline(""your number {0} is  less  than zero."", myint);.            else.                console.writeline(""your number {0} is equal zero."", myint);..            console.writeline(""enter another number"");.            number1 = console.readline(); .            number = int32.parse(myinput); ..            if (number < 0 || number == 0).                console.writeline(""your number {0} is  less  than zero or equal zero."", number);.            else if (number > 0 && number <= 10).                console.writeline(""your number {0} is  in the range from 0 to 10."", number);.            else.                console.writeline(""your number {0} is greater than 10."", number);..            console.writeline(""enter another number"");..        }.    }    .}"\n'
Label: 0
Question:  b'"credentials cannot be used for ntlm authentication i am getting org.apache.commons.httpclient.auth.invalidcredentialsexception: credentials cannot be used for ntlm authentication: exception in eclipse..whether it is possible mention eclipse to take system proxy settings directly?..public class httpgetproxy {.    private static final string proxy_host = ""proxy.****.com"";.    private static final int proxy_port = 6050;..    public static void main(string[] args) {.        httpclient client = new httpclient();.        httpmethod method = new getmethod(""https://kodeblank.org"");..        hostconfiguration config = client.gethostconfiguration();.        config.setproxy(proxy_host, proxy_port);..        string username = ""*****"";.        string password = ""*****"";.        credentials credentials = new usernamepasswordcredentials(username, password);.        authscope authscope = new authscope(proxy_host, proxy_port);..        client.getstate().setproxycredentials(authscope, credentials);..        try {.            client.executemethod(method);..            if (method.getstatuscode() == httpstatus.sc_ok) {.                string response = method.getresponsebodyasstring();.                system.out.println(""response = "" + response);.            }.        } catch (ioexception e) {.            e.printstacktrace();.        } finally {.            method.releaseconnection();.        }.    }.}...exception:...  dec 08, 2017 1:41:39 pm .          org.apache.commons.httpclient.auth.authchallengeprocessor selectauthscheme.         info: ntlm authentication scheme selected.       dec 08, 2017 1:41:39 pm org.apache.commons.httpclient.httpmethoddirector executeconnect.         severe: credentials cannot be used for ntlm authentication: .           org.apache.commons.httpclient.usernamepasswordcredentials.           org.apache.commons.httpclient.auth.invalidcredentialsexception: credentials .         cannot be used for ntlm authentication: .        enter code here .          org.apache.commons.httpclient.usernamepasswordcredentials.      at org.apache.commons.httpclient.auth.ntlmscheme.authenticate(ntlmscheme.blank:332).        at org.apache.commons.httpclient.httpmethoddirector.authenticateproxy(httpmethoddirector.blank:320).      at org.apache.commons.httpclient.httpmethoddirector.executeconnect(httpmethoddirector.blank:491).      at org.apache.commons.httpclient.httpmethoddirector.executewithretry(httpmethoddirector.blank:391).      at org.apache.commons.httpclient.httpmethoddirector.executemethod(httpmethoddirector.blank:171).      at org.apache.commons.httpclient.httpclient.executemethod(httpclient.blank:397).      at org.apache.commons.httpclient.httpclient.executemethod(httpclient.blank:323).      at httpgetproxy.main(httpgetproxy.blank:31).  dec 08, 2017 1:41:39 pm org.apache.commons.httpclient.httpmethoddirector processproxyauthchallenge.  info: failure authenticating with ntlm @proxy.****.com:6050"\n'
Label: 1

레이블은 0, 1, 2 또는 3입니다. 이들이 어떠한 문자열 레이블에 해당하는지 확인하려면 데이터세트의 class_names 속성을 검사하면 됩니다.

for i, label in enumerate(raw_train_ds.class_names):
  print("Label", i, "corresponds to", label)
Label 0 corresponds to csharp
Label 1 corresponds to java
Label 2 corresponds to javascript
Label 3 corresponds to python

다음으로 tf.keras.utils.text_dataset_from_directory를 사용하여 검증 및 테스트 세트를 만듭니다. 검증을 위해 훈련 세트의 나머지 1,600개 리뷰를 사용합니다.

참고: tf.keras.utils.text_dataset_from_directoryvalidation_splitsubset 인수를 사용할 때 검증 및 훈련 분할이 겹치지 않도록 임의 시드를 지정하거나 shuffle=False를 전달하도록 합니다.

# Create a validation set.
raw_val_ds = utils.text_dataset_from_directory(
    train_dir,
    batch_size=batch_size,
    validation_split=0.2,
    subset='validation',
    seed=seed)
Found 8000 files belonging to 4 classes.
Using 1600 files for validation.
test_dir = dataset_dir/'test'

# Create a test set.
raw_test_ds = utils.text_dataset_from_directory(
    test_dir,
    batch_size=batch_size)
Found 8000 files belonging to 4 classes.

훈련을 위한 데이터세트 준비하기

다음으로 tf.keras.layers.TextVectorization 레이어를 사용하여 데이터를 표준화, 토큰화 및 벡터화합니다.

  • 표준화는 일반적으로 데이터세트를 단순화하기 위해 구두점이나 HTML 요소를 제거하도록 텍스트를 전처리하는 것을 일컫습니다.
  • 토큰화는 문자열을 토큰으로 분할하는 것을 일컫습니다(예: 문장을 공백을 사용하여 개별 단어로 분할).
  • 벡터화는 신경망에 제공할 수 있도록 토큰을 숫자로 변환하는 것을 일컫습니다.

위의 모든 작업은 이 레이어로 수행할 수 있습니다(tf.keras.layers.TextVectorization API 문서에서 각 작업에 대해 자세히 알아볼 수 있습니다).

참고 사항:

  • 기본 표준화는 텍스트를 소문자로 변환하고 구두점을 제거합니다(standardize='lower_and_strip_punctuation').
  • 기본 토큰화는 공백으로 분할합니다(split='whitespace').
  • 기본 벡터화 모드는 'int'(output_mode='int')입니다. 이 모드는 정수 인덱스를 출력합니다(토큰당 하나). 이 모드는 단어 순서를 고려하는 모델을 빌드하는 데 사용할 수 있습니다. 'binary'와 같은 다른 모드를 사용하여 bag-of-words 모델을 빌드할 수도 있습니다.

TextVectorization을 사용하는 표준화, 토큰화 및 벡터화에 대해 자세히 알아보기 위해 다음 두 가지 모델을 빌드합니다.

  • 먼저 'binary' 벡터화 모드를 사용하여 bag-of-words 모델을 빌드합니다.
  • 그런 다음 1D ConvNet에서 'int' 모드를 사용합니다.
VOCAB_SIZE = 10000

binary_vectorize_layer = TextVectorization(
    max_tokens=VOCAB_SIZE,
    output_mode='binary')

'int' 모드의 경우 최대 어휘 크기 외에 명시적인 최대 시퀀스 길이(MAX_SEQUENCE_LENGTH)를 설정해야 레이어가 패딩되거나 시퀀스를 정확히 output_sequence_length 값으로 자릅니다.

MAX_SEQUENCE_LENGTH = 250

int_vectorize_layer = TextVectorization(
    max_tokens=VOCAB_SIZE,
    output_mode='int',
    output_sequence_length=MAX_SEQUENCE_LENGTH)

다음으로 전처리 레이어의 상태를 데이터세트에 맞추기 위해 TextVectorization.adapt를 호출합니다. 그러면 모델이 문자열 인덱스를 정수로 빌드합니다.

참고: 테스트세트를 사용하면 정보가 누출되므로 TextVectorization.adapt를 호출할 때 훈련 데이터만 사용하는 것이 중요합니다.

# Make a text-only dataset (without labels), then call `TextVectorization.adapt`.
train_text = raw_train_ds.map(lambda text, labels: text)
binary_vectorize_layer.adapt(train_text)
int_vectorize_layer.adapt(train_text)

이러한 레이어를 사용하여 데이터를 전처리한 결과를 인쇄합니다.

def binary_vectorize_text(text, label):
  text = tf.expand_dims(text, -1)
  return binary_vectorize_layer(text), label
def int_vectorize_text(text, label):
  text = tf.expand_dims(text, -1)
  return int_vectorize_layer(text), label
# Retrieve a batch (of 32 reviews and labels) from the dataset.
text_batch, label_batch = next(iter(raw_train_ds))
first_question, first_label = text_batch[0], label_batch[0]
print("Question", first_question)
print("Label", first_label)
Question tf.Tensor(b'"what is the difference between these two ways to create an element? var a = document.createelement(\'div\');..a.id = ""mydiv"";...and..var a = document.createelement(\'div\').id = ""mydiv"";...what is the difference between them such that the first one works and the second one doesn\'t?"\n', shape=(), dtype=string)
Label tf.Tensor(2, shape=(), dtype=int32)
print("'binary' vectorized question:",
      binary_vectorize_text(first_question, first_label)[0])
'binary' vectorized question: tf.Tensor([[1. 1. 0. ... 0. 0. 0.]], shape=(1, 10000), dtype=float32)
print("'int' vectorized question:",
      int_vectorize_text(first_question, first_label)[0])
'int' vectorized question: tf.Tensor(
[[ 55   6   2 410 211 229 121 895   4 124  32 245  43   5   1   1   5   1
    1   6   2 410 211 191 318  14   2  98  71 188   8   2 199  71 178   0
    0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0
    0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0
    0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0
    0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0
    0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0
    0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0
    0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0
    0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0
    0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0
    0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0
    0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0
    0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0]], shape=(1, 250), dtype=int64)

위에 표시된 것처럼 TextVectorization'binary' 모드는 입력에 한 번 이상 존재하는 토큰을 나타내는 배열을 반환하는 반면 'int' 모드는 각 토큰을 정수로 대체하기에 원래 순서를 유지합니다.

레이어에서 TextVectorization.get_vocabulary를 호출하여 각 정수가 해당하는 토큰(문자열)을 조회할 수 있습니다.

print("1289 ---> ", int_vectorize_layer.get_vocabulary()[1289])
print("313 ---> ", int_vectorize_layer.get_vocabulary()[313])
print("Vocabulary size: {}".format(len(int_vectorize_layer.get_vocabulary())))
1289 --->  roman
313 --->  source
Vocabulary size: 10000

모델을 훈련할 준비가 거의 되었습니다.

최종 전처리 단계로 이전에 생성한 TextVectorization 레이어를 훈련, 검증 및 테스트 세트에 적용합니다.

binary_train_ds = raw_train_ds.map(binary_vectorize_text)
binary_val_ds = raw_val_ds.map(binary_vectorize_text)
binary_test_ds = raw_test_ds.map(binary_vectorize_text)

int_train_ds = raw_train_ds.map(int_vectorize_text)
int_val_ds = raw_val_ds.map(int_vectorize_text)
int_test_ds = raw_test_ds.map(int_vectorize_text)

성능을 높이도록 데이터세트 구성하기

다음은 I/O가 차단되지 않도록 데이터를 로드할 때 사용해야 하는 두 가지 중요한 메서드입니다.

  • Dataset.cache는 데이터가 디스크에서 로드된 후 메모리에 데이터를 보관합니다. 이렇게 하면 모델을 훈련하는 동안 데이터세트로 인한 병목 현상이 발생하지 않습니다. 데이터세트가 너무 커서 메모리에 맞지 않는 경우 이 메서드를 사용하여 성능이 뛰어난 온 디스크 캐시를 생성할 수도 있습니다. 다수의 작은 파일보다 읽기가 더 효율적입니다.
  • Dataset.prefetch는 훈련하는 동안 데이터 전처리 및 모델 실행을 중첩시킵니다.

tf.data API를 통한 성능 향상 가이드의 프리페칭 섹션에서 두 가지 메서드와 데이터를 디스크에 캐시하는 방법에 대해 자세히 알아볼 수 있습니다.

AUTOTUNE = tf.data.AUTOTUNE

def configure_dataset(dataset):
  return dataset.cache().prefetch(buffer_size=AUTOTUNE)
binary_train_ds = configure_dataset(binary_train_ds)
binary_val_ds = configure_dataset(binary_val_ds)
binary_test_ds = configure_dataset(binary_test_ds)

int_train_ds = configure_dataset(int_train_ds)
int_val_ds = configure_dataset(int_val_ds)
int_test_ds = configure_dataset(int_test_ds)

모델 훈련하기

이제 신경망을 만들 차례입니다.

'binary' 벡터화된 데이터의 경우 간단한 bag-of-words 선형 모델을 정의한 다음 데이터를 구성하고 훈련합니다.

binary_model = tf.keras.Sequential([layers.Dense(4)])

binary_model.compile(
    loss=losses.SparseCategoricalCrossentropy(from_logits=True),
    optimizer='adam',
    metrics=['accuracy'])

history = binary_model.fit(
    binary_train_ds, validation_data=binary_val_ds, epochs=10)
Epoch 1/10
200/200 [==============================] - 2s 5ms/step - loss: 1.1217 - accuracy: 0.6442 - val_loss: 0.9182 - val_accuracy: 0.7837
Epoch 2/10
200/200 [==============================] - 1s 3ms/step - loss: 0.7805 - accuracy: 0.8181 - val_loss: 0.7533 - val_accuracy: 0.8056
Epoch 3/10
200/200 [==============================] - 1s 3ms/step - loss: 0.6291 - accuracy: 0.8609 - val_loss: 0.6673 - val_accuracy: 0.8131
Epoch 4/10
200/200 [==============================] - 1s 3ms/step - loss: 0.5355 - accuracy: 0.8861 - val_loss: 0.6135 - val_accuracy: 0.8238
Epoch 5/10
200/200 [==============================] - 1s 3ms/step - loss: 0.4694 - accuracy: 0.9031 - val_loss: 0.5767 - val_accuracy: 0.8344
Epoch 6/10
200/200 [==============================] - 1s 3ms/step - loss: 0.4191 - accuracy: 0.9150 - val_loss: 0.5499 - val_accuracy: 0.8381
Epoch 7/10
200/200 [==============================] - 1s 3ms/step - loss: 0.3788 - accuracy: 0.9270 - val_loss: 0.5296 - val_accuracy: 0.8394
Epoch 8/10
200/200 [==============================] - 1s 3ms/step - loss: 0.3454 - accuracy: 0.9364 - val_loss: 0.5139 - val_accuracy: 0.8400
Epoch 9/10
200/200 [==============================] - 1s 3ms/step - loss: 0.3171 - accuracy: 0.9411 - val_loss: 0.5016 - val_accuracy: 0.8406
Epoch 10/10
200/200 [==============================] - 1s 3ms/step - loss: 0.2927 - accuracy: 0.9483 - val_loss: 0.4917 - val_accuracy: 0.8419

그런 다음 'int' 벡터화된 레이어를 사용하여 1D ConvNet을 빌드합니다.

def create_model(vocab_size, num_labels):
  model = tf.keras.Sequential([
      layers.Embedding(vocab_size, 64, mask_zero=True),
      layers.Conv1D(64, 5, padding="valid", activation="relu", strides=2),
      layers.GlobalMaxPooling1D(),
      layers.Dense(num_labels)
  ])
  return model
# `vocab_size` is `VOCAB_SIZE + 1` since `0` is used additionally for padding.
int_model = create_model(vocab_size=VOCAB_SIZE + 1, num_labels=4)
int_model.compile(
    loss=losses.SparseCategoricalCrossentropy(from_logits=True),
    optimizer='adam',
    metrics=['accuracy'])
history = int_model.fit(int_train_ds, validation_data=int_val_ds, epochs=5)
Epoch 1/5
200/200 [==============================] - 3s 4ms/step - loss: 1.1294 - accuracy: 0.5203 - val_loss: 0.7556 - val_accuracy: 0.6975
Epoch 2/5
200/200 [==============================] - 1s 4ms/step - loss: 0.6195 - accuracy: 0.7614 - val_loss: 0.5407 - val_accuracy: 0.7950
Epoch 3/5
200/200 [==============================] - 1s 4ms/step - loss: 0.3703 - accuracy: 0.8869 - val_loss: 0.4717 - val_accuracy: 0.8231
Epoch 4/5
200/200 [==============================] - 1s 4ms/step - loss: 0.2032 - accuracy: 0.9527 - val_loss: 0.4726 - val_accuracy: 0.8200
Epoch 5/5
200/200 [==============================] - 1s 4ms/step - loss: 0.1002 - accuracy: 0.9822 - val_loss: 0.4988 - val_accuracy: 0.8169

두 모델을 비교합니다.

print("Linear model on binary vectorized data:")
print(binary_model.summary())
Linear model on binary vectorized data:
Model: "sequential"
_________________________________________________________________
 Layer (type)                Output Shape              Param #   
=================================================================
 dense (Dense)               (None, 4)                 40004     
                                                                 
=================================================================
Total params: 40,004
Trainable params: 40,004
Non-trainable params: 0
_________________________________________________________________
None
print("ConvNet model on int vectorized data:")
print(int_model.summary())
ConvNet model on int vectorized data:
Model: "sequential_1"
_________________________________________________________________
 Layer (type)                Output Shape              Param #   
=================================================================
 embedding (Embedding)       (None, None, 64)          640064    
                                                                 
 conv1d (Conv1D)             (None, None, 64)          20544     
                                                                 
 global_max_pooling1d (Globa  (None, 64)               0         
 lMaxPooling1D)                                                  
                                                                 
 dense_1 (Dense)             (None, 4)                 260       
                                                                 
=================================================================
Total params: 660,868
Trainable params: 660,868
Non-trainable params: 0
_________________________________________________________________
None

테스트 데이터에서 두 모델을 평가합니다.

binary_loss, binary_accuracy = binary_model.evaluate(binary_test_ds)
int_loss, int_accuracy = int_model.evaluate(int_test_ds)

print("Binary model accuracy: {:2.2%}".format(binary_accuracy))
print("Int model accuracy: {:2.2%}".format(int_accuracy))
250/250 [==============================] - 1s 3ms/step - loss: 0.5180 - accuracy: 0.8164
250/250 [==============================] - 1s 2ms/step - loss: 0.5057 - accuracy: 0.8150
Binary model accuracy: 81.64%
Int model accuracy: 81.50%

참고: 이 예제 데이터세트는 다소 단순한 분류 문제를 나타냅니다. 더 복잡한 데이터세트와 문제는 전처리 전략과 모델 아키텍처에서 미묘하지만 중요한 차이를 나타냅니다. 다양한 접근 방식을 비교하려면 다양한 하이퍼 매개 변수와 epochs를 시도해야 합니다.

모델 내보내기

위의 코드에서는 모델에 텍스트를 제공하기 전에 tf.keras.layers.TextVectorization을 데이터세트에 적용했습니다. 모델이 원시 문자열을 처리할 수 있도록 하려면(예: 배포를 단순화하기 위해) 모델 내부에 TextVectorization 레이어를 포함할 수 있습니다.

이를 위해 방금 훈련한 가중치를 사용하여 새 모델을 만들 수 있습니다.

export_model = tf.keras.Sequential(
    [binary_vectorize_layer, binary_model,
     layers.Activation('sigmoid')])

export_model.compile(
    loss=losses.SparseCategoricalCrossentropy(from_logits=False),
    optimizer='adam',
    metrics=['accuracy'])

# Test it with `raw_test_ds`, which yields raw strings
loss, accuracy = export_model.evaluate(raw_test_ds)
print("Accuracy: {:2.2%}".format(binary_accuracy))
250/250 [==============================] - 1s 4ms/step - loss: 0.5180 - accuracy: 0.8164
Accuracy: 81.64%

이제 여러분의 모델은 원시 문자열을 입력으로 사용하고 Model.predict를 사용하여 각 레이블의 점수를 예측할 수 있습니다. 다음과 같이 최대 점수를 가진 레이블을 찾는 함수를 정의합니다.

def get_string_labels(predicted_scores_batch):
  predicted_int_labels = tf.math.argmax(predicted_scores_batch, axis=1)
  predicted_labels = tf.gather(raw_train_ds.class_names, predicted_int_labels)
  return predicted_labels

새 데이터에 대한 추론 실행하기

inputs = [
    "how do I extract keys from a dict into a list?",  # 'python'
    "debug public static void main(string[] args) {...}",  # 'java'
]
predicted_scores = export_model.predict(inputs)
predicted_labels = get_string_labels(predicted_scores)
for input, label in zip(inputs, predicted_labels):
  print("Question: ", input)
  print("Predicted label: ", label.numpy())
Question:  how do I extract keys from a dict into a list?
Predicted label:  b'python'
Question:  debug public static void main(string[] args) {...}
Predicted label:  b'java'

모델 내부에 텍스트 전처리 논리를 포함하면 배포를 단순화하고 훈련/테스트 왜곡 가능성을 줄이는 프로덕션용 모델을 내보낼 수 있습니다.

tf.keras.layers.TextVectorization를 적용할 위치를 선택할 때 염두에 두어야 할 성능 차이가 있습니다. 레이어를 모델 외부에서 사용하면 GPU에서 훈련할 때 비동기 CPU 처리 및 데이터 버퍼링을 수행할 수 있습니다. 따라서 GPU에서 모델을 훈련하는 경우 모델을 개발하는 동안 최상의 성능을 얻기 위해 이 옵션을 사용하고 배포 준비가 완료되면 모델 내부에 TextVectorization 레이어를 포함하도록 전환할 수 있습니다.

모델 저장에 대해 자세히 알아보려면 모델 저장과 복원 튜토리얼을 방문하세요.

예제 2: 일리아드(Iliad) 번역의 작성자 예측하기

다음은 tf.data.TextLineDataset를 사용하여 텍스트 파일로부터 예제를 로드하고 TensorFlow Text를 사용하여 데이터를 전처리하는 예를 제공합니다. 호머의 일리아드 작품을 다르게 번역한 3개의 영어 번역문을 사용하게 되며, 한 줄의 텍스트가 제공되었을 때 번역가를 식별하는 모델을 훈련합니다.

데이터세트 다운로드 및 탐색하기

3가지 번역본은 다음과 같습니다.

이 튜토리얼에서 사용된 텍스트 파일은 문서 헤더와 바닥 글, 줄 번호 및 챕터 제목 등을 제거하는 일반적인 전처리 작업을 거쳤습니다.

이 가볍게 손질한 파일을 로컬로 다운로드합니다.

DIRECTORY_URL = 'https://storage.googleapis.com/download.tensorflow.org/data/illiad/'
FILE_NAMES = ['cowper.txt', 'derby.txt', 'butler.txt']

for name in FILE_NAMES:
  text_dir = utils.get_file(name, origin=DIRECTORY_URL + name)

parent_dir = pathlib.Path(text_dir).parent
list(parent_dir.iterdir())
Downloading data from https://storage.googleapis.com/download.tensorflow.org/data/illiad/cowper.txt
819200/815980 [==============================] - 0s 0us/step
827392/815980 [==============================] - 0s 0us/step
Downloading data from https://storage.googleapis.com/download.tensorflow.org/data/illiad/derby.txt
811008/809730 [==============================] - 0s 0us/step
819200/809730 [==============================] - 0s 0us/step
Downloading data from https://storage.googleapis.com/download.tensorflow.org/data/illiad/butler.txt
811008/807992 [==============================] - 0s 0us/step
819200/807992 [==============================] - 0s 0us/step
[PosixPath('/home/kbuilder/.keras/datasets/mnist.npz'),
 PosixPath('/home/kbuilder/.keras/datasets/heart.csv'),
 PosixPath('/home/kbuilder/.keras/datasets/fashion-mnist'),
 PosixPath('/home/kbuilder/.keras/datasets/derby.txt'),
 PosixPath('/home/kbuilder/.keras/datasets/HIGGS.csv.gz'),
 PosixPath('/home/kbuilder/.keras/datasets/butler.txt'),
 PosixPath('/home/kbuilder/.keras/datasets/cowper.txt')]

데이터세트 로드하기

이전에는 tf.keras.utils.text_dataset_from_directory를 사용할 경우 파일의 모든 콘텐츠를 단일 예제로 취급했습니다. 여기에서는 텍스트 파일로부터 tf.data.Dataset를 생성하도록 설계된 tf.data.TextLineDataset을 사용합니다. 이때 각 예제는 원본 파일의 텍스트 줄입니다. TextLineDataset은 주로 줄 기반의 텍스트 데이터(예: 시 또는 오류 로그)에 유용합니다.

이러한 파일을 반복하여 각 파일을 자체 데이터세트에 로드합니다. 각 예제는 개별적으로 레이블을 지정해야 하므로 Dataset.map을 사용하여 각 예제에 labeler 함수를 적용합니다. 이렇게 하면 데이터세트의 모든 예제를 반복하여 (example, label) 쌍을 반환합니다.

def labeler(example, index):
  return example, tf.cast(index, tf.int64)
labeled_data_sets = []

for i, file_name in enumerate(FILE_NAMES):
  lines_dataset = tf.data.TextLineDataset(str(parent_dir/file_name))
  labeled_dataset = lines_dataset.map(lambda ex: labeler(ex, i))
  labeled_data_sets.append(labeled_dataset)

다음으로 Dataset.concatenate를 사용하여 레이블이 지정된 데이터세트를 단일 데이터세트로 결합한 후 Dataset.shuffle을 사용하여 셔플합니다.

BUFFER_SIZE = 50000
BATCH_SIZE = 64
VALIDATION_SIZE = 5000
all_labeled_data = labeled_data_sets[0]
for labeled_dataset in labeled_data_sets[1:]:
  all_labeled_data = all_labeled_data.concatenate(labeled_dataset)

all_labeled_data = all_labeled_data.shuffle(
    BUFFER_SIZE, reshuffle_each_iteration=False)

이전과 같이 몇 가지 예제를 출력합니다. 데이터세트가 아직 일괄 처리되지 않았으므로 all_labeled_data의 각 항목은 하나의 데이터 포인트에 해당합니다.

for text, label in all_labeled_data.take(10):
  print("Sentence: ", text.numpy())
  print("Label:", label.numpy())
Sentence:  b"They slew, and shar'd, by tents, the ev'ning meal."
Label: 1
Sentence:  b'Achaeans, have them. At daybreak we will arm and fight about the ships;'
Label: 2
Sentence:  b'To whom, the foam-sprung Goddess, thus incensed.'
Label: 0
Sentence:  b'A respite (if the Atrid\xc3\xa6 so incline)'
Label: 0
Sentence:  b'therefore, let us all do as I say; we have eaten and drunk our fill,'
Label: 2
Sentence:  b'While morning lasted, and the light of day'
Label: 0
Sentence:  b'And, lifeless, instant, on the field he fell.'
Label: 0
Sentence:  b"Which bear renown'd Achilles o'er the field."
Label: 0
Sentence:  b'However short, to breathe again, so close'
Label: 0
Sentence:  b'through all their ranks, broke the battalions of the enemy. Agamemnon'
Label: 2

훈련을 위한 데이터세트 준비하기

tf.keras.layers.TextVectorization을 사용하여 텍스트 데이터세트를 전처리하는 대신에 이제는 TensorFlow Text API를 사용하여 데이터를 표준화 및 토큰화하고 어휘를 빌드하고, tf.lookup.StaticVocabularyTable을 사용하여 토큰을 모델에 정수에 매핑한 후 모델에 공급합니다(TensorFlow 텍스트에 대해 자세히 알아보기).

다음과 같이 텍스트를 소문자로 변환하고 토큰화하는 함수를 정의합니다.

  • TensorFlow Text는 다양한 토크나이저를 제공합니다. 이 예제에서는 text.UnicodeScriptTokenizer를 사용하여 데이터세트를 토큰화합니다.
  • 여러분은 Dataset.map을 사용하여 데이터세트에 토큰화를 적용합니다.
tokenizer = tf_text.UnicodeScriptTokenizer()
def tokenize(text, unused_label):
  lower_case = tf_text.case_fold_utf8(text)
  return tokenizer.tokenize(lower_case)
tokenized_ds = all_labeled_data.map(tokenize)

데이터세트를 반복하고 몇 가지 토큰화된 예제를 출력할 수 있습니다.

for text_batch in tokenized_ds.take(5):
  print("Tokens: ", text_batch.numpy())
Tokens:  [b'they' b'slew' b',' b'and' b'shar' b"'" b'd' b',' b'by' b'tents' b','
 b'the' b'ev' b"'" b'ning' b'meal' b'.']
Tokens:  [b'achaeans' b',' b'have' b'them' b'.' b'at' b'daybreak' b'we' b'will'
 b'arm' b'and' b'fight' b'about' b'the' b'ships' b';']
Tokens:  [b'to' b'whom' b',' b'the' b'foam' b'-' b'sprung' b'goddess' b',' b'thus'
 b'incensed' b'.']
Tokens:  [b'a' b'respite' b'(' b'if' b'the' b'atrid\xc3\xa6' b'so' b'incline' b')']
Tokens:  [b'therefore' b',' b'let' b'us' b'all' b'do' b'as' b'i' b'say' b';' b'we'
 b'have' b'eaten' b'and' b'drunk' b'our' b'fill' b',']

다음으로 빈도별로 토큰을 정렬하고 상위 VOCAB_SIZE 토큰을 유지하여 어휘를 빌드합니다.

tokenized_ds = configure_dataset(tokenized_ds)

vocab_dict = collections.defaultdict(lambda: 0)
for toks in tokenized_ds.as_numpy_iterator():
  for tok in toks:
    vocab_dict[tok] += 1

vocab = sorted(vocab_dict.items(), key=lambda x: x[1], reverse=True)
vocab = [token for token, count in vocab]
vocab = vocab[:VOCAB_SIZE]
vocab_size = len(vocab)
print("Vocab size: ", vocab_size)
print("First five vocab entries:", vocab[:5])
Vocab size:  10000
First five vocab entries: [b',', b'the', b'and', b"'", b'of']

토큰을 정수로 변환하려면 vocab 세트를 사용하여 tf.lookup.StaticVocabularyTable을 생성합니다. [2, vocab_size + 2] 범위의 정수에 토큰을 매핑합니다. TextVectorization 레이어와 마찬가지로 0은 패딩을 나타내기 위해 예약되어 있으며 1은 OOV(out-of-vocabulary) 토큰을 나타내기 위해 예약되어 있습니다.

keys = vocab
values = range(2, len(vocab) + 2)  # Reserve `0` for padding, `1` for OOV tokens.

init = tf.lookup.KeyValueTensorInitializer(
    keys, values, key_dtype=tf.string, value_dtype=tf.int64)

num_oov_buckets = 1
vocab_table = tf.lookup.StaticVocabularyTable(init, num_oov_buckets)

마지막으로 토크나이저 및 조회 테이블을 사용하여 데이터세트를 표준화, 토큰화 및 벡터화하는 함수를 정의합니다.

def preprocess_text(text, label):
  standardized = tf_text.case_fold_utf8(text)
  tokenized = tokenizer.tokenize(standardized)
  vectorized = vocab_table.lookup(tokenized)
  return vectorized, label

결과를 출력하기 위해 단일 예제에서 다음을 시도할 수 있습니다.

example_text, example_label = next(iter(all_labeled_data))
print("Sentence: ", example_text.numpy())
vectorized_text, example_label = preprocess_text(example_text, example_label)
print("Vectorized sentence: ", vectorized_text.numpy())
Sentence:  b"They slew, and shar'd, by tents, the ev'ning meal."
Vectorized sentence:  [  27  307    2    4 2902    5    9    2   26  617    2    3  543    5
  885 1007    7]

이제 Dataset.map을 사용하여 데이터세트에서 전처리 함수를 실행합니다.

all_encoded_data = all_labeled_data.map(preprocess_text)

데이터세트를 훈련 및 검증 세트로 분할하기

Keras TextVectorization 레이어는 벡터화된 데이터도 일괄 처리하고 패딩합니다. 배치 내부의 예제는 크기와 모양이 같아야 하기 때문에 패딩이 필요하지만 이러한 데이터세트의 예제는 모두 같은 크기가 아니며 각 텍스트 줄의 단어 수도 다릅니다.

tf.data.Dataset은 데이터세트 분할 및 패딩 일괄 처리를 지원합니다.

train_data = all_encoded_data.skip(VALIDATION_SIZE).shuffle(BUFFER_SIZE)
validation_data = all_encoded_data.take(VALIDATION_SIZE)
train_data = train_data.padded_batch(BATCH_SIZE)
validation_data = validation_data.padded_batch(BATCH_SIZE)

이제 validation_datatrain_data는 (example, label) 쌍의 모음이 아니라 배치의 모음입니다. 각 배치는 배열로 표시되는 한 쌍의 (많은 예제, 많은 레이블)입니다.

이는 다음과 같습니다.

sample_text, sample_labels = next(iter(validation_data))
print("Text batch shape: ", sample_text.shape)
print("Label batch shape: ", sample_labels.shape)
print("First text example: ", sample_text[0])
print("First label example: ", sample_labels[0])
Text batch shape:  (64, 18)
Label batch shape:  (64,)
First text example:  tf.Tensor(
[  27  307    2    4 2902    5    9    2   26  617    2    3  543    5
  885 1007    7    0], shape=(18,), dtype=int64)
First label example:  tf.Tensor(1, shape=(), dtype=int64)

패딩에는 0을 사용하고 OOV(out-of-vocabulary) 토큰에는 1을 사용하였기에 어휘 크기가 2배 증가했습니다.

vocab_size += 2

이전과 같은 더 나은 성능을 위한 데이터세트를 구성합니다.

train_data = configure_dataset(train_data)
validation_data = configure_dataset(validation_data)

모델 훈련하기

이전과 같이 이 데이터세트에서 모델을 훈련할 수 있습니다.

model = create_model(vocab_size=vocab_size, num_labels=3)

model.compile(
    optimizer='adam',
    loss=losses.SparseCategoricalCrossentropy(from_logits=True),
    metrics=['accuracy'])

history = model.fit(train_data, validation_data=validation_data, epochs=3)
Epoch 1/3
697/697 [==============================] - 28s 9ms/step - loss: 0.5224 - accuracy: 0.7657 - val_loss: 0.3604 - val_accuracy: 0.8448
Epoch 2/3
697/697 [==============================] - 3s 4ms/step - loss: 0.2856 - accuracy: 0.8826 - val_loss: 0.3490 - val_accuracy: 0.8540
Epoch 3/3
697/697 [==============================] - 3s 4ms/step - loss: 0.1931 - accuracy: 0.9262 - val_loss: 0.3780 - val_accuracy: 0.8496
loss, accuracy = model.evaluate(validation_data)

print("Loss: ", loss)
print("Accuracy: {:2.2%}".format(accuracy))
79/79 [==============================] - 1s 2ms/step - loss: 0.3780 - accuracy: 0.8496
Loss:  0.3780021369457245
Accuracy: 84.96%

모델 내보내기

원시 문자열을 입력으로 사용할 수 있는 모델을 만들기 위해 사용자 정의 전처리 함수와 동일한 단계를 수행하는 Keras TextVectorization 레이어를 생성하게 됩니다. 이미 어휘를 훈련했으므로 TextVectorization.adapt 대신 TextVectorization.set_vocabulary를 사용하여 새 어휘를 훈련할 수 있습니다.

preprocess_layer = TextVectorization(
    max_tokens=vocab_size,
    standardize=tf_text.case_fold_utf8,
    split=tokenizer.tokenize,
    output_mode='int',
    output_sequence_length=MAX_SEQUENCE_LENGTH)

preprocess_layer.set_vocabulary(vocab)
/tmpfs/src/tf_docs_env/lib/python3.9/site-packages/numpy/core/numeric.py:2468: FutureWarning: elementwise comparison failed; returning scalar instead, but in the future will perform elementwise comparison
  return bool(asarray(a1 == a2).all())
/tmpfs/src/tf_docs_env/lib/python3.9/site-packages/keras/layers/preprocessing/index_lookup.py:458: FutureWarning: elementwise comparison failed; returning scalar instead, but in the future will perform elementwise comparison
  if self.mask_token is not None and self.mask_token in tokens:
export_model = tf.keras.Sequential(
    [preprocess_layer, model,
     layers.Activation('sigmoid')])

export_model.compile(
    loss=losses.SparseCategoricalCrossentropy(from_logits=False),
    optimizer='adam',
    metrics=['accuracy'])
# Create a test dataset of raw strings.
test_ds = all_labeled_data.take(VALIDATION_SIZE).batch(BATCH_SIZE)
test_ds = configure_dataset(test_ds)

loss, accuracy = export_model.evaluate(test_ds)

print("Loss: ", loss)
print("Accuracy: {:2.2%}".format(accuracy))
2022-12-14 20:58:05.420812: W tensorflow/core/grappler/optimizers/loop_optimizer.cc:907] Skipping loop optimization for Merge node with control input: sequential_4/text_vectorization_2/UnicodeScriptTokenize/Assert_1/AssertGuard/branch_executed/_185
79/79 [==============================] - 6s 8ms/step - loss: 0.5388 - accuracy: 0.7914
Loss:  0.5388292670249939
Accuracy: 79.14%

인코딩된 검증 세트의 모델과 원시 검증 세트에 대해 내보내기를 수행한 모델의 손실 및 정확성은 예상대로 동일합니다.

새 데이터에 대한 추론 실행하기

inputs = [
    "Join'd to th' Ionians with their flowing robes,",  # Label: 1
    "the allies, and his armour flashed about him so that he seemed to all",  # Label: 2
    "And with loud clangor of his arms he fell.",  # Label: 0
]

predicted_scores = export_model.predict(inputs)
predicted_labels = tf.math.argmax(predicted_scores, axis=1)

for input, label in zip(inputs, predicted_labels):
  print("Question: ", input)
  print("Predicted label: ", label.numpy())
2022-12-14 20:58:08.859336: W tensorflow/core/grappler/optimizers/loop_optimizer.cc:907] Skipping loop optimization for Merge node with control input: sequential_4/text_vectorization_2/UnicodeScriptTokenize/Assert_1/AssertGuard/branch_executed/_185
Question:  Join'd to th' Ionians with their flowing robes,
Predicted label:  1
Question:  the allies, and his armour flashed about him so that he seemed to all
Predicted label:  2
Question:  And with loud clangor of his arms he fell.
Predicted label:  0

TensorFlow 데이터세트(TFDS)를 사용하여 더 많은 데이터세트 다운로드하기

TensorFlow 데이터세트로부터 더 많은 데이터세트를 다운로드할 수 있습니다.

이 예제에서는 IMDB 대형 영화 리뷰 데이터세트를 사용하여 감정 분류용 모델을 훈련합니다.

# Training set.
train_ds = tfds.load(
    'imdb_reviews',
    split='train[:80%]',
    batch_size=BATCH_SIZE,
    shuffle_files=True,
    as_supervised=True)
# Validation set.
val_ds = tfds.load(
    'imdb_reviews',
    split='train[80%:]',
    batch_size=BATCH_SIZE,
    shuffle_files=True,
    as_supervised=True)

몇 가지 예제를 출력합니다.

for review_batch, label_batch in val_ds.take(1):
  for i in range(5):
    print("Review: ", review_batch[i].numpy())
    print("Label: ", label_batch[i].numpy())
Review:  b"Instead, go to the zoo, buy some peanuts and feed 'em to the monkeys. Monkeys are funny. People with amnesia who don't say much, just sit there with vacant eyes are not all that funny.<br /><br />Black comedy? There isn't a black person in it, and there isn't one funny thing in it either.<br /><br />Walmart buys these things up somehow and puts them on their dollar rack. It's labeled Unrated. I think they took out the topless scene. They may have taken out other stuff too, who knows? All we know is that whatever they took out, isn't there any more.<br /><br />The acting seemed OK to me. There's a lot of unfathomables tho. It's supposed to be a city? It's supposed to be a big lake? If it's so hot in the church people are fanning themselves, why are they all wearing coats?"
Label:  0
Review:  b'Well, was Morgan Freeman any more unusual as God than George Burns? This film sure was better than that bore, "Oh, God". I was totally engrossed and LMAO all the way through. Carrey was perfect as the out of sorts anchorman wannabe, and Aniston carried off her part as the frustrated girlfriend in her usual well played performance. I, for one, don\'t consider her to be either ugly or untalented. I think my favorite scene was when Carrey opened up the file cabinet thinking it could never hold his life history. See if you can spot the file in the cabinet that holds the events of his bathroom humor: I was rolling over this one. Well written and even better played out, this comedy will go down as one of this funnyman\'s best.'
Label:  1
Review:  b'I remember stumbling upon this special while channel-surfing in 1965. I had never heard of Barbra before. When the show was over, I thought "This is probably the best thing on TV I will ever see in my life." 42 years later, that has held true. There is still nothing so amazing, so honestly astonishing as the talent that was displayed here. You can talk about all the super-stars you want to, this is the most superlative of them all!<br /><br />You name it, she can do it. Comedy, pathos, sultry seduction, ballads, Barbra is truly a story-teller. Her ability to pull off anything she attempts is legendary. But this special was made in the beginning, and helped to create the legend that she quickly became. In spite of rising so far in such a short time, she has fulfilled the promise, revealing more of her talents as she went along. But they are all here from the very beginning. You will not be disappointed in viewing this.'
Label:  1
Review:  b"Firstly, I would like to point out that people who have criticised this film have made some glaring errors. Anything that has a rating below 6/10 is clearly utter nonsense.<br /><br />Creep is an absolutely fantastic film with amazing film effects. The actors are highly believable, the narrative thought provoking and the horror and graphical content extremely disturbing. <br /><br />There is much mystique in this film. Many questions arise as the audience are revealed to the strange and freakish creature that makes habitat in the dark rat ridden tunnels. How was 'Craig' created and what happened to him?<br /><br />A fantastic film with a large chill factor. A film with so many unanswered questions and a film that needs to be appreciated along with others like 28 Days Later, The Bunker, Dog Soldiers and Deathwatch.<br /><br />Look forward to more of these fantastic films!!"
Label:  1
Review:  b"I'm sorry but I didn't like this doc very much. I can think of a million ways it could have been better. The people who made it obviously don't have much imagination. The interviews aren't very interesting and no real insight is offered. The footage isn't assembled in a very informative way, either. It's too bad because this is a movie that really deserves spellbinding special features. One thing I'll say is that Isabella Rosselini gets more beautiful the older she gets. All considered, this only gets a '4.'"
Label:  0

이제 이전과 같이 데이터를 전처리하고 모델을 훈련할 수 있습니다.

참고: 이진 분류 문제이므로 여러분의 모델에 tf.keras.losses.SparseCategoricalCrossentropy 대신 tf.keras.losses.BinaryCrossentropy를 사용합니다.

훈련을 위한 데이터세트 준비하기

vectorize_layer = TextVectorization(
    max_tokens=VOCAB_SIZE,
    output_mode='int',
    output_sequence_length=MAX_SEQUENCE_LENGTH)

# Make a text-only dataset (without labels), then call `TextVectorization.adapt`.
train_text = train_ds.map(lambda text, labels: text)
vectorize_layer.adapt(train_text)
def vectorize_text(text, label):
  text = tf.expand_dims(text, -1)
  return vectorize_layer(text), label
train_ds = train_ds.map(vectorize_text)
val_ds = val_ds.map(vectorize_text)
# Configure datasets for performance as before.
train_ds = configure_dataset(train_ds)
val_ds = configure_dataset(val_ds)

모델 생성, 구성 및 훈련하기

model = create_model(vocab_size=VOCAB_SIZE + 1, num_labels=1)
model.summary()
Model: "sequential_5"
_________________________________________________________________
 Layer (type)                Output Shape              Param #   
=================================================================
 embedding_2 (Embedding)     (None, None, 64)          640064    
                                                                 
 conv1d_2 (Conv1D)           (None, None, 64)          20544     
                                                                 
 global_max_pooling1d_2 (Glo  (None, 64)               0         
 balMaxPooling1D)                                                
                                                                 
 dense_3 (Dense)             (None, 1)                 65        
                                                                 
=================================================================
Total params: 660,673
Trainable params: 660,673
Non-trainable params: 0
_________________________________________________________________
model.compile(
    loss=losses.BinaryCrossentropy(from_logits=True),
    optimizer='adam',
    metrics=['accuracy'])
history = model.fit(train_ds, validation_data=val_ds, epochs=3)
Epoch 1/3
313/313 [==============================] - 3s 6ms/step - loss: 0.5353 - accuracy: 0.6667 - val_loss: 0.3715 - val_accuracy: 0.8270
Epoch 2/3
313/313 [==============================] - 1s 4ms/step - loss: 0.2973 - accuracy: 0.8699 - val_loss: 0.3170 - val_accuracy: 0.8552
Epoch 3/3
313/313 [==============================] - 1s 4ms/step - loss: 0.1812 - accuracy: 0.9301 - val_loss: 0.3233 - val_accuracy: 0.8606
loss, accuracy = model.evaluate(val_ds)

print("Loss: ", loss)
print("Accuracy: {:2.2%}".format(accuracy))
79/79 [==============================] - 0s 2ms/step - loss: 0.3233 - accuracy: 0.8606
Loss:  0.32325324416160583
Accuracy: 86.06%

모델 내보내기

export_model = tf.keras.Sequential(
    [vectorize_layer, model,
     layers.Activation('sigmoid')])

export_model.compile(
    loss=losses.SparseCategoricalCrossentropy(from_logits=False),
    optimizer='adam',
    metrics=['accuracy'])
# 0 --> negative review
# 1 --> positive review
inputs = [
    "This is a fantastic movie.",
    "This is a bad movie.",
    "This movie was so bad that it was good.",
    "I will never say yes to watching this movie.",
]

predicted_scores = export_model.predict(inputs)
predicted_labels = [int(round(x[0])) for x in predicted_scores]

for input, label in zip(inputs, predicted_labels):
  print("Question: ", input)
  print("Predicted label: ", label)
Question:  This is a fantastic movie.
Predicted label:  1
Question:  This is a bad movie.
Predicted label:  0
Question:  This movie was so bad that it was good.
Predicted label:  1
Question:  I will never say yes to watching this movie.
Predicted label:  1

결론

이 튜토리얼에서는 텍스트를 로드하고 전처리하는 여러 방법을 보여 드렸습니다. 다음 단계로 다음과 같은 추가 텍스트 전처리 TensorFlow 텍스트 튜토리얼을 탐색할 수 있습니다.

TensorFlow 데이터세트에서 새 데이터세트를 찾을 수도 있습니다. 그리고 tf.data에 대해 자세히 알아보려면 입력 파이프라인 빌드 가이드를 확인하세요.