반응형

디스크에 저장되어 있는 텍스트 파일을 입력으로 사용하여  텍스트 분류를 해봅니다.

IMDB 데이터셋을 가지고 감성 분석 (Sentiment analysis)을 위해 이진 분류를 학습시킵니다.



텐서플로우 튜토리얼을 보며 작성한 글입니다. 

아직 공부하는 중인 문서라 틀린 점이 있을 수 있습니다.



2021. 05. 09  최초작성

 

원문

https://www.tensorflow.org/tutorials/keras/text_classification

Last updated 2021-04-23 UTC.



테스트에 사용한 파이썬과 텐서플로우 버전입니다. 

 

C:\Users\webnautes>python

Python 3.7.7 (tags/v3.7.7:d7c567b08f, Mar 10 2020, 10:41:24) [MSC v.1900 64 bit (AMD64)] on win32

Type "help", "copyright", "credits" or "license" for more information.

>>> import tensorflow as tf

2021-01-14 20:06:37.129877: I tensorflow/stream_executor/platform/default/dso_loader.cc:49] Successfully opened dynamic library cudart64_110.dll

>>> tf.__version__

'2.4.0'




모델을 학습시킬 때 숫자로 이루어진 벡터를 입력으로 사용해야 하기 때문에 텍스트를 입력으로 사용하려면 모델의 입력으로 사용하기 전에 문자열을 숫자로 변환하는 작업이 필요합니다.  

 

이때 단어 임베딩(Word embeddings)을 사용합니다.  단어 임베딩을 사용하면 유사한 단어는 유사한 인코딩을 갖게 됩니다. 인코딩을 직접 지정할 필요가 없습니다. 임베딩은 실수값입니다. ( 벡터의 길이는 사용자가 지정하는 매개변수입니다.)

임베딩 값을 수동으로 지정하는 대신 학습 가능한 매개 변수입니다.(모델이 덴스 레이어에 대한 가중치를 학습하는 것과 동일한 방식으로 학습 중에 모델이 학습한 가중치.)

 

작은 데이터 셋의 경우 8차원의 단어 임베딩, 대규모 데이터 셋인 경우 최대 1024 차원의 단어 임베딩을 합니다. 

 

더 높은 차원의 임베딩은 단어 간의 세밀한 관계를 캡처 할 수 있지만 학습하는 데 더 많은 데이터가 필요합니다.



4차원 벡터 워드 임베딩의 예입니다.

각 단어는 실수값 원소로 구성된 4 차원 벡터로 표시됩니다.

임베딩을 생각하는 또 다른 방법은 "lookup table"입니다.

 

이러한 가중치를 학습 한 후 표에서 해당하는 벡터를 찾아 각 단어를 정수로 인코딩 할 수 있습니다.





본 튜토리얼은 디스크에 저장된 텍스트 파일을 가지고 텍스트 분류를 하는 방법을 보여줍니다.  IMDB 데이터 세트에 대한 감정 분석을 수행하도록 이진 분류기를 학습시킵니다. 



import matplotlib.pyplot as plt

import os

import re

import shutil

import string

import tensorflow as tf


from tensorflow.keras import layers

from tensorflow.keras import losses

from tensorflow.keras import preprocessing

from tensorflow.keras.layers.experimental.preprocessing import TextVectorization



감성 분석(Sentiment analysis)

 

영화 리뷰가 저장되어 있는 텍스트 파일을 데이터셋으로 사용하여 긍정적 리뷰와 부정적 리뷰로 분류하는 감성 분석 모델을 학습시킵니다. 

본 예제는 두 개의 클래스로 분류하는 이진 분류의 한 예입니다. 

 

여기에선 인터넷 영화 데이터베이스(Internet Movie Database, IMDb)에서 가져온 50,000개의 영화 리뷰 텍스트를 포함하고 있는 영화 리뷰 데이터셋을 사용할 겁니다. 

 

데이터셋은 학습하는데 사용할 훈련 데이터셋 25,000개의 리뷰와 학습된 모델의 성능을 평가하는데 사용할 테스트 데이터셋  25,000개의 리뷰로 구성됩니다.

훈련 데이터셋과 테스트 데이터셋에는 동일한 개수의 긍정 리뷰와 부정적 리뷰가 포함되어 있습니다.

 

IMDB 데이터 세트를 다운로드하고 살펴보기

데이터셋을 다운로드 받아 압축을 풀고 디렉토리 구조를 살펴봅니다. 

 

url = "https://ai.stanford.edu/~amaas/data/sentiment/aclImdb_v1.tar.gz"



# 데이터셋을 다운로드 하고 aclImdb 디렉토리에 압축을 풀어줍니다. 

# 최초 한번만 다운로드가 진행되고 이후에는 기존에 다운로드된 파일을 사용합니다.

