Deeplearning - chap 11-1

심준보·2023년 6월 17일
0

Deeplearning

목록 보기
5/5
post-thumbnail

텍스트를 위한 딥러닝

1. 텍스트 표준화

2. 텍스트 분할

3. 어휘 인덱싱

  • TextVectorization층 사용하기

class Vectorizer:
    def standardize(self, text):
        text = text.lower()
        return "".join(char for char in text if char not in string.punctuation)

    def tokenize(self, text):
        return text.split()

    def make_vocabulary(self, dataset):
        self.vocabulary = {"": 0, "[UNK]": 1}   
        for text in dataset:
            text = self.standardize(text) 
            tokens = self.tokenize(text)  
            for token in tokens:
                if token not in self.vocabulary:
                    self.vocabulary[token] = len(self.vocabulary)
        self.inverse_vocabulary = dict(
            (v, k) for k, v in self.vocabulary.items())

    def encode(self, text):   
        text = self.standardize(text)
        tokens = self.tokenize(text)
        return [self.vocabulary.get(token, 1) for token in tokens]

    def decode(self, int_sequence):   
        return " ".join(
            self.inverse_vocabulary.get(i, "[UNK]") for i in int_sequence)  

vectorizer = Vectorizer()
dataset = [  
    "I write, erase, rewrite",
    "Erase again, and then",
    "A poppy blooms.",
]
vectorizer.make_vocabulary(dataset)  

-> 주어진 데이터셋을 기반으로 단어 집합을 생성하고 , 텍스트를 인코딩하거나 디코딩하는 기능 제공

  • standardize(self, text) 함수:

입력된 텍스트를 소문자로 변환합니다.

  • string.punctuation 포함된 모든 구두점 문자를 제거합니다.
    결과적으로, 소문자로 변환된 텍스트에서 구두점이 제거된 텍스트를 반환합니다.

  • tokenize(self, text)

입력된 텍스트를 공백을 기준으로 분리하여 토큰화합니다.
분리된 토큰들을 리스트로 반환합니다.

  • make_vocabulary(self, dataset)

주어진 데이터셋을 기반으로 단어 집합(vocabulary)을 생성합니다.
초기 단어 집합에는 빈 문자열("")과 미확인 단어("[UNK]")를 포함합니다.
데이터셋의 각 텍스트에 대해 standardize 함수를 적용하여 텍스트를 표준화합니다.
표준화된 텍스트를 토큰화하여 각 토큰을 단어 집합에 추가합니다. 단어가 이미 단어 집합에 존재한다면 추가하지 않습니다.
단어 집합과 역반전 단어 집합(inverse_vocabulary)을 생성합니다. 역반전 단어 집합은 인덱스와 단어를 매핑한 딕셔너리입니다.

  • encode(self, text) 함수

입력된 텍스트를 표준화합니다.
표준화된 텍스트를 토큰화합니다.
각 토큰에 대해 단어 집합에서 해당 단어의 인덱스를 가져옵니다. 단어가 단어 집합에 존재하지 않는 경우 1(미확인 단어)을 할당합니다.
인덱스의 리스트를 반환합니다.

  • decode(self, int_sequence) 함수

인덱스의 시퀀스를 입력으로 받습니다.
각 인덱스를 역반전 단어 집합에서 해당하는 단어로 변환합니다. 만약 단어가 없다면 미확인 단어("[UNK]")로 변환합니다.
변환된 단어들을 공백으로 연결하여 복원된 텍스트를 반환합니다.

  • encode
test_sentence = "I write, rewrite, and still rewrite again"   
encoded_sentence = vectorizer.encode(test_sentence)
print(encoded_sentence)
  1. test_sentence를 표준화합니다. 소문자로 변환되고 구두점이 제거됩니다.
  2. 표준화된 텍스트를 토큰화하여 단어로 분리합니다.
  3. 각 단어의 단어 집합에서 해당하는 인덱스를 찾습니다. 만약 단어가 단어 집합에 존재하지 않는 경우 [UNK] (미확인 단어)의 인덱스인 1을 할당합니다.
  4. 인코딩된 결과는 각 단어의 인덱스로 이루어진 리스트로 반환됩니다.
  • decode
decoded_sentence = vectorizer.decode(encoded_sentence)
print(decoded_sentence)
  1. 주어진 encoded_sentence는 단어의 인덱스로 이루어진 리스트입니다.
  2. 각 인덱스를 역반전 단어 집합에서 해당하는 단어로 변환합니다. 만약 단어가 단어 집합에 존재하지 않는 경우 [UNK] (미확인 단어)로 변환됩니다.
  3. 변환된 단어들은 공백으로 연결되어 원래의 텍스트로 복원됩니다.
