본문 바로가기

Natural_Language

[NLP] 02.자연어 처리 개발 준비 - Tensorflow

반응형

자연어 처리 개발을 하는 데 있어서 사용되는 라이브러리에 대한 소개

첫 번째로는 텐서플로우이다.

 

텐서플로우

tf.keras.layers.Dense

INPUT_SIZE = (20, 1)

input = tf.placeholder(tf.float32, shape = INPUT_SIZE)
hidden = tf.keras.layers.Dense(units = 10, activation = tf.nn.sigmoid)(input)
output = tf.keras.layers.Dense(units = 2, activation = tf.nn.sigmoid)(hidden)

- 10개의 노드를 가지는 은닉층이 있고, 최종 출력 값은 2개의 노드가 있는 신경망 구조

 

 

tf.keras.layers.Dropout

INPUT_SIZE = (20, 1)

input = tf.placeholder(tf.float32, shape = INPUT_SIZE)
dropout = tf.keras.Dropout(rate = 0.2)(input)
hidden = tf.keras.layers.Dense(units = 10, activation = tf.nn.sigmoid)(dropout)
output = tf.keras.layers.Dense(units = 2, activation = tf.nn.sigmoid)(hidden)

- 신경망 모델 생성 시 발생하는 여러 문제 중 하나는 과적합

- 과적합 문제는 정규화를 통해 해결하는데, 그중 가장 대표적인 방법이 드롭아웃이다.

- rate : 드롭아웃을 적용할 확률. dropout=0.2로 지정하면 전체 입력 값 중에서 20%를 0으로 만든다.

 

 

tf.keras.layers.Conv1D

INPUT_SIZE = (1, 28, 28)

input = tf.placeholder(tf.float32, shape = INPUT_SIZE)
conv = tf.keras.layers.Conv1D(filter = 10,
                              kernel_size = 3,
                              padding = 'same',
                              activation = tf.nn.relu)(input)

- Conv1D : 합성곱의 방향은 가로로 한 방향이다. 출력은 1-D Arrary(vector) 값이다.

- 자연어 처리 분야에서 사용하는 합성곱의 경우 각 단어 혹은 문자 벡터의 차원 전체에 대해 필터를 적용하기 때문에 주로 Conv1D를 사용한다. 

- kernel_size : 합성곱은 기본적으로 필터의 크기의 입력을 필요로 하는데, Conv1D는 입력값의 차원수와 높이가 동일하게 연산되기 때문에 필터의 가로길이만 설정하면 된다.

- filter : 총 몇 개의 필터를 사용할지 지정

- padding : 입력값과 출력 값의 가로 크기를 똑같이 만들고 싶다면 padding='same'으로 지정

 

 

tf.keras.layers.MaxPool1D

INPUT_SIZE = (1, 28, 28)

input = tf.placeholder(tf.float32, shape = INPUT_SIZE)
dropout = tf.keras.Dropout(rate=0.2)(input)
conv = tf.keras.layers.Conv1D(filter = 10,
                              kernel_size = 3,
                              padding = 'same',
                              activation = tf.nn.relu)(dropout)
max_pool = tf.keras.layers.MaxPool1D(pool_size = 3, paddin='same')(conv)
flatten = tf.keras.layers.Flatten()(max_pool)
hidden = tf.keras.layers.Dense(units = 50, activation = tf.nn.relu)(flatten)
output = tf.keras.layers.Dense(units = 10, activation = tf.nn.softmax)(hidden)

- 보통 피처 맵의 크기를 줄이거나 주요한 특징을 뽑아내기 위해 합성곱 이후에 적용

- 2가지 풀링 기법 : 맥스 풀링(최댓값을 뽑아내는 방식)과 평균 풀링(전체 값에 평균한 값을 뽑는 방식)

- MaxPool1D는 한 방향으로만 풀링이 진행

- pool_size : 풀링을 적용할 필터의 크기

- strides : 적용할 스트라이드 값

- 맥스 풀링 결괏값을 완전 연결 계층으로 연결하기 위해서는 행렬을 벡터로 만들어야 한다. 이때 flatten을 사용 

 

 

tf.data