dataset = tf.keras.utils.get_file("aclImdb_v1", url,

                                    untar=True, cache_dir='.',

                                    cache_subdir='')


# os.path.dirname(dataset)

# '.\\aclImdb_v1.tar.gz'에서 디렉토리인 .만 떼어냅니다.

# os.path.join(os.path.dirname(dataset), 'aclImdb')

# 디렉터리 .에다가 aclImdb를 추가하여 '.\\aclImdb'를 만듬, 이곳에 압축이 풀립니다.  

dataset_dir = os.path.join(os.path.dirname(dataset), 'aclImdb')


# Downloading data from https://ai.stanford.edu/~amaas/data/sentiment/aclImdb_v1.tar.gz

# 84131840/84125825 [==============================] - 8s 0us/step




# aclImdb 디렉터리의 구조를 출력합니다.

print(os.listdir(dataset_dir))

# aclImdb 디렉터리에 있는 서브 디렉터리와 파일들을 출력합니다.

# ['imdb.vocab', 'imdbEr.txt', 'README', 'test', 'train']

# test와 train은 디렉토리입니다. 



# tarin 디렉터리의 구조를 출력합니다. 

train_dir = os.path.join(dataset_dir, 'train')

print(os.listdir(train_dir))


# ['labeledBow.feat', 'neg', 'pos', 'unsup', 'unsupBow.feat', 'urls_neg.txt', 'urls_pos.txt', 'urls_unsup.txt']

# neg와 pos, unsup는 디렉토리입니다. 




# test 디렉터리의 구조를 출력합니다. 

test_dir = os.path.join(dataset_dir, 'test')

print(os.listdir(test_dir))


# ['labeledBow.feat', 'neg', 'pos', 'urls_neg.txt', 'urls_pos.txt']

# neg와 pos는 디렉토리입니다. 






aclImdb/train/pos와 aclImdb/train/neg,  aclImdb/test/pos와 aclImdb/test/neg 디렉토리에  많은 수의 텍스트 파일이 포함되어 있습니다.

하나의 텍스트 파일은 하나의 영화 리뷰입니다. 



 neg와 pos 디렉토리에는 각각 긍정적 리뷰와 부정적 리뷰 텍스트 파일들이 포함되어 있습니다.

하나를 출력해보겠습니다.

 

sample_file = os.path.join(test_dir, 'pos/17_8.txt')


# 경로 '.\\aclImdb\\test\\pos/17_8.txt'에 있는 텍스트 파일의 내용을 출력한다. 

with open(sample_file) as f:

    print(f.read())


# Brilliant and moving performances by Tom Courtenay and Peter Finch.



데이터셋 로드하기

디스크에서 텍스트 파일을 읽어서 학습시 요구되는 포맷으로 변환합니다.  

이를 위해 text_dataset_from_directory 함수를 사용합니다. 이때 다음과 같은 디렉토리 구조가 요구됩니다. 

클래스 이름(class_a, class_b)을 갖는 2개의 디렉토리에 각각 텍스트 파일이 저장되어 있습니다.  

 

main_directory/

...class_a/

......a_text_1.txt

......a_text_2.txt

...class_b/

......b_text_1.txt

......b_text_2.txt




이진 분류를 위한 데이터 세트를 준비하려면 디스크에 class_a,  class_b에 해당하는 두 개의 디렉토리가 필요합니다. 

디렉토리에 각각 긍정적, 부정적인 영화 리뷰 텍스트 파일이 저장될 것이며, aclImdb/train/pos, aclImdb/train/neg에서 찾을 수 있습니다. 




진행하기 전에 train 디렉터리에 있는 unsup 디렉터리를 제거해야 합니다. 

text_dataset_from_directory를 사용하려면 테스트 파일이 포함된 pos와 neg 디렉토리만 있어야 하기 때문입니다.

 

remove_dir = os.path.join(train_dir, 'unsup')

shutil.rmtree(remove_dir)



text_dataset_from_directory 함수를 사용하여 레이블이 있는 tf.data.Dataset을 만듭니다. 

tf.data는 데이터 작업을 위한 강력한 도구 모음입니다.

 

train 디렉터리에 포함된 텍스트 파일을 로드하여 텍스트 파일이 속한 pos와 neg 디렉터리 이름으로 구분하여 각각 라벨을 부여하여 훈련 데이터셋을 생성합니다.  

 

머신 러닝 학습을 진행할 때, 데이터 세트를 훈련(train), 검증(validation), 테스트(test)의 세 그룹으로 나누는 것이 좋습니다.

 

IMDB 데이터 세트는 이미 훈련과 테스트로 나뉘었지만 검증 세트가 없습니다. 