from tensorflow.keras.layers import TextVectorization
text_vectorization = TextVectorization(
    output_mode="int",
)
  • TextVectorization 클래스는 TensorFlow에서 제공하는 텍스트 벡터화를 위한 레이어입니다.

  • TextVectorization 클래스의 객체를 생성하고 output_mode 매개변수를 "int"로 설정

  1. 텍스트 전처리: 주어진 텍스트 데이터를 사전 정의된 전처리 단계를 거쳐 벡터화할 수 있습니다. 전처리 단계에는 토큰화, 소문자 변환, 구두점 제거 등이 포함될 수 있습니다.

  2. 단어 집합 생성: 주어진 텍스트 데이터를 기반으로 단어 집합(vocabulary)을 생성합니다. 각 단어는 고유한 정수 인덱스로 매핑됩니다.

  3. 벡터화: 텍스트 데이터를 단어 인덱스의 시퀀스로 변환합니다. 각 문장은 단어의 인덱스들로 구성된 벡터로 표현됩니다.

  • output_mode

"int" ,"binary", "count", "tf-idf" 등이 있으며, 각각에 따라 벡터화 방식이 달라집니다.

TextVectorization 객체를 생성한 후에는 fit 메소드를 사용하여 텍스트 데이터에 적합한 전처리 및 단어 집합 생성을 수행하고, transform 메소드를 사용하여 텍스트를 벡터로 변환할 수 있습니다. 이렇게 변환된 벡터는 신경망 모델의 입력으로 사용될 수 있습니다.

import re
import string
import tensorflow as tf

def custom_standardization_fn(string_tensor):
    lowercase_string = tf.strings.lower(string_tensor)
    return tf.strings.regex_replace(
        lowercase_string, f"[{re.escape(string.punctuation)}]", "") # 중요

def custom_split_fn(string_tensor):
    return tf.strings.split(string_tensor)

text_vectorization = TextVectorization(
    output_mode="int",
    standardize=custom_standardization_fn,
    split=custom_split_fn,
)

TextVectorization

  • output_mode를 "int"
  • standardize 매개변수에는 custom_standardization_fn
  • split 매개변수에는 custom_split_fn 함수를 지정

custom_standardization_fn 함수

: 주어진 문자열 텐서를 소문자로 변환하고, re 모듈과 string.punctuation을 사용하여 구두점을 제거합니다. 이후 변환된 문자열을 반환

custom_split_fn 함수

:주어진 문자열 텐서를 공백을 기준으로 분리하여 토큰화합니다. 분리된 토큰들은 문자열 텐서의 리스트로 반환됩니다.

dataset = [
    "I write, erase, rewrite",
    "Erase again, and then",
    "A poppy blooms.",
]
text_vectorization.adapt(dataset) 
  • adapt() 메소드
    -> TextVectorization 객체에게 주어진 데이터셋을 기반으로 전처리 및 단어 집합 생성을 수행하도록 합니다.
  1. 주어진 데이터셋에 대해 standardize 함수를 적용하여 텍스트를 표준화합니다. 이 경우에는 custom_standardization_fn 함수가 사용됩니다.
  2. 표준화된 텍스트를 split 함수를 적용하여 토큰화합니다. 이 경우에는 custom_split_fn 함수가 사용됩니다.
  3. 토큰화된 결과를 기반으로 단어 집합(vocabulary)을 생성합니다. 각 단어는 고유한 정수 인덱스로 매핑됩니다.
text_vectorization.get_vocabulary() 
  • get_vocabulary()

ex) [b'', b'[UNK]', b'erase', b'and', b'write', b'rewrite', b'i', b'then', b'poppy', b'blooms']

-> 인덱스 0: 빈 문자열 ('')
인덱스 1: 미확인 단어 ('[UNK]')
인덱스 2: 'erase'
인덱스 3: 'and'
인덱스 4: 'write'
인덱스 5: 'rewrite'
인덱스 6: 'i'
인덱스 7: 'then'
인덱스 8: 'poppy'
인덱스 9: 'blooms'

vocabulary = text_vectorization.get_vocabulary()
test_sentence = "I write, rewrite, and still rewrite again"
encoded_sentence = text_vectorization(test_sentence)
print(encoded_sentence)
  • get_vacabulary() 를 통해 얻을 수 있는 값들을 통해 밑에 코드를 통해 나온것의
    인덱스를 비교해보자
