텍스트를 위한 딥러닝
1. 텍스트 표준화
2. 텍스트 분할
3. 어휘 인덱싱
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)
-> 주어진 데이터셋을 기반으로 단어 집합을 생성하고 , 텍스트를 인코딩하거나 디코딩하는 기능 제공
입력된 텍스트를 소문자로 변환합니다.
string.punctuation 포함된 모든 구두점 문자를 제거합니다.
결과적으로, 소문자로 변환된 텍스트에서 구두점이 제거된 텍스트를 반환합니다.
tokenize(self, text)
입력된 텍스트를 공백을 기준으로 분리하여 토큰화합니다.
분리된 토큰들을 리스트로 반환합니다.
주어진 데이터셋을 기반으로 단어 집합(vocabulary)을 생성합니다.
초기 단어 집합에는 빈 문자열("")과 미확인 단어("[UNK]")를 포함합니다.
데이터셋의 각 텍스트에 대해 standardize 함수를 적용하여 텍스트를 표준화합니다.
표준화된 텍스트를 토큰화하여 각 토큰을 단어 집합에 추가합니다. 단어가 이미 단어 집합에 존재한다면 추가하지 않습니다.
단어 집합과 역반전 단어 집합(inverse_vocabulary)을 생성합니다. 역반전 단어 집합은 인덱스와 단어를 매핑한 딕셔너리입니다.
입력된 텍스트를 표준화합니다.
표준화된 텍스트를 토큰화합니다.
각 토큰에 대해 단어 집합에서 해당 단어의 인덱스를 가져옵니다. 단어가 단어 집합에 존재하지 않는 경우 1(미확인 단어)을 할당합니다.
인덱스의 리스트를 반환합니다.
인덱스의 시퀀스를 입력으로 받습니다.
각 인덱스를 역반전 단어 집합에서 해당하는 단어로 변환합니다. 만약 단어가 없다면 미확인 단어("[UNK]")로 변환합니다.
변환된 단어들을 공백으로 연결하여 복원된 텍스트를 반환합니다.
test_sentence = "I write, rewrite, and still rewrite again"
encoded_sentence = vectorizer.encode(test_sentence)
print(encoded_sentence)
decoded_sentence = vectorizer.decode(encoded_sentence)
print(decoded_sentence)
from tensorflow.keras.layers import TextVectorization
text_vectorization = TextVectorization(
output_mode="int",
)
TextVectorization 클래스는 TensorFlow에서 제공하는 텍스트 벡터화를 위한 레이어입니다.
TextVectorization 클래스의 객체를 생성하고 output_mode 매개변수를 "int"로 설정
텍스트 전처리: 주어진 텍스트 데이터를 사전 정의된 전처리 단계를 거쳐 벡터화할 수 있습니다. 전처리 단계에는 토큰화, 소문자 변환, 구두점 제거 등이 포함될 수 있습니다.
단어 집합 생성: 주어진 텍스트 데이터를 기반으로 단어 집합(vocabulary)을 생성합니다. 각 단어는 고유한 정수 인덱스로 매핑됩니다.
벡터화: 텍스트 데이터를 단어 인덱스의 시퀀스로 변환합니다. 각 문장은 단어의 인덱스들로 구성된 벡터로 표현됩니다.
"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
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)
text_vectorization.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)
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에서 찾아 해당하는 단어로 변환하고, 단어들을 공백으로 구분하여 문자열로 결합
-> 벡터화된 문장을 원래의 텍스트로 디코딩한 결과를 보여줍니다
단어 그룹을 표현하는 두 가지 방법 : 집홥과 시퀀스
!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
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)
]
이진 유니그램 모델 훈련하고 테스트하기
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",
)
TF-IDF 인코딩을 사용한 바이그램
text_vectorization = TextVectorization(
ngrams=2,
max_tokens=20000,
output_mode="count"
)
-output_mode="count" : "count" 모드는 각 토큰의 등장 횟수를 카운트하여 텍스트를 표현하는 방식
text_vectorization = TextVectorization(
ngrams=2,
max_tokens=20000,
output_mode="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")
outputs = model(processed_inputs)
inference_model = 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} 퍼센트")
predictions = inference_model(raw_text_data)
print(f"긍정적인 리뷰일 확률: {float(predictions[0] * 100):.2f} 퍼센트")
predictions[0]은 첫 번째 예측 결과를 나타냅니다. float(predictions[0] * 100)은 예측 결과를 확률 값으로 변환하고, :.2f는 소수점 둘째 자리까지만 출력되도록 포맷팅합니다.
데이터 값이 하나라고 해도 , 인덱스 0이기 떄문에 predictions[0] 을 해줘야한다