text_dataset_from_directory 함수의 validation_split 인수를 사용하여 훈련 데이터의 20 %를 검증 세트로 사용하도록 합니다. 

나머지 80%만 훈련 데이터로 사용되게 됩니다. 

 

batch_size = 32

seed = 42


raw_train_ds = tf.keras.preprocessing.text_dataset_from_directory(

    'aclImdb/train', 

    batch_size=batch_size, 

    validation_split=0.2, 

    subset='training',  # 여기에선 훈련 데이터셋을 사용합니다.

    seed=seed)



Found 25000 files belonging to 2 classes.

Using 20000 files for training.




위 출력에서 볼 수 있듯이 training 폴더에는 25,000 개의 샘플이 있으며 그 중 80 % (또는 20,000개)를 훈련 데이터로 사용합니다.

데이터 세트를 model.fit에 전달하여 모델을 학습시키게 됩니다. 

 

라벨을 붙인 데이터셋을 3개 출력해봅니다.

Label로 시작하는 줄이 라벨이 붙은 것입니다.

 

for text_batch, label_batch in raw_train_ds.take(1):

  for i in range(3):

    print("Review", text_batch.numpy()[i])

    print("Label", label_batch.numpy()[i])



Review b'"Pandemonium" is a horror movie spoof that comes off more stupid than funny. Believe me when I tell you, I love comedies. Especially comedy spoofs. "Airplane", "The Naked Gun" trilogy, "Blazing Saddles", "High Anxiety", and "Spaceballs" are some of my favorite comedies that spoof a particular genre. "Pandemonium" is not up there with those films. Most of the scenes in this movie had me sitting there in stunned silence because the movie wasn\'t all that funny. There are a few laughs in the film, but when you watch a comedy, you expect to laugh a lot more than a few times and that\'s all this film has going for it. Geez, "Scream" had more laughs than this film and that was more of a horror film. How bizarre is that?<br /><br />*1/2 (out of four)'

Label 0


Review b"David Mamet is a very interesting and a very un-equal director. His first movie 'House of Games' was the one I liked best, and it set a series of films with characters whose perspective of life changes as they get into complicated situations, and so does the perspective of the viewer.<br /><br />So is 'Homicide' which from the title tries to set the mind of the viewer to the usual crime drama. The principal characters are two cops, one Jewish and one Irish who deal with a racially charged area. The murder of an old Jewish shop owner who proves to be an ancient veteran of the Israeli Independence war triggers the Jewish identity in the mind and heart of the Jewish detective.<br /><br />This is were the flaws of the film are the more obvious. The process of awakening is theatrical and hard to believe, the group of Jewish militants is operatic, and the way the detective eventually walks to the final violent confrontation is pathetic. The end of the film itself is Mamet-like smart, but disappoints from a human emotional perspective.<br /><br />Joe Mantegna and William Macy give strong performances, but the flaws of the story are too evident to be easily compensated."

Label 0


Review b'Great documentary about the lives of NY firefighters during the worst terrorist attack of all time.. That reason alone is why this should be a must see collectors item.. What shocked me was not only the attacks, but the"High Fat Diet" and physical appearance of some of these firefighters. I think a lot of Doctors would agree with me that,in the physical shape they were in, some of these firefighters would NOT of made it to the 79th floor carrying over 60 lbs of gear. Having said that i now have a greater respect for firefighters and i realize becoming a firefighter is a life altering job. The French have a history of making great documentary\'s and that is what this is, a Great Documentary.....'

Label 1



리뷰에서 HTML 태그 <br />를 제거 해야 합니다.  다음 섹션에서 이를 처리합니다.




레이블은 0 또는 1입니다. 특정 샘플이 긍정적, 부정적 영화 리뷰 중 어느 쪽에 해당하는지 확인하려면 데이터 세트의 class_names 속성을 확인하면 됩니다.

 

라벨의 이름을 출력해 봅니다. 디렉터리 이름이 출력되는 것을 볼 수 있습니다.

 

print("Label 0 corresponds to", raw_train_ds.class_names[0])

print("Label 1 corresponds to", raw_train_ds.class_names[1])



Label 0 corresponds to neg

Label 1 corresponds to pos




다음으로 검증, 테스트 데이터 세트를 만듭니다. 검증을 위해 훈련 세트의 20%인 5,000 개 리뷰를 사용합니다.

 

참고 : validation_split, subset 인수를 사용하는 경우 임의 시드(seed)를 지정하거나 shuffle = False를 전달하여 검증 데이터셋과 훈련 데이터 셋이 겹치지 않도록 해야 합니다. 



raw_val_ds = tf.keras.preprocessing.text_dataset_from_directory(

    'aclImdb/train', 

    batch_size=batch_size, 

    validation_split=0.2, # 검증 데이터셋으로 훈련 데이터셋의 20%인 5000개 사용합니다.

    subset='validation', # 여기에선 검증 데이터셋을 사용합니다.

    seed=seed)