inverse_vocab = dict(enumerate(vocabulary))
decoded_sentence = " ".join(inverse_vocab[int(i)] for i in encoded_sentence)
print(decoded_sentence)
  • inverse_vocab은 단어 인덱스를 키(key)로 가지고 해당 단어를 값(value)으로 가지는 딕셔너리입니다. 이를 생성하기 위해 enumerate 함수를 사용하여 단어 집합의 인덱스와 단어를 순회하고, dict()를 사용하여 딕셔너리로 변환

  • decoded_sentence
    : encoded_sentence에 있는 각 정수 인덱스를 inverse_vocab에서 찾아 해당하는 단어로 변환하고, 단어들을 공백으로 구분하여 문자열로 결합

-> 벡터화된 문장을 원래의 텍스트로 디코딩한 결과를 보여줍니다

단어 그룹을 표현하는 두 가지 방법 : 집홥과 시퀀스

  • IMDB 영화 리뷰 데이터 준비하기
!curl -O https://ai.stanford.edu/~amaas/data/sentiment/aclImdb_v1.tar.gz
!tar -xf aclImdb_v1.tar.gz
import os, pathlib, shutil, random

base_dir = pathlib.Path("aclImdb")
val_dir = base_dir / "val"
train_dir = base_dir / "train"
for category in ("neg", "pos"):
    os.makedirs(val_dir / category)
    files = os.listdir(train_dir / category)
    random.Random(1337).shuffle(files)
    num_val_samples = int(0.2 * len(files))
    val_files = files[-num_val_samples:]
    for fname in val_files:
        shutil.move(train_dir / category / fname,
                    val_dir / category / fname)
  • base_dir 변수
    : "aclImdb" 폴더를 가리키는 pathlib.Path 객체

  • val_dir 변수와 train_dir 변수는 각각 검증 세트와 훈련 세트의 디렉토리 경로를 나타냅니다.

  • "neg"와 "pos" 두 가지 카테고리에 대해 반복문을 실행

  • os.makedirs(val_dir / category) :검증 세트용 디렉토리를 생성

  • os.listdir(train_dir / category) : 파일 목록을 가져옵니다

  • random.Random(1337).shuffle(files) : 무작위로 섞는다

  • num_val_samples = int(0.2 * len(files)) : 숫자 설정

  • val_files = files[-num_val_samples:] : 뒤에서부터

  • shutil.move(train_dir / category / fname,
    val_dir / category / fname)
    -> 각 파일을 검증 세트로 이동

from tensorflow import keras
batch_size = 32