- 머신러닝에서 문제를 해결할 때 많은 시간이 데이터를 다루는데 소요된다.

- tf.data에는 다양한 데이터 접근 방식이 존재한다.

- 예제를 실행시키기 위한 데이터 생성

samples = [ '너 오늘 이뻐 보인다',
            '나는 오늘 기분이 더러워',
            '끝내주는데, 좋은 일이 있나봐',
            '나 좋은 일이 생겼어',
            '아 오늘 진짜 짜증나',
            '환상적인데, 정말 좋은거 같아' ]
            
label = [[1], [0], [1], [1], [0], [1]]            
from tensorflow.keras import preprocessing

tokenizer = preprocessing.text.Tokenizer()
tokenizer.fit_on_texts(samples)
sequences = tokenizer.texts_to_sequences(samples)

word_index = tokenizer.word_index

- 토크나이저 객체를 사용해서, 텍스트로 구성된 데이터에서 각 단어를 단어의 인덱스로 변경하는 과정

- 즉, 텍스트 데이터를 수치화한 것이다.

- 이제 이 데이터를 텐서 플로의 tf.data를 활용하여 처리

dataset = tf.data.Dataset.from_tensor_slices((sequences, label))
iterator = dataset.make_one_shot_iterator()
next_data = iterator.get_next()

- tf.data.Dataset.from_tensor_slices : 주어진 데이터 sequences와 label을 묶어서 조각으로 만들고 같이 사용

- make_one_shot_iterator : 데이터를 하나씩 사용할 수 있게 만들어준다.

- get_next() : 데이터가 하나씩 나오게 되는 구조

 

위와 같이 tf.data를 사용하면 간단하게 데이터를 모델에 적용할 수 있다.

뿐만 아니라 데이터의 셔플, 배치, 반복 등 여러 기능들을 간단히 구현할 수 있다.

 

< BATCH >

BATCH_SIZE = 2

dataset = tf.data.Dataset.from_tensor_slices((sequences, label))
dataset = dataset.batch(BATCH_SIZE)
iterator = dataset.make_one_shot_iterator()
next_data = iterator.get_next()

with tf.Session() as sess:
	while True:
    	try:
        	print(sess.run(next_data))
        except tf.errors.OutOfRangeError:
        	break

- 배치 사이즈를 2로 설정하면, 한 번에 2개의 데이터가 나온다.

 

 

< SHUFFLE >

dataset = tf.data.Dataset.from_tensor_slices((sequences, label))
dataset = dataset.shuffle(len(sequences))
iterator = dataset.make_one_shot_iterator()
next_data = iterator.get_next()

with tf.Session() as sess:
	while True:
    	try:
        	print(sess.run(next_data))
        except tf.errors.OutOfRangeError:
        	break

- 위와 같이 시퀀스 길이만큼 셔플을 돌리면 기존에 출력하던 순서와 다르게 데이터가 출력

- 기본적으로 tf.data는 기존 데이터의 순서와 동일하게 데이터를 불러오는 구조이다.

- 따라서 셔플 함수를 사용하면 임의의 순서대로 데이터를 불러오는 구조가 된다.

- 셔플의 인자 값으로는 데이터의 전체 길이를 넣으면 도니다.

 

 

< EPOCH >

EPOCH = 2

dataset = tf.data.Dataset.from_tensor_slices((sequences, label))
dataset = dataset.repeat(EPOCH)
iterator = dataset.make_one_shot_iterator()
next_data = iterator.get_next()

with tf.Session() as sess:
	while True:
    	try:
        	print(sess.run(next_data))
        except tf.errors.OutOfRangeError:
        	break

- 모델을 데이터를 사용해 학습시킬 때 데이터를 한 번씩만 사용하는 것이 아니라, 여러 번 사용해서 학습하게 되는데, 전체 데이터를 몇 번 사용하는지를 지칭하는 말이 에폭이다.

- 기존의 tf.data를 사용하게 되면 데이터가 한 번씩만 사용되고 더 이상 데이터를 불러오지 않는데, 여러 번 반복되도록 

repeat함수를 사용해서 설정하면, 설정한 만큼 데이터를 계속 불러올 수 있다.