Found 25000 files belonging to 2 classes.

Using 5000 files for validation.




테스트 데이터셋을 생성합니다. 전체 25000개를 모두 사용합니다. 

 

raw_test_ds = tf.keras.preprocessing.text_dataset_from_directory(

    'aclImdb/test', 

    batch_size=batch_size)



Found 25000 files belonging to 2 classes.



참고 : 다음 섹션에서 사용되는 전처리 API는 TensorFlow 2.3에서 실험적이며 변경 될 수 있습니다.



훈련을 위해 데이터셋 준비하기

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

 

표준화는 일반적으로 데이터 세트에서 구두점이나 HTML 요소를 제거하기 위해 텍스트를 사전 처리하는 것을 말합니다. 

토큰화는 문자열을 토큰으로 분할하는 것을 말합니다 (예 : 공백으로 분할하여 문장을 개별 단어로 분할). 

 

벡터화는 토큰을 숫자로 변환하여 신경망에 입력으로 제공하는 것을 말합니다. 

이러한 모든 작업은 preprocessing.TextVectorization 레이어으로 수행 할 수 있습니다.

 

위에서 본 것처럼 리뷰에는 <br />과 같은 다양한 HTML 태그가 포함되어 있습니다. 

 

이러한 태그는 TextVectorization 레이어의 기본 표준화(standardizer)에 의해 제거되지 않습니다 (텍스트를 소문자로 변환하고 기본적으로 구두점을 제거하지만 HTML은 제거하지 않음). 

HTML을 제거하기 위해 사용자 정의 표준화 함수를 작성합니다.

 

참고 : 학습 / 테스트 왜곡 skew  (train / serving skew라고도 함)을 방지하려면 학습 및 테스트 시간에 데이터를 동일하게 사전 처리하는 것이 중요합니다. 

 

이를 용이하게하기 위해  이 튜토리얼의 뒷부분에서 볼 수 있듯이 TextVectorization 레이어를 모델 내부에 직접 포함 할 수 있습니다.



감정 분류 모델에 필요한 데이터 세트 전처리 단계를 정의합니다. 원하는 매개 변수로 TextVectorization 레이어를 초기화하여 영화 리뷰를 벡터화합니다.

custom_standardization는 텍스트 입력에서 HTML 태그 <br />를 제거하는 전처리 과정입니다.  

 

def custom_standardization(input_data):

  lowercase = tf.strings.lower(input_data)

  stripped_html = tf.strings.regex_replace(lowercase, '<br />', ' ')

  return tf.strings.regex_replace(stripped_html,

                                  '[%s]' % re.escape(string.punctuation),

                                  '')




다음으로 TextVectorization 레이어를 생성합니다. 

이 레이어를 사용하여 standardize, tokenize, vectorize를 수행하고 토큰을 정수로 출력합니다.(output_mode를 int로 했기 때문입니다.)

 

standardize는 데이터셋에서 HTML 태그를 제거합니다. 

tokenize는 문자열을 토큰으로 분리합니다. 예를 들어 문장을 빈공백을 기준으로 단어로 분리합니다.

vectorize는 토큰을 숫자로 변환하여 신경망의 입력으로 사용할 수 있도록 합니다. 

 

tokenize는 디폴트 split 함수를 사용하고 standardize는 직접 만든 custom_standardization 함수를 사용합니다.

 

최대 시컨스 길이(maximum sequence_length)를 sequence_length로 정의하고 있습니다.

Sequence length는 입력 데이터의 길이를 의미합니다.

이 길이보다 긴 입력 데이터는 지정한 길이에 맞게 잘라내고 부족한 경우에는 지정한 길이에 맞추어 채우도록 합니다. 

 

max_features = 10000  # Vocabulary size 

sequence_length = 250 #  number of words in a sequence


vectorize_layer = TextVectorization(

    standardize=custom_standardization,

    max_tokens=max_features, # The maximum size of the vocabulary for this layer. 모든 샘플의 길이가 같지 않으므로 maximum_sequence 길이를 설정합니다.

    output_mode='int',

    output_sequence_length=sequence_length)





전처리 레이어의 상태를 데이터 셋에 맞추기 위해 adapt를 호출합니다. 그 결과 문자열에서 정수로의 인덱스를 만드는 모델이 만들어집니다. 

이것은 훈련 데이터셋에만 해당됩니다. 테스트 데이터셋은 정보가 부족합니다. 

 

라벨이 없는 텍스트만 있는 데이터셋을 만들고 나서 adapt를 호출하여 vocabulary를 만듭니다. 

 

# Make a text-only dataset (without labels), then call adapt

