오토인코더를 이용한 비정상 거래 검출 모델의 구현 #2
MNIST 오토인코더 샘플
조대협 (http://bcho.tistory.com)
신용카드 이상 거래 감지 시스템 구현에 앞서서, 먼저 오토인코더에 대한 이해를 하기 위해서 오토 인코더를 구현해보자. 오토 인코더 샘플 구현은 MNIST 데이타를 이용하여 학습하고 복원하는 코드를 만들어 보겠다.
이 코드의 원본은 Etsuji Nakai 님의 https://github.com/enakai00/autoencoder_example 코드를 사용하였다.
데이타 전처리
이 예제에서는 텐서플로우에 포함된 MNIST 데이타 tensorflow.contrib.learn.python.learn.datasets 를 tfrecord 로 변경해서 사용한다.TFRecord에 대한 설명은 http://bcho.tistory.com/1190 를 참고하기 바란다.
MNIST 데이타를 TFRecord로 변경하는 코드는 https://github.com/bwcho75/tensorflowML/blob/master/LAB5-Create-MNIST-TFRecord-Data.ipynb 에 있다. 이 코드를 실행하면, ./data/train.tfrecord ./data/test.tfrecords 에 학습 및 테스트 데이타 파일이 생성된다. 이 파일들을 아래서 만들 모델이 들어가 있는 디렉토리 아래 /data 디렉토리로 옮겨놓자.
학습 코드 구현
학습에 사용되는 모델은 텐서플로우 하이레벨 API인 tf.layers와 Estimator를 이용해서 구현한다.
하이레벨 API를 사용하는 이유는 http://bcho.tistory.com/1195 http://bcho.tistory.com/1196 에서도 설명했듯이 구현이 상대적으로 쉬울뿐더러, 분산 학습이 가능하기 때문이다.
전체 코드는 hhttps://github.com/bwcho75/tensorflowML/blob/master/LAB5-Autoencoder-MNIST-Estimator.ipynb 에 공유되어 있다.
데이타 입력부
데이타 입력 부분은 tfrecord 파일을 읽어서, 파일 큐를 생성해서 input_fn 을 생성하는 부분이다. 이렇게 생성된 input_fn 함수는 Estimator 를 통해서, 학습과 테스트(검증) 데이타로 피딩되게 된다.
데이타 입력 부분은 read_and_decode함수와 input_fn 함수로 구현되어 있는데, 각각을 살펴보자
def read_and_decode(filename_queue):
reader = tf.TFRecordReader()
_,serialized_example = reader.read(filename_queue)
features = tf.parse_single_example(
serialized_example,
features={
'image_raw':tf.FixedLenFeature([],tf.string),
'label':tf.FixedLenFeature([],tf.int64),
})
image = tf.decode_raw(features['image_raw'],tf.uint8)
image.set_shape([784]) #image shape is (784,)
image = tf.cast(image,tf.float32)*(1.0/255)
label = tf.cast(features['label'],tf.int32)
return image,label
read_and_decode 함수는 filename_queue에서, 파일을 읽어서 순서대로 TFRecoderReader를 읽어서 파싱한후에, image_raw이름으로 된 피쳐와, label로 된 피쳐를 읽어서 각각 image와 label 이라는 텐서에 저장한다.
image는 차원을 맞추기 위해서 set_shape를 이용하여 1차원으로 784의 길이를 가진 텐서로 변환하고, 학습에 적절하도록 데이타를 regulization 을 하기 위해서, 1.0/255 를 곱해줘서 1~255값의 칼라값을 0~1사이의 값으로 변환한다.
그리고 label값은 0~9를 나타내는 숫자 라벨이기 때문에, tf.int32로 형 변환을 한다.
변환이 끝난 image와 label 텐서를 리턴한다.
def input_fn(filename,batch_size=100):
filename_queue = tf.train.string_input_producer([filename])
image,label = read_and_decode(filename_queue)
images,labels = tf.train.batch(
[image,label],batch_size=batch_size,
capacity=1000+3*batch_size)
#images : (100,784), labels : (100,1)
return {'inputs':images},labels
Input_fn 함수는 실제로 Estimator에 값을 피딩하는 함수로, 입력 받은 filename으로 파일이름 큐를 만들어서 read_and_decode 함수에 전달 한 후, image와 label 값을 리턴받는다.
리턴 받은 값을 바로 리턴하지 않고 배치 학습을 위해서 tf.train.batch를 이용하여 배치 사이즈(batch_size)만큼 묶어서 리턴한다.
모델 구현부
데이타 입력 부분이 완성되었으면, 데이타를 읽어서 학습 하는 부분을 살펴보자.
모델 구현
아래는 모델을 구현한 autoecndoer_model_fn 함수이다.
Custom Estimator를 구현하기 위해서 사용한 구조이다.
def autoencoder_model_fn(features,labels,mode):
input_layer = features['inputs']
dense1 = tf.layers.dense(inputs=input_layer,units=256,activation=tf.nn.relu)
dense2 = tf.layers.dense(inputs=dense1,units=128,activation=tf.nn.relu)
dense3 = tf.layers.dense(inputs=dense2,units=16,activation=tf.nn.relu)
dense4 = tf.layers.dense(inputs=dense3,units=128,activation=tf.nn.relu)
dense5 = tf.layers.dense(inputs=dense4,units=256,activation=tf.nn.relu)
output_layer = tf.layers.dense(inputs=dense5,units=784,activation=tf.nn.sigmoid)
#training and evaluation mode
if mode in (Modes.TRAIN,Modes.EVAL):
global_step = tf.contrib.framework.get_or_create_global_step()
label_indices = tf.cast(labels,tf.int32)
loss = tf.reduce_sum(tf.square(output_layer - input_layer))
tf.summary.scalar('OptimizeLoss',loss)
if mode == Modes.TRAIN:
optimizer = tf.train.AdamOptimizer(learning_rate=0.001)
train_op = optimizer.minimize(loss,global_step=global_step)
return tf.estimator.EstimatorSpec(mode,loss = loss, train_op = train_op)
if mode == Modes.EVAL:
eval_metric_ops = None
return tf.estimator.EstimatorSpec(
mode,loss=loss,eval_metric_ops = eval_metric_ops)
# prediction mode
if mode == Modes.PREDICT:
predictions={
'outputs':output_layer
}
export_outputs={
'outputs':tf.estimator.export.PredictOutput(predictions)
}
return tf.estimator.EstimatorSpec(
mode,predictions=predictions,export_outputs=export_outputs) #이부분 코드 상세 조사할것
오토인코더 네트워크를 구현하기 위한 코드는 다음 부분으로 복잡하지 않다
input_layer = features['inputs']
dense1 = tf.layers.dense(inputs=input_layer,units=256,activation=tf.nn.relu)
dense2 = tf.layers.dense(inputs=dense1,units=128,activation=tf.nn.relu)
dense3 = tf.layers.dense(inputs=dense2,units=16,activation=tf.nn.relu)
dense4 = tf.layers.dense(inputs=dense3,units=128,activation=tf.nn.relu)
dense5 = tf.layers.dense(inputs=dense4,units=256,activation=tf.nn.relu)
output_layer = tf.layers.dense(inputs=dense5,units=784,activation=tf.nn.sigmoid)
input_fn에서 피딩 받은 데이타를 input_layer로 받아서, 각 256,128,16,128,,256의 노드로 되어 있는 5개의 네트워크를 통과한 후에, 최종적으로 784의 아웃풋과 sigmoid 함수를 활성화(activation function)으로 가지는 output layer를 거쳐서 나온다.
다음 모델의 모드 즉 학습, 평가, 그리고 예측 모드에 따라서 loss 함수나 train_op 등이 다르게 정해진다.
#training and evaluation mode
if mode in (Modes.TRAIN,Modes.EVAL):
global_step = tf.contrib.framework.get_or_create_global_step()
label_indices = tf.cast(labels,tf.int32)
loss = tf.reduce_sum(tf.square(output_layer - input_layer))
tf.summary.scalar('OptimizeLoss',loss)
학습과 테스트 모드일 경우, global_step을 정하고, loss 함수를 정의한다.
학습 모드일 경우에는 아래와 같이 옵티마이저를 정하고,이 옵티마이저를 이용하여 loss 값을 최적화 하도록 하는 train_op를 정의해서 EstimatorSpec을 만들어서 리턴하였다.
if mode == Modes.TRAIN:
optimizer = tf.train.AdamOptimizer(learning_rate=0.001)
train_op = optimizer.minimize(loss,global_step=global_step)
return tf.estimator.EstimatorSpec(mode,loss = loss, train_op = train_op)
테스트 모드 일 경우에는 옵티마이즈할 필요가 없기 때문에, 옵티마이져를 정의하지 않고 loss 값을 리턴하고, 평가를 위한 Evalutaion metrics를 정해서 리턴한다. 아래 코드는 별도로 evaluation metrics 를 정의하지 않고, 디폴트 메트릭스를 사용하였다.
if mode == Modes.EVAL:
eval_metric_ops = None
return tf.estimator.EstimatorSpec(
mode,loss=loss,eval_metric_ops = eval_metric_ops)
예측 모드일 경우에는 loss 값이나 optimizer 등의 정의가 필요 없고, output값을 어떤 값을 내보낼지만 정의하면 되고, 예측 모델 (prediction model)을 프로토콜 버퍼 포맷으로 export 할때의 구조를 정의하기 위해서 export_outpus 부분만 아래와 같이 정의해주면 된다.
# prediction mode
if mode == Modes.PREDICT:
predictions={
'outputs':output_layer
}
export_outputs={
'outputs':tf.estimator.export.PredictOutput(predictions)
}
return tf.estimator.EstimatorSpec(
mode,predictions=predictions,export_outputs=export_outputs)
Estimator 생성
모델에 대한 정의가 끝났으면, Estimator를 생성하는데, Estimator 정의는 아래와 같이 앞에서 정의한 모델인 autoencoder_model_fn을 정의해주고
def build_estimator(model_dir):
return tf.estimator.Estimator(
model_fn = autoencoder_model_fn,
model_dir = model_dir,
config=tf.contrib.learn.RunConfig(save_checkpoints_secs=180))
실험 (Experiment) 구현
앞에서 구현된 Estimator를 이용하여, 학습과 테스트를 진행할 수 있는데, 직접 Estimator를 불러사용하는 방법 이외에 Experiment 라는 클래스를 사용하면, 이 부분을 단순화 할 수 있다.
Experiment에는 사용하고자 하는 Estimator와 학습과 테스트용 데이타 셋, 그리고 export 전략 및, 학습,테스트 스탭을 넣어주면 자동으로 Estimator를 이용하여 학습과 테스트를 진행해준다.
아래는 Experiment 를 구현한 예이다.
def generate_experiment_fn(data_dir,
train_batch_size = 100,
eval_batch_size = 100,
train_steps = 1000,
eval_steps = 1,
**experiment_args):
def _experiment_fn(output_dir):
return Experiment(
build_estimator(output_dir),
train_input_fn=get_input_fn('./data/train.tfrecords',batch_size=train_batch_size),
eval_input_fn=get_input_fn('./data/test.tfrecords',batch_size=eval_batch_size),
export_strategies = [saved_model_export_utils.make_export_strategy(
serving_input_fn,
default_output_alternative_key=None,
exports_to_keep=1)
],
train_steps = train_steps,
eval_steps = eval_steps,
**experiment_args
)
return _experiment_fn
learn_runner.run(
generate_experiment_fn(
data_dir='./data/',
train_steps=2000),
OUTDIR)
대략 50,000 스탭까지 학습을 진행하면 loss 값 500 정도로 수렴 되는 것을 확인할 수 있다.
검증 코드 구현
검증 코드는 MNIST 데이타에서 테스트용 데이타를 로딩하여 테스트 이미지를 앞에서 학습된 이미지로 인코딩했다가 디코딩 하는 예제이다. 입력 이미지와 출력 이미지가 비슷할 수 록 제대로 학습된것이라고 볼수 있다.
Export 된 모듈 로딩
아래 코드는 앞의 학습과정에서 Export 된 학습된 모델을 로딩하여 새롭게 그래프를 로딩 하는 코드이다.
#reset graph
tf.reset_default_graph()
export_dir = OUTDIR+'/export/Servo/'
timestamp = os.listdir(export_dir)[0]
export_dir = export_dir + timestamp
print(export_dir)
sess = tf.Session()
meta_graph = tf.saved_model.loader.load(sess,[tf.saved_model.tag_constants.SERVING],export_dir)
model_signature = meta_graph.signature_def['serving_default']
input_signature = model_signature.inputs
output_signature = model_signature.outputs
print(input_signature.keys())
print(output_signature.keys())
tf.reset_default_graph()를 이용하여, 그래프를 리셋 한후, tf.save_model.loader.load()를 이용하여 export_dir에서 Export 된 파일을 읽어서 로딩한다.
다음 입력값과 출력값의 텐서 이름을 알기 위해서 model_signature.input과 output 시그니쳐를 읽어낸후 각각 keys()를 이용하여 입력과 출력 텐서 이름을 출력하였다.
이 텐서 이름은 로딩된 그래프에 입력을 넣고 출력 값을 뽑을 때 사용하게 된다.
테스트 코드 구현
학습된 모델이 로딩 되었으면 로딩된 모델을 이용하여 MNIST 테스트 데이타를 오토 인코더에 넣어서 예측을 진행 해본다.
from tensorflow.examples.tutorials.mnist import input_data
mnist = input_data.read_data_sets("/tmp/data/", one_hot=True)
images, labels = mnist.test.images, mnist.test.labels
feed_dict = {sess.graph.get_tensor_by_name(input_signature['inputs'].name): mnist.test.images[:10]}
output = sess.graph.get_tensor_by_name(output_signature['outputs'].name)
results = sess.run(output, feed_dict=feed_dict)
fig = plt.figure(figsize=(4,15))
for i in range(10):
subplot = fig.add_subplot(10,2,i*2+1)
subplot.set_xticks([])
subplot.set_yticks([])
subplot.imshow(images[i].reshape((28,28)), vmin=0, vmax=1,
cmap=plt.cm.gray_r, interpolation="nearest")
subplot = fig.add_subplot(10,2,i*2+2)
subplot.set_xticks([])
subplot.set_yticks([])
subplot.imshow(results[i].reshape((28,28)), vmin=0, vmax=1,
cmap=plt.cm.gray_r, interpolation="nearest")
plt.show()
feed_dict = {sess.graph.get_tensor_by_name(input_signature['inputs']
댓글을 달아 주세요
관리자의 승인을 기다리고 있는 댓글입니다