train_ds = keras.utils.text_dataset_from_directory(
    "aclImdb/train", batch_size=batch_size
)
val_ds = keras.utils.text_dataset_from_directory(
    "aclImdb/val", batch_size=batch_size
)
test_ds = keras.utils.text_dataset_from_directory(
    "aclImdb/test", batch_size=batch_size
)
  • 위의 코드는 디렉토리에서 텍스트 데이터셋을 생성하는 과정을 보여줍니다. 이는 TensorFlow의 Keras 모듈을 사용하여 수행

  • batch_size :데이터셋을 배치 단위로 처리할 때 한 번에 처리할 데이터 샘플의 개수를 결정하는 변수

  • train_ds = keras.utils.text_dataset_from_directory(
    "aclImdb/train", batch_size=batch_size

    -> train_ds 변수에는 해당 디렉토리의 텍스트 데이터셋이 생성되어 할당

: 이렇게 생성된 데이터셋은 텍스트 파일들을 읽어와서 토큰화하고 벡터화하는 등의 전처리 과정을 자동으로 수행

for inputs, targets in train_ds:
    print("inputs.shape:", inputs.shape)
    print("inputs.dtype:", inputs.dtype)  
    print("targets.shape:", targets.shape)
    print("targets.dtype:", targets.dtype)
    print("inputs[0]:", inputs[0])
    print("targets[0]:", targets[0])
    break
  • 위의 코드는 train_ds 데이터셋에서 한 배치의 데이터를 가져와 출력하는 과정
  • 첫 번째 배치의 정보를 출력
  • inputs와 targets는 한 번에 하나의 배치를 나타내는 텐서

  • inputs.shape
    -> inputs 텐서의 크기를 출력합니다. 이는 (배치 크기, 시퀀스 길이) 형태의 튜플

  • targets.shape
    -> targets 텐서의 크기를 출력합니다. 이는 (배치 크기,) 형태의 튜플입니다

단어를 집합으로 처리하기: BoW 방식

text_vectorization = TextVectorization(
    max_tokens=20000,
    output_mode="multi_hot",  
)
text_only_train_ds = train_ds.map(lambda x, y: x)  
text_vectorization.adapt(text_only_train_ds)

binary_1gram_train_ds = train_ds.map(
    lambda x, y: (text_vectorization(x), y),  
    num_parallel_calls=4)
binary_1gram_val_ds = val_ds.map(
    lambda x, y: (text_vectorization(x), y),
    num_parallel_calls=4)
binary_1gram_test_ds = test_ds.map(
    lambda x, y: (text_vectorization(x), y),
    num_parallel_calls=4)
  • max_tokens 매개변수는 단어 집합의 최대 크기를 지정하는 것으로, 가장 빈도가 높은 상위 20,000개의 단어만 고려하도록 설정

  • output_mode은 벡터화된 표현 방식을 나타내며, 여기서는 "multi_hot"으로 설정되어 있습니다. 이는 단어의 존재 여부를 이진 값으로 표현하는 방식

  • text_only_train_ds는 텍스트 데이터만을 포함하는 데이터셋
    -> map() 함수와 람다(lambda) 함수를 사용하여 데이터셋의 각 샘플에서 (x, y) 쌍을 (x,)로 변환

  • binary_1gram_train_ds = train_ds.map(
    lambda x, y: (text_vectorization(x), y),
    num_parallel_calls=4)

    -> 텍스트 벡터화된 형태로 변환한 데이터셋

  • num_parallel_calls 매개변수를 통해 병렬 처리를 지정하여 데이터셋의 변환 과정을 가속화

from tensorflow import keras
from tensorflow.keras import layers

def get_model(max_tokens=20000, hidden_dim=16):  
    inputs = keras.Input(shape=(max_tokens,))
    x = layers.Dense(hidden_dim, activation="relu")(inputs)
    x = layers.Dropout(0.5)(x)
    outputs = layers.Dense(1, activation="sigmoid")(x)
    model = keras.Model(inputs, outputs)
    model.compile(optimizer="rmsprop",
                  loss="binary_crossentropy",
                  metrics=["accuracy"])
    return model
  • keras를 사용하여 이진 분류 모델을 생성하는 get_model 함수를 정의하는 부분

  • max_tokens은 입력 시퀀스의 최대 토큰 수를 의미하며, hidden_dim은 은닉층의 차원 크기를 나타냅니다.

  • inputs는 입력 텐서를 정의하는 부분으로, shape는 (max_tokens,)로 지정

  • x는 입력을 받아 은닉층을 통과시키는 부분입니다. layers.Dense를 사용하여 hidden_dim 차원의 은닉층을 생성하고, 활성화 함수로 ReLU를 사용

  • layers.Dropout을 사용하여 0.5의 드롭아웃 비율을 지정

  • outputs = layers.Dense(1, activation="sigmoid")(x)
    -> 이진 분류를 위한 1개의 뉴런을 가진 출력층을 생성

callbacks = [
    keras.callbacks.ModelCheckpoint("binary_1gram.keras", save_best_only=True)
]
  • callbacks 리스트에 ModelCheckpoint 콜백을 추가합니다. 이 콜백은 모델을 학습하는 동안 최적의 모델을 저장하기 위해 사용
  • "binary_1gram.keras" 파일에 최적의 모델이 저장

이진 유니그램 모델 훈련하고 테스트하기

model.fit(binary_1gram_train_ds.cache(),
          validation_data=binary_1gram_val_ds.cache(),
          epochs=10,
          callbacks=callbacks)
model = keras.models.load_model("binary_1gram.keras")
print(f"테스트 정확도: {model.evaluate(binary_1gram_test_ds)[1]:.3f}")

이진 인코딩을 사용한 바이그램

text_vectorization = TextVectorization(
    ngrams=2,  
    max_tokens=20000,  
    output_mode="multi_hot", 
)
  • ngrams=2 :예를 들어, "hello world"라는 문장은 "hello", "world", "hello world"로 토큰화되어 표현
  • output_mode="multi_hot : 생성된 벡터는 단어의 존재 여부에만 관심을 두고, 단어의 순서나 빈도 등은 고려하지 않습니다.

TF-IDF 인코딩을 사용한 바이그램

  • <토큰 카운트를 반환하는 TextVectorization층>
text_vectorization = TextVectorization(
    ngrams=2,  
    max_tokens=20000,
    output_mode="count" 
)

-output_mode="count" : "count" 모드는 각 토큰의 등장 횟수를 카운트하여 텍스트를 표현하는 방식

  • <TF-IDF 가중치가 적용된 출력을 반환하는 TextVectorization 층>
text_vectorization = TextVectorization(
    ngrams=2,
    max_tokens=20000,
    output_mode="tf_idf"
)
  • output_mode="tf_idf"
    : "tf_idf" 모드는 Term Frequency-Inverse Document Frequency(TF-IDF) 방식으로 텍스트를 표현하는 방식
    : 각 토큰에 대해 해당하는 인덱스 위치에 TF-IDF 값이 설정
    : TF-IDF는 단어의 빈도와 문서의 역문서 빈도를 고려하여 단어의 중요도를 나타내는 값

<TF-IDF 바이그램 모델 훈련하고 테스트하기>

with tf.device("cpu"):
    text_vectorization.adapt(text_only_train_ds)   
tfidf_2gram_train_ds = train_ds.map(
    lambda x, y: (text_vectorization(x), y),
    num_parallel_calls=4)
tfidf_2gram_val_ds = val_ds.map(
    lambda x, y: (text_vectorization(x), y),
    num_parallel_calls=4)
tfidf_2gram_test_ds = test_ds.map(
    lambda x, y: (text_vectorization(x), y),
    num_parallel_calls=4)

model = get_model()
model.summary()
callbacks = [
    keras.callbacks.ModelCheckpoint("tfidf_2gram.keras",
                                    save_best_only=True)
]
model.fit(tfidf_2gram_train_ds.cache(),
          validation_data=tfidf_2gram_val_ds.cache(),
          epochs=10,
          callbacks=callbacks)
model = keras.models.load_model("tfidf_2gram.keras")
print(f"테스트 정확도: {model.evaluate(tfidf_2gram_test_ds)[1]:.3f}")
  • text_vectorization 객체를 CPU 디바이스에서 적용합니다.

  • text_vectorization.adapt(text_only_train_ds)
    ->adapt() 함수를 사용하여 학습 데이터셋을 기반으로 텍스트 벡터화 객체를 적합시킵니다.

  • map() :입력 데이터 x와 레이블 y를 입력으로 받아 텍스트 벡터화된 데이(text_vectorization(x), y)를 반환하는 람다 함수를 사용

inputs = keras.Input(shape=(1,), dtype="string")
  • keras.Input(): 입력 레이어를 정의하는 함수입니다. shape=(1,)은 입력의 형태를 나타내며, (1,)은 하나의 문자열을 입력으로 받는 것을 의미
outputs = model(processed_inputs)
  • model(processed_inputs): 이 부분은 이전에 학습한 모델에 텍스트 벡터화된 입력 데이터 processed_inputs를 주입하여 예측 결과를 얻는 과정입니다. 모델은 입력 데이터를 받아 예측 결과를 반환하는 함수처럼 동작합니다. 예측 결과를 outputs로 저장
inference_model = keras.Model(inputs, outputs)
  • keras.Model(): 입력과 출력을 연결하여 전체 모델을 정의하는 함수입니다. 여기서는 입력 데이터 inputs와 예측 결과 outputs를 연결하여 추론 모델을 생성
import tensorflow as tf
raw_text_data = tf.convert_to_tensor([
    ["That was an excellent movie, I loved it."],
])
predictions = inference_model(raw_text_data)
print(f"긍정적인 리뷰일 확률: {float(predictions[0] * 100):.2f} 퍼센트")
  • tf.convert_to_tensor() : 주어진 데이터를텐서로 변환하는 함수
predictions = inference_model(raw_text_data)
  • inference_model(raw_text_data): 이 부분은 추론 모델에 텍스트 데이터를 주입하여 예측 결과를 얻는 과정
print(f"긍정적인 리뷰일 확률: {float(predictions[0] * 100):.2f} 퍼센트")
  • predictions[0]은 첫 번째 예측 결과를 나타냅니다. float(predictions[0] * 100)은 예측 결과를 확률 값으로 변환하고, :.2f는 소수점 둘째 자리까지만 출력되도록 포맷팅합니다.

  • 데이터 값이 하나라고 해도 , 인덱스 0이기 떄문에 predictions[0] 을 해줘야한다

profile
밑거름이라고생각합니다

0개의 댓글