train_text = raw_train_ds.map(lambda x, y: x)

vectorize_layer.adapt(train_text)




vectorize_layer 레이어를 사용하여 특정 데이터를 전처리 한 결과를 확인하는 함수를 만들어 보겠습니다.

리뷰가 정수로 출력됩니다. 

 

def vectorize_text(text, label):

  text = tf.expand_dims(text, -1)

  return vectorize_layer(text), label

 

데이터 세트에서 배치(batch)를 리턴합니다. 하나의 배치는  32개의 영화 리뷰와 라벨로 구성됩니다. 

# retrieve a batch (of 32 reviews and labels) from the dataset

text_batch, label_batch = next(iter(raw_train_ds))

first_review, first_label = text_batch[0], label_batch[0]

print("Review", first_review)

print("Label", raw_train_ds.class_names[first_label])

print("Vectorized review", vectorize_text(first_review, first_label))



Review tf.Tensor(b'Silent Night, Deadly Night 5 is the very last of the series, and like part 4, it\'s unrelated to the first three except by title and the fact that it\'s a Christmas-themed horror flick.<br /><br />Except to the oblivious, there\'s some obvious things going on here...Mickey Rooney plays a toymaker named Joe Petto and his creepy son\'s name is Pino. Ring a bell, anyone? Now, a little boy named Derek heard a knock at the door one evening, and opened it to find a present on the doorstep for him. Even though it said "don\'t open till Christmas", he begins to open it anyway but is stopped by his dad, who scolds him and sends him to bed, and opens the gift himself. Inside is a little red ball that sprouts Santa arms and a head, and proceeds to kill dad. Oops, maybe he should have left well-enough alone. Of course Derek is then traumatized by the incident since he watched it from the stairs, but he doesn\'t grow up to be some killer Santa, he just stops talking.<br /><br />There\'s a mysterious stranger lurking around, who seems very interested in the toys that Joe Petto makes. We even see him buying a bunch when Derek\'s mom takes him to the store to find a gift for him to bring him out of his trauma. And what exactly is this guy doing? Well, we\'re not sure but he does seem to be taking these toys apart to see what makes them tick. He does keep his landlord from evicting him by promising him to pay him in cash the next day and presents him with a "Larry the Larvae" toy for his kid, but of course "Larry" is not a good toy and gets out of the box in the car and of course, well, things aren\'t pretty.<br /><br />Anyway, eventually what\'s going on with Joe Petto and Pino is of course revealed, and as with the old story, Pino is not a "real boy". Pino is probably even more agitated and naughty because he suffers from "Kenitalia" (a smooth plastic crotch) so that could account for his evil ways. And the identity of the lurking stranger is revealed too, and there\'s even kind of a happy ending of sorts. Whee.<br /><br />A step up from part 4, but not much of one. Again, Brian Yuzna is involved, and Screaming Mad George, so some decent special effects, but not enough to make this great. A few leftovers from part 4 are hanging around too, like Clint Howard and Neith Hunter, but that doesn\'t really make any difference. Anyway, I now have seeing the whole series out of my system. Now if I could get some of it out of my brain. 4 out of 5.', shape=(), dtype=string)

Label neg

Vectorized review (<tf.Tensor: shape=(1, 250), dtype=int64, numpy=

array([[1287,  313, 2380,  313,  661,    7,    2,   52,  229,    5,    2,

         200,    3,   38,  170,  669,   29, 5492,    6,    2,   83,  297,

         549,   32,  410,    3,    2,  186,   12,   29,    4,    1,  191,

         510,  549,    6,    2, 8229,  212,   46,  576,  175,  168,   20,

           1, 5361,  290,    4,    1,  761,  969,    1,    3,   24,  935,

        2271,  393,    7,    1, 1675,    4, 3747,  250,  148,    4,  112,

         436,  761, 3529,  548,    4, 3633,   31,    2, 1331,   28, 2096,

           3, 2912,    9,    6,  163,    4, 1006,   20,    2,    1,   15,

          85,   53,  147,    9,  292,   89,  959, 2314,  984,   27,  762,

           6,  959,    9,  564,   18,    7, 2140,   32,   24, 1254,   36,

           1,   85,    3, 3298,   85,    6, 1410,    3, 1936,    2, 3408,

         301,  965,    7,    4,  112,  740, 1977,   12,    1, 2014, 2772,

           3,    4,  428,    3, 5177,    6,  512, 1254,    1,  278,   27,

         139,   25,  308,    1,  579,    5,  259, 3529,    7,   92, 8981,

          32,    2, 3842,  230,   27,  289,    9,   35,    2, 5712,   18,

          27,  144, 2166,   56,    6,   26,   46,  466, 2014,   27,   40,

        2745,  657,  212,    4, 1376, 3002, 7080,  183,   36,  180,   52,

         920,    8,    2, 4028,   12,  969,    1,  158,   71,   53,   67,

          85, 2754,    4,  734,   51,    1, 1611,  294,   85,    6,    2,

        1164,    6,  163,    4, 3408,   15,   85,    6,  717,   85,   44,

           5,   24, 7158,    3,   48,  604,    7,   11,  225,  384,   73,

          65,   21,  242,   18,   27,  120,  295,    6,   26,  667,  129,

        4028,  948,    6,   67,   48,  158,   93,    1]])>, <tf.Tensor: shape=(), dtype=int32, numpy=0>)