- repeat함수의 인자로 설정한 횟수만큼 전체 데이터가 반복해서 나온다.

 

 

<MAPPING >

# 하나의 입력값만 사용 할 경우 아래와 같이 정의
def mapping_fn(X, Y=None):
	input = {'x' : X}
    label = Y
    return input, label
   
# 두 개 이상의 입력값이 존재한다면 아래와 같이 정의   
def mapping_fn(X, Y=None):
	input = {'x1' : X, 'x2' : X2 }
    label = Y
    return input, label

dataset = tf.data.Dataset.from_tensor_slices((sequences, label))
dataset = dataset.map(mapping_fn)
iterator = dataset.make_one_shot_iterator()
next_data = iterator.get_next()

with tf.Session() as sess:
	while True:
    	try:
        	print(sess.run(next_data))
        except tf.errors.OutOfRangeError:
        	break

- 위와 같이 맵핑 함수를 지정하고, 데이터를 불러오면 'x' 혹은 'x1', 'x2'라는 키 값을 가지고 있는 딕셔너리 구조이다.

 

 

Estimator

- Estimator는 고수준 API로 모델 구현에만 집중할 수 있는 환경을 제공한다.

- 모델 구현 이외에 Train, Evaluate, Predict에 필요한 부가적인 구현은 Estimator의 함수로 손쉽게 사용 가능

- 모델 검증과 평가가 끝난 모델을 사용하기 위한 배포도 간단하게 구현 가능

 

Estimator 구현하기 위한 2가지 함수

1. 사용할 모델을 구현한 모델 함수

2. 모델이 적용될 데이터를 Estimator에 전달하기 위한 데이터 입력 함수

>> 올바르게 동작하기 위해서는 정해진 형식에 맞춰서 구현해야 함

 

첫 번째 함수 : 모델 함수

def model_fn(features, labels, mode, params, config):

	#모델 구현부분
    
    return tf.estimator.EstimatorSpec( ... )

- 모델 함수의 5가지 인자

- features : 모델에 적용되는 입력 값을 의미, 텐서 자료형이거나 딕셔너리 자료형.

- labels : 모델의 정답 라벨 값을 의미한다. 텐서 자료형이거나 딕셔너리 자료형 

            예측 과정에서는 라벨 값이 존재하지 않기 때문에 Estimator에서 자동으로 이 값이 들어오지 않는다.

- mode :  현재 모델 함수가 실행된 모드(학습, 검증, 예측)를 의미한다.

- params : 모델에 적용될 부가적인 하이퍼 파라미터 값. 딕셔너리 자료형.

- config : 모델에 적용할 설정값

 

estimator = tf.estimator.Estimator(model_fn = model_fn, model_dir = ..., config = ..., params = ...)

- 생성한 모델 함수를 Estimator 객체에 적용하면 되는데, Estimator 객체의 경우 위와 같이 생성하면 된다.

- 객체 생성 시 필요한 인자는 모델 함수와 변숫값이 저장되는 체크포인트 파일이 저장될 경로, 모델의 설정 값인 configs, 하이퍼 파라미터인 params를 넣어준다. 그리고 모델 함수(model_fn)는 필수적으로 넣어준다

 

#모델 학습
estimator.train(input_fn = ...)

#학습한 모델 검증
estimator.evaluate(input_fn = ...)

#학습한 모델을 통한 예측
estimator.predict(input_fn = ...)

- 객체 생성이 끝나면, 위와 같은 방법을 사용해서 실행할 수 있다.

- 위의 함수를 실행시킬 때는 입력 함수를 인자로 넣어줘야 한다.

 

 

두 번째 함수 :  입력 함수

def train_input_fn():
	
    #데이터 파이프라인 구현 부분
    
    return features, labels

- 위와 같은 방법으로 데이터에 맞게 입력 함수를 구현하면 된다.

- 데이터 파이프라인 구현 부분에는 데이터의 셔플, 배치, 반복 등의 기능이 들어갈 것이다.

- 보통 입력 함수의 경우 보통 학습, 검증, 예측 상황에 맞는 기능들이 모두 다르기 때문에 각 입력 함수를 따로 구현한다.

 

 

Full Process