위에서 볼 수 있듯이 각 토큰은 정수로 대체되었습니다. 

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

 

print("1287 ---> ",vectorize_layer.get_vocabulary()[1287])

print(" 313 ---> ",vectorize_layer.get_vocabulary()[313])

print('Vocabulary size: {}'.format(len(vectorize_layer.get_vocabulary())))



1287 --->  silent

 313 --->  night

Vocabulary size: 10000



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

마지막 전처리 단계로 이전에 생성한 TextVectorization 레이어를 학습, 검증 및 테스트 데이터 세트에 적용합니다.

 

train_ds = raw_train_ds.map(vectorize_text)

val_ds = raw_val_ds.map(vectorize_text)

test_ds = raw_test_ds.map(vectorize_text)



성능을 위한 데이터 세트 설정

다음은 입출력이 블로킹 되지 않도록 데이터를 로드 할 때 사용해야 하는 두 가지 중요한 방법입니다.

 

.cache()는 데이터가 디스크에서 로드 된 후 메모리에 데이터를 저장합니다. 

이렇게하면 모델을 학습하는 동안 데이터 세트가 병목 현상이 발생하지 않습니다.

데이터 세트가 너무 커서 메모리에 맞지 않는 경우 이 방법을 사용하여 성능이 뛰어난 온 디스크 캐시를 생성 할 수도 있습니다. 이는 많은 작은 파일을 읽는 것보다 더 효율적입니다.



 .prefetch()는 학습중 데이터 전처리 및 모델 실행를 오버래핑합니다. 

현재 요소가 처리되는 동안 다음에 처리한 요소를 미리 준비합니다. 

대기 시간과 처리량이 향상됩니다.

 

아래 링크의 데이터 성능 가이드에서 두 방법 및 디스크에 데이터를 캐시하는 방법에 대해 자세히 알아볼 수 있습니다.

https://www.tensorflow.org/guide/data_performance 

 

AUTOTUNE = tf.data.AUTOTUNE


train_ds = train_ds.cache().prefetch(buffer_size=AUTOTUNE)

val_ds = val_ds.cache().prefetch(buffer_size=AUTOTUNE)

test_ds = test_ds.cache().prefetch(buffer_size=AUTOTUNE)



모델 생성하기

신경망을 생성합니다. 

 

embedding_dim = 16


model = tf.keras.Sequential([

  layers.Embedding(max_features + 1, embedding_dim),

  layers.Dropout(0.2),

  layers.GlobalAveragePooling1D(),

  layers.Dropout(0.2),

  layers.Dense(1)])


model.summary()



Model: "sequential"

_________________________________________________________________

Layer (type)                 Output Shape              Param #   

=================================================================

embedding (Embedding)        (None, None, 16)          160016    

_________________________________________________________________

dropout (Dropout)            (None, None, 16)          0         

_________________________________________________________________

global_average_pooling1d (Gl (None, 16)                0         

_________________________________________________________________

dropout_1 (Dropout)          (None, 16)                0         

_________________________________________________________________

dense (Dense)                (None, 1)                 17        

=================================================================

Total params: 160,033

Trainable params: 160,033

Non-trainable params: 0

_________________________________________________________________



레이어를 순차적으로 쌓아서 분류기(classifier)를 만듭니다. 

 

1.   layers.Embedding(max_features + 1, embedding_dim),

 

첫 번째 레이어는 Embedding 레이어입니다. 

10,001 단어 어휘를 16 차원의 배열에 저장합니다. .

이 레이어는 정로 인코딩된 리뷰를 받아 각 단어 인덱스에 대한 임베딩 벡터(embedding vector)를 찾습니다. 

이러한 벡터는 모델 학습으로 학습됩니다. 벡터는 출력 배열에 차원을 추가합니다. 

결과 차원은 다음과 같습니다 : (batch, sequence, embedding). 

 

임베딩에 대한 자세한 내용은 아래 링크의 단어 임베딩 자습서를 참조하십시오.

https://www.tensorflow.org/tutorials/text/word_embeddings 

 

임베딩 레이어는 정수 인덱스(특정 단어를 의미)로부터 덴스 벡터(임베딩)로 매핑하는 룩업 테이블로 이해할 수 있습니다.

임베딩의 차원은 Dense 레이어의 뉴런 수를 실험하는 것과 같은 방식으로 문제에 잘 맞는지 확인하기 위해 실험 할 수있는 매개 변수입니다.



2.   layers.GlobalAveragePooling1D(),

 

다음으로 GlobalAveragePooling1D 계층은 시퀀스 차원을 평균하여 각 샘플에 대해 고정 길이 출력 벡터를 반환합니다. 

이를 통해 모델은 가능한 가장 간단한 방법으로 가변 길이 입력을 처리 할 수 있습니다.

 

3.  고정 길이 출력 벡터는 16 개의 은닉 유닛이 있는 완전 연결 (Dense) 레이어를 통해 파이프됩니다.

 

4.   layers.Dense(1)

 

마지막 레이어는 단일 출력 노드와 연결됩니다.



손실(Loss) 함수와 옵티마이저(optimizer)

모델을 학습시키려면 손실 함수와 옵티마이저가 필요합니다. 

 

모델의 성능을 평가하기 위해 전체 데이터셋에서 올바르게 분류된 텍스트 비율을 표시하는 BinaryAccuracy를 사용합니다.

이진 분류이고 모델의 출력이 확률(=시그모이드 활성화 함수가 있는 단일 레이어)이기 때문에 손실함수로 losses.BinaryCrossentropy를 사용합니다. 

 

실제 레이블과 예측 레이블 간의 교차 엔트로피 손실(cross-entropy loss)을 계산합니다.

참고할 부분 https://www.tensorflow.org/api_docs/python/tf/keras/losses/BinaryCrossentropy

 

model.compile(loss=losses.BinaryCrossentropy(from_logits=True),

              optimizer='adam',

              metrics=tf.metrics.BinaryAccuracy(threshold=0.0))



모델 학습시키기

데이터 세트 객체를 fit 메서드에 전달하여 모델을 학습시킵니다.



epochs = 10

history = model.fit(

    train_ds,

    validation_data=val_ds,

    epochs=epochs)



Epoch 1/10

625/625 [==============================] - 5s 6ms/step - loss: 0.6811 - binary_accuracy: 0.6217 - val_loss: 0.6113 - val_binary_accuracy: 0.7764

Epoch 2/10

625/625 [==============================] - 3s 4ms/step - loss: 0.5763 - binary_accuracy: 0.7903 - val_loss: 0.4955 - val_binary_accuracy: 0.8230

Epoch 3/10

625/625 [==============================] - 3s 4ms/step - loss: 0.4637 - binary_accuracy: 0.8381 - val_loss: 0.4184 - val_binary_accuracy: 0.8484

Epoch 4/10

625/625 [==============================] - 3s 4ms/step - loss: 0.3904 - binary_accuracy: 0.8605 - val_loss: 0.3726 - val_binary_accuracy: 0.8604

Epoch 5/10

625/625 [==============================] - 3s 4ms/step - loss: 0.3421 - binary_accuracy: 0.8768 - val_loss: 0.3442 - val_binary_accuracy: 0.8688

Epoch 6/10

625/625 [==============================] - 3s 4ms/step - loss: 0.3101 - binary_accuracy: 0.8899 - val_loss: 0.3250 - val_binary_accuracy: 0.8726

Epoch 7/10

625/625 [==============================] - 3s 4ms/step - loss: 0.2848 - binary_accuracy: 0.8964 - val_loss: 0.3117 - val_binary_accuracy: 0.8750

Epoch 8/10

625/625 [==============================] - 3s 4ms/step - loss: 0.2636 - binary_accuracy: 0.9034 - val_loss: 0.3023 - val_binary_accuracy: 0.8772

Epoch 9/10

625/625 [==============================] - 3s 4ms/step - loss: 0.2482 - binary_accuracy: 0.9113 - val_loss: 0.2960 - val_binary_accuracy: 0.8782

Epoch 10/10

625/625 [==============================] - 3s 4ms/step - loss: 0.2343 - binary_accuracy: 0.9166 - val_loss: 0.2916 - val_binary_accuracy: 0.8796



모델 평가하기

모델의 성능을 평가합니다.  두 개의 값이 반환됩니다. 

 

loss -손실, 에러를 의미합니다. 낮은 값일수록 좋은 성능입니다.

accuracy - 정확도. 87%를 얻을 수 있습니다.  

 

loss, accuracy = model.evaluate(test_ds)


print("Loss: ", loss)

print("Accuracy: ", accuracy)



782/782 [==============================] - 3s 4ms/step - loss: 0.3099 - binary_accuracy: 0.8740

Loss:  0.30993229150772095

Accuracy:  0.873960018157959



이 접근 방식은 약 87 %의 정확도를 달성합니다.



시간 경과에 따른 정확도 및 손실 그래프 그리기

 

 model.fit()가 리턴한 History를 사용하여 그래프를 그립니다. 

 

history_dict = history.history

history_dict.keys()



dict_keys(['loss', 'binary_accuracy', 'val_loss', 'val_binary_accuracy'])



4 개의 항목이 있습니다. 학습 및 검증 중에 모니터링되는 각 메트릭에 대해 하나씩 있습니다. 

이를 사용하여 비교를 위한 훈련 및 검증 손실과 훈련 및 검증 정확도를 각각 같이 플로팅 할 수 있습니다.

 

acc = history_dict['binary_accuracy']

val_acc = history_dict['val_binary_accuracy']

loss = history_dict['loss']

val_loss = history_dict['val_loss']


epochs = range(1, len(acc) + 1)


# "bo" is for "blue dot"

plt.plot(epochs, loss, 'bo', label='Training loss')

# b is for "solid blue line"

plt.plot(epochs, val_loss, 'b', label='Validation loss')

plt.title('Training and validation loss')

plt.xlabel('Epochs')

plt.ylabel('Loss')

plt.legend()


plt.show()

 



plt.plot(epochs, acc, 'bo', label='Training acc')

plt.plot(epochs, val_acc, 'b', label='Validation acc')

plt.title('Training and validation accuracy')

plt.xlabel('Epochs')

plt.ylabel('Accuracy')

plt.legend(loc='lower right')


plt.show()

 



이 그림에서 점은 훈련중의 손실과 정확도를 나타내고 실선은 검증중의 손실과 정확도를 나타냅니다.

 

훈련 손실은 각 epoch마다 감소하고 훈련 정확도는 각 epoch마다 증가합니다. 

이는 경사 하강 법 최적화를 사용할 때 예상되는 현상입니다. epoch 할 때마다 최소화해야합니다.



이것은 검증 손실 및 정확도의 경우가 아닙니다. 훈련 정확도보다 먼저 정점에있는 것처럼 보입니다. 이것은 과적합의 예입니다. 

과적합의 의미는 모델이 이전에 본 적이 없는 데이터보다 학습 데이터에서 더 좋은 결과를 갖는다는 의미입니다.  

이 시점 이후 모델은 테스트 데이터로 일반화되지 않는 훈련 데이터에 특정한 표현을 과도하게 최적화하고 학습합니다.



지금과 같은 특별한 경우에는 검증 정확도가 더 이상 증가하지 않을 때 단순히 훈련을 중지하여 과적합을 방지 할 수 있습니다. 

이렇게하는 한 가지 방법은 tf.keras.callbacks.EarlyStopping 콜백을 사용하는 것입니다.




Export the model

위의 코드에서는 모델에 텍스트를 공급하기 전에 TextVectorization 레이어를 데이터 세트에 적용했습니다. 

모델이 raw 문자열을 처리 할 수 있도록하려면 (예 : 배포 단순화) 모델 내부에 TextVectorization 레이어를 포함 할 수 있습니다. 

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



export_model = tf.keras.Sequential([

  vectorize_layer,

  model,

  layers.Activation('sigmoid')

])


export_model.compile(

    loss=losses.BinaryCrossentropy(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)



782/782 [==============================] - 5s 5ms/step - loss: 0.3121 - accuracy: 0.8716

0.873960018157959



Inference on new data

새로운 샘플에 대한 예측을 얻으려면 model.predict ()를 호출하기만 하면 됩니다.

 

examples = [

  "The movie was great!",

  "The movie was okay.",

  "The movie was terrible..."

]


export_model.predict(examples)



array([[0.63023424],

       [0.4530166 ],

       [0.36852628]], dtype=float32)



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



TextVectorization 레이어를 적용 할 위치를 선택하는 것에 따라  성능차이가 있을 수 있습니다.

TextVectorization 레이어를 모델 외부에서 사용하면 GPU에서 학습 할 때 비동기 CPU 처리 및 데이터 버퍼링이 발생할 수 있습니다.

따라서 GPU에서 모델을 교육하는 경우 이렇게 사용하여 모델을 개발하는 동안 최상의 성능을 얻을 수 있습니다 

 

배포할 준비가되면 모델 내부에 TextVectorization 레이어를 포함하도록 전환하는 것이 좋습니다 .



모델 저장에 대해 자세히 알아 보려면 이 튜토리얼을 참조하십시오.

https://www.tensorflow.org/tutorials/keras/save_and_load





문제 발생시 지나치지 마시고 댓글 남겨주시면 가능한 빨리 답장드립니다.


제가 쓴 책도 한번 검토해보세요 ^^

+ Recent posts