- 우선 각 단어로 구성된 입력값은 임베딩 된 벡터로 변형된다. 

- 이후 각 벡터를 평균해서 하나의 벡터로 만들어준다.

- 하나의 은닉층을 거친 후 하나의 결괏값을 뽑는 구조다. 

- 마지막으로 나온 결괏값에 시그모이드 함수를 적용해 0과 1 사이의 값을 구한다.

 

데이터 전처리

import tensorflow as tf
from tensorflow.keras import preprocessing

samples = [ '너 오늘 이뻐 보인다',
            '나는 오늘 기분이 더러워',
            '끝내주는데, 좋은 일이 있나봐',
            '나 좋은 일이 생겼어',
            '아 오늘 진짜 짜증나',
            '환상적인데, 정말 좋은거 같아' ]
            
label = [[1], [0], [1], [1], [0], [1]]     

tokenizer = preprocessing.text.Tokenizer()
tokenizer.fit_on_texts(samples)
sequences = tokenizer.texts_to_sequences(samples)

word_index = tokenizer.word_index

 

 

데이터 입력 함수

EPOCH = 100

def train_input_fn():
	
    dataset = tf.data.Dataset.from_tensor_slices((sequences, label))
    dataset = dataset.repeat(EPOCH)
    dataset = dataset.batch(1)
    dataset = dataset.shuffle(len(sequences))
    iterator = dataset.make_one_shot_iterator()
    
    return iterator.get_next()	

 

 

모델 함수 정의

VOCAB_SIZE = len(word_index) + 1
EMB_SIZE = 128

def model_fn(features, labels, mode):
    
    TRAIN = mode = tf.estimator.ModeKeys.TRAIN
    EVAL = mode = tf.estimator.ModeKeys.EVAL
    PREDICT = mode = tf.estimator.ModeKeys.PREDICT
    
    embed_input = tf.keras.layers.Embedding(VOCAB_SIZE, EMB_SIZE)(features)
    embed_input = tf.reduce_mean(embed_input, axis = 1)
    
    hidden_layer = tf.keras.layers.Dense(128, activation = tf.nn.relu)(embed_input)
    output_layer = tf.keras.layers.Dense(1)(hidden_layer)
    output = tf.nn.sigmoid(output_layer)
    
    loss = tf.losses.mean_squared_error(output, labels)
    
    if TRAIN:
    	global_step = tf.train.get_global_step()
        train_op = tf.train.AdamOptimizer(1e-3).minimize(loss, global_step)
        
        return tf.estimator.EstimatorSpec( mode = mode, train_op = train_op, loss = loss)

- 우선 모델 함수에서 사용될 상숫값을 정의해준 후 모델 함수를 구현

- 정의한 상숫값은 전체 단어의 개수와 임베딩 벡터의 크기를 의미

- 현재 실행 모드가 어떠한 상태인지 확인하기 위한 TRAIN, EVAL, PREDICT 정의 : 해당하는 상태가 True

- 입력값을 우선 임베딩 벡터 형태로 만든다.

- 텐서 플로의 reduce_mean 함수를 통해 평균을 구해서 하나의 입력 벡터로 만든다

- 이 값을 Dense를 거쳐 출력 값을 만든다.

- 출력값을 시그모이드 함수를 적용해 0과 1 사이의 값으로 만들면 최종 출력 값이 나온다.

- 마지막으로 학습 상태 일 때 모델을 학습할 수 있도록 손실 값과 옵티마이저를 설정해야 한다.

- 이렇게 구한 두 값을 학습모드인 경우 EstimatorSpec의 인자 값으로 반환하면 모델 함수 구현이 끝난다.

 

 

실제 학습

DATA_OUT_PATH = './data_out/'

import os

if not os.path.exists(DATA_OUT_PATH):
	 os.makedirs(DATA_OUT_PATH)
     
estimator = tf.estimator.Estimator(model_fn = model_fn, model_dir = DATA_OUT_PATH + '/checkpoint/dnn')

estimator.train(train_input_fn)

- 위와 같이 학습을 거친 후 손실 값이 더는 떨어지지 않는다면 이제 학습한 모델을 사용해 검증과 예측을 하면 된다.

반응형