블로그 이미지
평범하게 살고 싶은 월급쟁이 기술적인 토론 환영합니다.같이 이야기 하고 싶으시면 부담 말고 연락주세요:이메일-bwcho75골뱅이지메일 닷컴. 조대협


Archive»


 
 

오토 인코더를 이용한 신용카드 비정상 거래 검출 

#3 학습 데이타 전처리


조대협 (http://bcho.tistory.com)




앞의 글들 (http://bcho.tistory.com/1198 http://bcho.tistory.com/1197 ) 에서 신용카드 이상 검출을 하기 위한 데이타에 대한 분석과, 오토 인코더에 대한 기본 원리 그리고 오토 인코더에 대한 샘플 코드를 살펴보았다.


이제 실제 모델을 만들기에 앞서 신용카드 거래 데이타를 학습에 적절하도록 전처리를 하도록한다.

데이타양이 그리 크지 않기 때문에, 데이타 전처리는 파이썬 데이타 라이브러리인 pandas dataframe을 사용하였다. 여기서 사용된 전처리 코드는 https://github.com/bwcho75/tensorflowML/blob/master/autoencoder/creditcard_fraud_detection/2.data_normalization.ipynb 에 공개되어 있다.


데이타 전처리 과정

신용카드 거래 데이타를 머신러닝 학습의 검증과 테스트에 적절하도록 다음과 같은 절차를 통하여 데이타를 전처리하여 CSV 파일로 저장하였다.

데이타 정규화

학습 데이타에 여러가지 피쳐를 사용하는데, 예를 들어 피쳐 V1의 범위가 -10000~10000이고, 피쳐 V2의 범위가 10~20 이라면, 각 피쳐의 범위가 차이가 매우 크기 때문에, 경사 하강법등을 이용할때, 학습 시간이 더디거나 또는 제대로 학습이 되지 않을 수 있다. 자세한 내용은 김성훈 교수님의 모두를 위한 딥러닝 강좌중 정규화 부분  https://www.youtube.com/watch?v=1jPjVoDV_uo&feature=youtu.be 을 참고하기 바란다.

그래서 피쳐의 범위를 보정(정규화)하여 학습을 돕는 과정을 데이타 정규화라고 하는데, 정규화에는 여러가지 방법이 있다. 여기서 사용한 방법은 Fearture scaling이라는 방법으로, 모든 피쳐의 값들을 0~1사이로 변환하는 방법이다. 위에서 언급한 V1은 -10000~10000의 범위가 0~1사이로 사상되는 것이고, V2도 10~20의 범위가 0~1사이로 사상된다.

공식은 아래와 같은데



참고 https://en.wikipedia.org/wiki/Normalization_(statistics)


정규화된 값은 = (원본값 - 피쳐의 최소값) / (피쳐의 최대값 - 피쳐의 최소값)


으로 계산한다.

앞의 V1값에서 0의 경우는 (0 - (-10000)) / (10000 - (-10000)) = 0.5 로 사상이 되는것이다.


그러면 신용카드 데이타에서 V1~V28 컬럼을 Feature scaling을 위해서 정규화를 하려면

df_csv = pd.read_csv('./data/creditcard.csv')

CSV에서 원본 데이타를 읽는다.

읽어드린 데이타의 일부를 보면 다음과 같다.


df_csv 는 데이타의 원본값을 나타내고,  df_csv.min() 각 컬럼의 최소값, df_csv.max()는 각 컬럼의 최대값을 나타낸다. 이 값들을 이용하여 위의 Feature Scaling 공식으로 구현하면 아래와 같이 된다


df_norm = (df_csv - df_csv.min() ) / (df_csv.max() - df_csv.min() )


이렇게 정규화된 값을 출력해보면 다음과 같다.




V1 컬럼의 -1.359807이 정규화후에 0.935192 로 변경된것을 확인할 수 있고 다른 필드들도 변경된것을 확인할 수 있다.

데이타 분할

전체 데이타를 정규화 하였으면 데이타를 학습용, 검증용, 테스트용 데이타로 나눠야 하는데, 오토 인코더의 원리는 정상적인 데이타를 학습 시킨후에, 데이타를 넣어서 오토인코더가 학습되어 있는 정상적인 패턴과 얼마나 다른가를 비교하는 것이기 때문에 학습 데이타에는 이상거래를 제외하고 정상적인 거래만으로 학습을 한다.

이를 위해서 먼저 데이타를 정상과 비정상 데이타셋 두가지로 분리한다.

아래 코드는 Class=1이면 비정상, Class=0이면 정상인 데이타로 분리가 되는데, 정상 데이타는 df_norm_nonfraud에 저장하고, 비정상 데이타는 df_norm_fraud에 저장하는 코드이다.

# split normalized data by label
df_norm_fraud=df_norm[ df_norm.Class==1.0] #fraud
df_norm_nonfraud=df_norm[ df_norm.Class==0.0] #non_fraud


정상 데이타를 60:20:20 비율로 학습용, 테스트용, 검증용으로 나누고, 비정상 데이타는 학습에는 사용되지 않고 테스트용 및 검증용에만 사용되기 때문에, 테스트용 및 검증용으로 50:50 비율로 나눈다.


# split non_fraudfor 60%,20%,20% (training,validation,test)
df_norm_nonfraud_train,df_norm_nonfraud_validate,df_norm_nonfraud_test = \
   np.split(df_norm_nonfraud,[int(.6*len(df_norm_nonfraud)),int(.8*len(df_norm_nonfraud))])


numpy의 split 함수를 쓰면 쉽게 데이타를 분할 할 수 있다. [int(.6*len(df_norm_nonfraud)),int(.8*len(df_norm_nonfraud))] 가 데이타를 분할하는 구간을 정의하는데,  데이타 프레임의 60%, 80% 구간을 데이타 분할 구간으로 하면 0~60%, 60~80%, 80~100% 구간 3가지로 나누어서 데이타를 분할하여 리턴한다. 같은 방식으로 아래와 같이 비정상 거래 데이타도 50% 구간을 기준으로 하여 두 덩어리로 데이타를 나눠서 리턴한다.


# split fraud data to 50%,50% (validation and test)
df_norm_fraud_validate,df_norm_fraud_test = \
   np.split(df_norm_fraud,[int(0.5*len(df_norm_fraud))])

데이타 합치기

다음 이렇게 나눠진 데이타를 테스트용 데이타는 정상과 비정상 거래 데이타를 합치고, 검증용 데이타 역시 정상과 비정상 거래를 합쳐서 각각 테스트용, 검증용 데이타셋을 만들어 낸다.

두개의 데이타 프레임을 합치는 것은 아래와 같이 .append() 메서드를 이용하면 된다.


df_train = df_norm_nonfraud_train.sample(frac=1)
df_validate = df_norm_nonfraud_validate.append(df_norm_fraud_validate).sample(frac=1)
df_test = df_norm_nonfraud_test.append(df_norm_fraud_test).sample(frac=1)

셔플링

데이타를 합치게 되면, 테스트용과 검증용 데이타 파일에서 처음에는 정상데이타가 나오다가 뒷부분에 비정상 데이타가 나오는 형태가 되기 때문에 테스트 결과가 올바르지 않을 수 있는 가능성이 있다. 그래서, 순서를 무작위로 섞는 셔플링(Shuffling) 작업을 수행한다.

셔플링은 위의 코드에서 .sample(frac=1)에 의해서 수행되는데, .sample은 해당 데이타 프레임에서 샘플 데이타를 추출하는 명령으로 frac은 샘플링 비율을 정의한다 1이면 100%로, 전체 데이타를 가져오겠다는 이야기 인데, sample()함수는 데이타를 가지고 오면서 순서를 바꾸기 때문에, 셔플링된 결과를 리턴하게 된다.


전체 파이프라인을 정리해서 도식화 해보면 다음과 같다.


다음글에서는 이렇게 정재된 데이타를 가지고 학습할 오토인코더 모델을 구현해보도록 한다.


저작자 표시 비영리
신고

지난 1년 회고

사는 이야기 | 2017.09.20 00:03 | Posted by 조대협

구글 입사한지 대략 1년6개월 정도 지남

기술적으로는 그동안 애매했던 빅데이타 분석에 대한 기술과 어느정도 인사이트를 가지게 되었고 머신러닝에 대한 전반적인 이해와 딥러닝 기술에 대한 이해와 기본적인 구현능력을 가지게 되었음

영어는 쬐끔 늘어난거 같으나 큰 발전은 없고


크게 봤을때, 집에 일찍 들어오기 때문에, 애들과 보내는 시간이 압도적으로 늘어난 반면 공부에 투자하는 시간은 상대적으로 줄었음.

돌이켜 보면 1년반이면 길다면 길고 짧다면 짧은 시간인데, 기술적으로 어느정도 발전이 있었음.

2017이 엊그제 같은데 벌써 3분기를 달리고 있는데. 연말에 정리는 어떻게 될련지 궁금하다.


남은건 딥러닝 기술을 더 발전 시키는것 이외에, 데이타 시각화 및 분석 능력을 가지는게 단기적인 목표


9월 20일 생각 로그


저작자 표시 비영리
신고

'사는 이야기' 카테고리의 다른 글

지난 1년 회고  (0) 2017.09.20
블로그 400만 돌파  (2) 2017.07.17
2016년 업무 종료....  (3) 2016.12.29
세번째 책이 나왔습니다.  (3) 2016.08.29
2015.12.16 기록 블로그 방문자수 200만 돌파  (1) 2015.12.16
조직 문화에 대한 메모  (0) 2015.09.25

오토인코더를 이용한 비정상 거래 검출 모델의 구현 #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/autoencoder/MNIST/create_record.py 에 있다. 이 코드를 실행하면, /tmp/data/train.tfrecord /tmp/data/test.tfrecords 에 학습 및 테스트 데이타 파일이 생성된다. 이 파일들을 아래서 만들 모델이 들어가 있는 디렉토리 아래 /data 디렉토리로 옮겨놓자.

학습 코드 구현

학습에 사용되는 모델은 텐서플로우 하이레벨 API인 tf.layers와 Estimator를 이용해서 구현한다.

하이레벨 API를 사용하는 이유는 http://bcho.tistory.com/1195 http://bcho.tistory.com/1196 에서도 설명했듯이 구현이 상대적으로 쉬울뿐더러, 분산 학습이 가능하기 때문이다.


전체 코드는 https://github.com/bwcho75/tensorflowML/blob/master/autoencoder/MNIST/MNIST_AutoEncoder.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'].name): mnist.test.images[:10]} 부분은 입력 데이타를 정의하는 부분으로, 앞에 모델 로딩시 사용했던 것과 같이 입력 텐서의 이름을 얻기 위해서 input_signature의 이름을 얻은 후, 그래프에서 그 이름으로 텐서를 가지고 온다. 그 이후, 가져온 텐서에 mnist 테스트 데이타셋에서 이미지 부분을 0~9 개를 피딩한다.


출력 값도 마찬가지로 output_signature에서 output 텐서 이름을 가지고 온후에, get_tensor_by_name 으로 해당 텐서를 가지고 온후에, output 변수에 저장한다.


마지막으로 sess.run을 통해서 feed_dict 값을 피딩하고, output 텐서를 리턴하여, 결과를 results로 리턴한다.

나머지는 리턴된 10개의 prediction result를 matplotlib를 이용하여 시각화 한 결과이다.

아래 결과와 같이 입력값과 출력값이 거의 유사하게 복원되었음을 확인할 수 있다.



테스트 코드를 웹으로 구현

테스트를 위해서 MNIST 데이타를 입력하는 것 말고, HTML 화면을 이용하여 직접 마우스로 숫자를 그래서 입력할 수 있도록 해보자


코드 구조 자체는 위의 예제와 같기 때문에 별도로 설명하지 않는다.



위의 그림과 같이 HTML 입력 박스에 마우스로 그림을 그리면 아래 그림과 같이 입력값과 함께 복원된 이미지를 보여 준다.

웹을 이용하여 숫자와 알파벳을 입력해서 입력과 결과값을 구분해본 결과, 영문이던 숫자이던 입출력 차이가 영문이나 숫자가 크게 차이가 나지 않아서, 변별력이 크지 않았다.



트레이닝 스탭이 이 50,000 스텝 정도면 loss값이 500 근처로 수렴을 하였는데, 1,000,000 스텝을 학습 시켜서 MNIST 데이타에 대한 기억 효과를 극대화 하려고 했지만 큰 효과가 없었다.

여러가지 원인이 있겠지만, HTML에서 손으로 이미지를 인식 받는 만큼, 글자의 위치나 크기에 따라서 loss 값이 크게 차이가 나는 결과를 보였다.  이 부분은 컨볼루셔널 필터 (Convolution Filter)를 사용하면 해결이 가능할것 같으나 적용은 하지 않았다.




또한 학습에 사용된 데이타는 0~255 의 흑백 값이지만, 위의 예제에서 웹을 통해 입력받은 값은 흑/백 (0 or 255)인 값이기 때문에 눈으로 보기에는 비슷하지만 실제로는 많이 다른 값이다.


또는 학습 데이타가 모자르거나 또는 네트워크 사이즈가 작았을 것으로 생각하는데, 그 부분은 별도로 테스트 하지 않았다.

신용 카드 데이타의 경우 손으로 그리는 그림이 아니기 때문에, 이런 문제는 없을 것으로 생각 하는데, 만약 문제가 된다면 네트워크 사이즈를 조정해보는 방안으로 진행할 예정이다.


다음 글에서는 신용 카드 데이타를 가지고 오토 인코더를 이용하여 비정상 거래를 검출하기 위해서 학습을 우하여 데이타 전처리를 하는 부분에 대해서 알아보도록 하겠다.


저작자 표시 비영리
신고

오토인코더를 이용한 비정상 거래 검출 모델의 구현 #1

신용카드 거래 데이타 분석


조대협 (http://bcho.tistory.com)


이미지 인식 모델은 만들어봤고, 아무래도 실제로 짜봐야 하는지라 좋은 시나리오를 고민하고 있는데, 추천 시스템도 좋지만, 이상 거래 감지에 대해 접할 기회가 있어서 이상 거래 감지 (Fraud Detection System)  시스템을 만들어 보기로 하였다


데이타셋

샘플 데이타를 구해야 하는데, 마침 kaggle.com 에 크레딧 카드 이상거래 감지용 데이타가 있었다.

https://www.kaggle.com/dalpozz/creditcardfraud 에서 데이타를 다운 받을 수 있다.




CSV 형태로 되어 있으며, 2013년 유럽 카드사의 실 데이타 이다. 2일간의 데이타 이고, 총 284,807건의 트렌젝션 로그중에, 492건이 비정상 데이타이고, 데이타 분포는 비정상 데이타가 0.172%로 심하게 불균형적이다.


전체 31개의 컬럼중, 첫번째 컬럼은 시간,30번째 컬럼은 비정상 거래 유무 (1이면 비정상, 0이면 정상) 그리고 마지막 31번째 컬럼은 결재 금액을 나타낸다 2~29번째 컬럼이 특징 데이타 인데, V1~V28로 표현되고 데이타 컬럼명은 보안을 이유로 모두 삭제 되었다.


데이타 분석

어떤 컬럼들을 피쳐로 정할것인가를 결정하기 위해서 데이타 분석을 시작한다.

데이타 분석 방법은  https://www.kaggle.com/currie32/predicting-fraud-with-tensorflow 를 참고하였다.


시간대별 트렌젝션양을 분석해보면 별다른 상관 관계를 찾을 수 없다.


트렌젝션 금액별로 비교를 한 그림이다.


위의 비정상 데이타를 보면, 작은 금액에서 비정상 거래가 많이 일어난것을 볼 수 있지만, 정상 거래군과 비교를 해서 다른 특징을 찾아낼 수 없다.


다음은 트랜젝션 금액을 기준으로 V1~V28 피쳐를 비교 분석해봤다.


붉은 점은 비정상, 파란점이 정상 거래이고, 가로축이 금액, 새로축이 V1 값이다. 이런 방법으로 V1~V8에 대한 그래프를 그려봤으나, 비정상 거래가 항상 정상거래의 부분집합형으로 별다른 특이점을 찾아낼 수 없었다.


다음으로 V1~V28 각 컬럼간의 값 분포를 히스토 그램으로 표현한 결과이다.

아래는 V2 피쳐의 값을 히스토그램으로 표현한 결과로 파란색이 정상, 붉은 색이 비정상 거래인데, 히스토그램이 차이가 나는 것을 확인할 수 있다.


V4 피쳐 역시 아래 그림과 같이 차이가 있는 것을 볼 수 있다.


V22 피쳐의 경우에는 정상과 비정상 거래의 패턴이 거의 유사하여 변별력이 없는것을 볼 수 있다.



이런식으로, V1~V28중에 비정상과 정상거래에 차이를 보이는 피쳐들만 선정한다.

위의 그래프들은 생성하는 코드는 https://github.com/bwcho75/tensorflowML/blob/master/autoencoder/Credit%20card%20fraud%20detection%20(Data%20Analytics).ipynb 에 있다.


모델 선택

정상거래와 비정상 거래가 라벨링이 되어 있기 때문에, 로지스틱 회귀나 일반적인 뉴럴네트워크를 사용해도 되지만, 비정상 거래 검출 로직의 경우 비정상 거래를 분별해서 라벨링한 데이타를 구하기가 매우 어렵다.

그래서 라벨된 데이타를 전제로 하는 지도학습보다 비지도학습 알고리즘을 선택하기로 한다.


비지도 학습 모델 중에서 오토 인코더라는 모델을 사용할 예정이다.

오토인코더 (AutoEncoder)

오토 인코더는 딥네트워크 기반의 비지도 학습 모델로, 뉴럴네트워크 두개를 뒤집어서 붙여놓은 형태이다.





<그림 출처 : https://deeplearning4j.org/deepautoencoder >

앞에 있는 뉴럴네트워크는 인코더, 뒤에 붙은 네트워크는 디코더가 된다.

인코더를 통해서 입력 데이타에 대한 특징을 추출해내고, 이 결과를 가지고 뉴럴 네트워크를 역으로 붙여서 원본 데이타를 생성해낸다.




이 과정에서 입력과 출력값이 최대한 같아지도록 튜닝함으로써, Feature를 잘 추출할 수 있게 하는것이 오토 인코더의 원리이다.


비정상 거래 검출에 있어서 이를 활용하는 방법은 학습이 되지 않은 데이타의 경우 디코더에 의해 복원이 제대로 되지 않고 원본 데이타와 비교했을때 차이값이 크기 때문에, 정상 거래로 학습된 모델은 비정상 거래가 들어왔을때 결과값이 입력값보다 많이 다를것이라는 것을 가정한다.


그러면 입력값 대비 출력값이 얼마나 다르면 비정상 거래로 판단할것인가에 대한 임계치 설정이 필요한데, 이는 실제 데이타를 통한 설정이나 또는 통계상의 데이타에 의존할 수 밖에 없다. 예를 들어 전체 신용카드 거래의 0.1%가 비정상 거래라는 것을 가정하면, 입력 값들 중에서 출력값과 차이가 큰 순서대로 데이타를 봤을때 상위 0.1%만을 비정상 거래로 판단한다.


또는 비지도 학습이기 때문에, 나온 데이타로 정상/비정상을 판단하기 보다는 비정상 거래일 가능성을 염두해놓고, 그 거래들을 비정상 거래일 것이라고 예측하고 이 비정상 거래 후보에 대해서 실제 확인이나 다른 지표에 대한 심층 분석을 통해서 비정상 거래를 판별한다.


이러한 과정을 거쳐서 비정상 거래가 판별이 되면, 비정상 거래에 대한 데이타를 라벨링하고 이를 통해서 다음 모델 학습시 임계치 값을 설정하거나 다른 지도 학습 알고리즘으로 변경하는 방법등을 고민해볼 수 있다.


다음글에서는 실제로 오토인코더 모델을 텐서플로우를 이용해서 구현해보겠다.


저작자 표시 비영리
신고

텐서플로우 하이레벨 API Estimator를 이용한 모델 정의 방법


조대협 (http://bcho.tistory.com)


텐서플로우의 하이레벨 API를 이용하기 위해서는 Estimator 를 사용하는데, Estimator 는 Predefined model 도 있지만, 직접 모델을 구현할 수 있다. 하이레벨 API와 Estimator에 대한 설명은 http://bcho.tistory.com/1195 글을 참고하기 바란다.


이 문서는 Custom Estimator를 이용하여 Estimator를 구현하는 방법에 대해서 설명하고 있으며, 대부분 https://www.tensorflow.org/extend/estimators 의 내용을 참고하여 작성하였다.

Custom Estimator

Estimator의 스켈레톤 코드는 다음과 같다. 모델을 정의하는 함수는 학습을 할 feature와, label을 입력 받고, 모델의 모드 (학습, 테스트, 예측) 모드를 인자로 받아서 모드에 따라서 모델을 다르게 정의할 수 있다. 예를 들어 학습의 경우 드롭 아웃을 사용하지만 테스트 모드에서는 드롭 아웃을 사용하지 않는다.

def model_fn(features, labels, mode, params):
  # Logic to do the following:
  # 1. Configure the model via TensorFlow operations
  # 2. Define the loss function for training/evaluation
  # 3. Define the training operation/optimizer
  # 4. Generate predictions
  # 5. Return predictions/loss/train_op/eval_metric_ops in EstimatorSpec object
  return EstimatorSpec(mode, predictions, loss, train_op, eval_metric_ops)

입력 인자에 대한 설명

그러면 각 인자를 구체적으로 살펴보자

  • features : input_fn을 통해서 입력되는 feature로 dict 형태가 된다.

  • labels : input_fn을 통해서 입력되는 label 값으로 텐서 형태이고, predict (예측) 모드 일 경우에는 비어 있게 된다.

  • mode : 모드는 모델의 모드로, tf.estimator.ModeKeys 중 하나를 사용하게 된다.

    • tf.estimator.ModeKeys.TRAIN : 학습 모드로 Estimator의 train()을 호출하였을 경우 사용되는 모드이다.

    • tf.estimator.ModeKeys.EVAL : 테스트 모드로, evaluate() 함수를 호출하였을 경우 사용되는 모드이다.

    • tf.estimator.ModeKeys.PREDICT : 예측모드로,  predict() 함수를 호출하였을 경우에 사용되는 모드이다.  

  • param : 추가적으로 입력할 수 있는 패러미터로, dict 포맷을 가지고 있으며, 하이퍼 패러미터등을 이 변수를 통해서 넘겨 받는다.

Estimator 에서 하는 일

Estimator 를 구현할때, Estimator 내의 내용은 모델을 설정하고, 모델의 그래프를 그린 다음에, 모델에 대한 loss 함수를 정의하고, Optimizer를 정의하여 loss 값의 최소값을 찾는다. 그리고 prediction 값을 계산한다.


Estimator의 리턴값

Estimator에서 리턴하는 값은 tf.estimator.EstimatorSpec 객체를 리턴하는데, 이 객체는 다음과 같은 값을 갖는다.

  • mode : Estimator가 수행한 모드. 보통 입력값으로 받은 모드 값이 그대로 리턴된다.

  • prediction (PREDICT 모드에서만 사용됨) : PREDICT 모드에서 예측을 수행하였을 경우, 예측된 값을 dict 형태로 리턴한다.

  • loss (EVAL 또는, TRAIN 모드에서 사용됨) : 학습과 테스트중에 loss 값을 리턴한다.

  • train_op (트레이닝 모드에서만 필요함) : 한 스텝의 학습을 수행하기 위해서 호출하는 함수를 리턴한다. 보통 옵티마이져의  minimize()와 같은 함수가 사용된다.
           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)

  • eval_metrics_ops (optional) : EVAL (테스트) 모드에서 테스트를 위해서 사용된 인자들을 dict 형태로 리턴한다. tf.metrics에는 미리 정의된 일반적인 메트릭들이 정의되어 있는데, 예를 들어 accuracy 등이 이에 해당한다. 아래는 tf.metrics.accuracy를 이용하여 예측값 (predictions)과 라벨(labels)의 값을 계산하여, 메트릭으로 리턴하는 방법이다.

    eval_metric_ops = {
    "accuracy": tf.metrics.accuracy(labels, predictions) }

    만약 rmse를 evaluation metric으로 사용하고자 하면 다음과 같이 정의한다.
    eval_metric_ops = {
       "rmse": tf.metrics.root_mean_squared_error(
           tf.cast(labels, tf.float64), predictions)
    }

    만약에 별도의 메트릭을 정의하지 않으면, 디폴트로 loss 값만 EVAL 단계에서 계산되게 된다.

데이타 입력 처리

모델로의 데이타 입력은 Esitmator의 모델 함수로 입력되는 features 변수를 통해서 입력 된다.

features는 컬럼명으로된 키와, 컬럼 값으로 이루어진 dict 형태의 데이타 형으로, 뉴럴 네트워크 모델에 데이타를 입력하기 위해서는 이중에서 학습에 사용할 컬럼만을 추출하여, 입력 레이어에 넣어 줘야 한다.

이 features 에서 특정 컬럼만을 지정하여 추출한 후에, 그 컬럼의 값을 넣어주는 것은 tf.feature_column.input_layer 함수를 사용하면 된다.


예제를 보자

input_layer = tf.feature_column.input_layer(
 features=features, feature_columns=[age, height, weight])


위의 예제는 features 에서 age,height,weight 컬럼을 추출하여 input layer로 넣는 코드이다.

네트워크 정의

데이타를 읽었으면 이제 뉴럴네트워크를 구성해야 한다. 네트워크의 레이어는 tf.layers 로 간단하게 구현할 수 있다. tf.layer에는 풀링,드롭아웃,일반적인 뉴럴네트워크의 히든 레이어, 컨볼루셔널 네트워크들이 함수로 구현되어 있기 때문에 각 레이어를 하나의 함수로 간단하게 정의가 가능하다.


아래는 히든레이어를 구현하는 tf.layers.dense 함수이다.


tf.layers.dense( inputs, units, activation)


  • inputs는 앞의 레이어를 정의하고

  • units는 이 레이어에 크기를 정의하고

  • 마지막으로 activation은 sigmoid나,ReLu와 같은 Activation 함수를 정의한다.


다음 예제는 5개의 히든 레이어를 가지는 오토 인코더 네트워크를 정의한 예이다.

 input_layer = features['inputs'] # 784 pixels
   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)


5개의 히든 레이어는 각각 256,128,16,128,256 개의 노드를 가지고 있고, 각각 ReLu를 Activation 함수로 사용하였다.

그리고 마지막 output layer는 784개의 노드를 가지고 sigmoid 함수를 activation 함수로 사용하였다.

Loss 함수 정의

다음 모델에 대한 비용함수(loss/cost function)을 정의한다. 이 글을 읽을 수준이면 비용함수에 대해서 별도로 설명하지 않아도 되리라고 보는데, 비용함수는 예측값과 원래 라벨에 대한 차이의 합을 나타내는 것이 비용함수이다.


 # Connect the output layer to second hidden layer (no activation fn)

 output_layer = tf.layers.dense(second_hidden_layer, 1)
 # Reshape output layer to 1-dim Tensor to return predictions
 predictions = tf.reshape(output_layer, [-1])
 predictions_dict = {"ages": predictions}

 # Calculate loss using mean squared erro
 loss = tf.losses.mean_squared_error(labels, predictions)

코드를 보면, 최종 예측된 값은 predictions에 저장되고, 학습 데이타로 부터 받은 라벨 값은 labels에 저장된다. 이 차이를 계산할때, MSE (mean square error)를 사용하였다.

Training Op 정의

비용 함수가 적용되었으면, 이 비용함수의 값을 최적화 하는 것이 학습이기 때문에, 옵티마이저를 정의하고, 옵티마이저를 이용하여 비용함수의 최적화가 되도록 한다.

아래 코드는  Optimizer를 GradientDescentOptimizer로 정의하고, 이 옵티마이저를 이용하여 이용하여 loss 값을 최소화 하도록 하였다.

optimizer = tf.train.GradientDescentOptimizer(
   learning_rate=params["learning_rate"])

train_op = optimizer.minimize(
   loss=loss, global_step=tf.train.get_global_step())

전체 코드

그러면 위의 내용을 모두 합쳐서 model_fn으로 모아서 해보자.

def model_fn(features, labels, mode, params):
 """Model function for Estimator."""
 # Connect the first hidden layer to input layer
 # (features["x"]) with relu activation
 first_hidden_layer = tf.layers.dense(features["x"], 10, activation=tf.nn.relu)

 # Connect the second hidden layer to first hidden layer with relu
 second_hidden_layer = tf.layers.dense(
     first_hidden_layer, 10, activation=tf.nn.relu)

 # Connect the output layer to second hidden layer (no activation fn)
 output_layer = tf.layers.dense(second_hidden_layer, 1)


 # Reshape output layer to 1-dim Tensor to return predictions
 predictions = tf.reshape(output_layer, [-1])

 # Provide an estimator spec for `ModeKeys.PREDICT`.
 if mode == tf.estimator.ModeKeys.PREDICT:
   return tf.estimator.EstimatorSpec(
       mode=mode,
       predictions={"ages": predictions})

 # Calculate loss using mean squared error
 loss = tf.losses.mean_squared_error(labels, predictions)

 # Calculate root mean squared error as additional eval metric
 eval_metric_ops = {
     "rmse": tf.metrics.root_mean_squared_error(
         tf.cast(labels, tf.float64), predictions)
 }

 optimizer = tf.train.GradientDescentOptimizer(
  learning_rate=params["learning_rate"])

 train_op = optimizer.minimize(
     loss=loss, global_step=tf.train.get_global_step())

 # Provide an estimator spec for `ModeKeys.EVAL` and `ModeKeys.TRAIN` modes.

 return tf.estimator.EstimatorSpec(
     mode=mode,
     loss=loss,
     train_op=train_op,
     eval_metric_ops=eval_metric_ops)

데이타 입력

 first_hidden_layer = tf.layers.dense(features["x"], 10, activation=tf.nn.relu)

네트워크 정의

 # Connect the second hidden layer to first hidden layer with relu
 second_hidden_layer = tf.layers.dense(
     first_hidden_layer, 10, activation=tf.nn.relu)

 # Connect the output layer to second hidden layer (no activation fn)
 output_layer = tf.layers.dense(second_hidden_layer, 1)

first_hidden_layer의 입력값을 가지고 네트워크를 구성한다. 두번째 레이어는 first_hidden_layer를 입력값으로 하여, 10개의 노드를 가지고, ReLu를 activation 레이어로 가지도록 하였다.  

마지막 계층은 두번째 계층에서 나온 결과를 하나의 노드를 이용하여 합쳐서 activation 함수 없이 결과를 냈다.

 # Reshape output layer to 1-dim Tensor to return predictions
 predictions = tf.reshape(output_layer, [-1])

 # Provide an estimator spec for `ModeKeys.PREDICT`.
 if mode == tf.estimator.ModeKeys.PREDICT:
   return tf.estimator.EstimatorSpec(
       mode=mode,
       predictions={"ages": predictions})

예측 모드에서는 prediction 값을 리턴해야 하기 때문에, 먼저 예측값을 output_layer에서 나온 값으로, 행렬 차원을 변경하여 저장하고, 만약에 예측 모드 tf.estimator.ModeKeys.PREDICT일 경우 EstimatorSpec에 predction 값을 넣어서 리턴한다. 이때 dict 형태로 prediction 결과 이름을 age로 값을 predictions 값으로 채워서 리턴한다.

Loss 함수 정의

다음 비용 함수를 정의하고, 테스트 단계(EVAL)에서 사용할 evaluation metrics에 rmse를 테스트 기준으로 메트릭으로 정의한다.

 # Calculate loss using mean squared error
 loss = tf.losses.mean_squared_error(labels, predictions)

 # Calculate root mean squared error as additional eval metric
 eval_metric_ops = {
     "rmse": tf.metrics.root_mean_squared_error(
         tf.cast(labels, tf.float64), predictions)
 }

Training OP 정의

비용 함수를 정했으면, 비용 함수를 최적화 하기 위한 옵티마이져를 정의한다. 아래와 같이 GradientDescentOptimzer를 이용하여 loss 함수를 최적화 하도록 하였다.

 optimizer = tf.train.GradientDescentOptimizer(
  learning_rate=params["learning_rate"])

 train_op = optimizer.minimize(
     loss=loss, global_step=tf.train.get_global_step())

 # Provide an estimator spec for `ModeKeys.EVAL` and `ModeKeys.TRAIN` modes.

마지막으로, PREDICTION이 아니고, TRAIN,EVAL인 경우에는 EstimatorSpec을 다음과 같이 리턴한다.

Loss 함수와, Training Op를 정의하고 평가용 매트릭스를 정의하여 리턴한다.

 return tf.estimator.EstimatorSpec(
     mode=mode,
     loss=loss,
     train_op=train_op,
     eval_metric_ops=eval_metric_ops)

실행

그러면 완성된 Estimator를 사용해보자

train_input_fn = tf.estimator.inputs.numpy_input_fn(
   x={"x": np.array(training_set.data)},
   y=np.array(training_set.target),
   num_epochs=None,
   shuffle=True)

# Train

nn.train(input_fn=train_input_fn, steps=5000)

# Score accuracy

test_input_fn = tf.estimator.inputs.numpy_input_fn(
   x={"x": np.array(test_set.data)},
   y=np.array(test_set.target),
   num_epochs=1,
   shuffle=False)

ev = nn.evaluate(input_fn=test_input_fn)
print("Loss: %s" % ev["loss"])
print("Root Mean Squared Error: %s" % ev["rmse"])

각 코드를 보면

train_input_fn = tf.estimator.inputs.numpy_input_fn(
   x={"x": np.array(training_set.data)},
   y=np.array(training_set.target),
   num_epochs=None,
   shuffle=True)

를 이용하여 numpy 의 데이타로 input_fn 함수를 만들었다. training_set.data는 학습 데이타, training_set.target을 학습용 라벨로 설정하고, epoch는 무제한, 그리고 데이타는 셔플 하도록 하였다.

nn.train(input_fn=train_input_fn, steps=5000)

앞서 정의된 모델에 train_input_fn을 넣어서 총 5000 번 학습을 하도록 하였다.

학습이 끝난 모델을 테스트 해야 하는데, 같은 방법으로 test_input_fn을 정의하고

ev = nn.evaluate(input_fn=test_input_fn)

evaluate를 이용하여, 학습된 모델을 평가한다.

평가된 결과를 보기 위해서 loss 값과 rmse 값을 ev[‘loss’], ev[‘rmse’]로 출력하였다.

지금까지 Estimator를 만드는 방법에 대해서 알아보았다. 다음 글에서는 Auto Encoder 네트워크를 Estimator로 구현해보도록 하겠다.





저작자 표시 비영리
신고

텐서플로우 하이레벨 API

빅데이타/머신러닝 | 2017.09.06 14:57 | Posted by 조대협

텐서플로우 하이레벨 API에 대한 이해


머신러닝을 공부하고 구현하다 보니, 모델 개발은 새로운 모델이나 알고리즘을 개발하는 일 보다는, 기존의 알고리즘을 습득해서 내 데이타 모델에 맞도록 포팅하고, 학습 시키는 것이 주된 일이 되고, 오히려, 모델 보다는 데이타에 대한 이해와 전처리에 많은 시간이 소요되었다.


특히 여러번 실험을 하면서 패러미터를 조정하고 피쳐등을 조정하기 위해서는 많은 실험을 할 수 있어야 하는데, 이러기 위해서는 실험(학습)시간이 짧아야 한다. 이를 위해서는 모델 개발 보다 분산 러닝을 하기 위한 코드 변경 작업등이 많이 소요된다.


결론을 요약하자면, 실제로 알고리즘을 개발하는 데이타 과학자가 아니라, 머신러닝을 활용만 하는 프랙티셔너 입장이라면, 모델을 개발하는 것 보다는 있는 모델을 선택해서 쉽게 사용할 수 있는 방법을 찾으면 된다.

하이레벨 API

이런 관점에서 시작한 것이 머신러닝 하이레벨 API 이다. 복잡한 수식이 없이 마치 함수처럼 모델을 선택해서 사용하는 방법인데, 쉽게 이야기 하면, Hash table 알고리즘을 100% 이해하지 않더라도, hashtable 라이브러리를 가져다가 사용하면 되는것과 같은 원리이다.


머신러닝에서도 이미 이러한 하이레벨 API가 많이 제공되고 있는데, 파이썬 싸이킥 런(http://scikit-learn.org/) 이나 SparkML 등이 해당한다.

텐서플로우에도 같은 방식으로 하이레벨 API를 제공하는데, 텐서플로우 공식 SDK와 써드파티 오픈소스 라이브러리들이 있다.

그중에서 tf.contrib가 공식 텐서플로우의 하이레벨 API이며, 딥러닝 모델을 간단하게 만들 수 있는 Keras역시 얼마전에 텐서플로우 공식 하이레벨 API로 로 편입되었다.




텐서플로우에서는 Linear regression, SVM등 많이 쓰이는 일반적인 머신러닝 모델에서 부터 Deep Wide Network와 같은 딥 러닝 모델들을 Estimator 라는 형태로 제공하고 있다.

하이레벨 API를 쓰면 장점

그러면 이러한 하이레벨 API를 쓰면 장점이 무엇일까?

모델 개발이 쉽다

모델 개발이 매우 쉽다. 복잡한 모델을 손쉽게 개발할 수 있을뿐더러, 일부 모델들은 Out of box 형태로, 바로 라이브러리 식으로 불러서 사용만 하면 되기 때문에 모델 개발 시간이 줄어들고, 모델에 대한 기본적인 이해만 있더라도 쉽게 개발이 가능하다.

스케일링이 용이하다

큰 모델을 많은 데이타로 학습하기 위해서는 여러 머신에서 학습을 하는 분산 학습이 필요한데, 로우레벨 API를 이용할 경우 분산 학습을 개발하기가 쉽지 않다.  하이레벨 API를 이용할 경우 코드 변경 없이 싱글 머신에서 부터 GPU 그리고 분산 학습까지 손쉽게 지원이 되기 때문에, 실험 (학습/테스트) 시간을 많이 절약할 수 있다.


배포가 용이하다

모델을 학습 시킨 후 예측을 위해서 배포를 할 경우, 보통 모델을 *.pb 파일 형태로 Export 해야 하는데, 이 경우 학습에 사용된 그래프 말고 예측을 위한 그래프를 새로 그려야 하는 등 추가적인 작업이 필요하고 쉽지 않은데 반해 하이레벨 API의 경우, 코드 몇줄만으로도 손쉽게 예측 서비스를 위한 그래프를 Export할 수 있다.


텐서플로우 하이레벨 API

tf.layers

텐서플로우는 특히 딥러닝 (뉴럴네트워크)에 강점을 가지고 있는데, 딥네트워크의 각 계층을 설계 하기 위해서는 컨볼루셔널 필터, 풀링, 스트라이드,드롭 아웃 등 다양한 기법을 사용하게 된다. 이러한 것들을 복잡하게 구현하지 않고, 딥 네트워크를 손쉽게 만들 수 있게 각 레이어에 대한 구현을 함수식으로 제공한다.


다음 그림은 tf.layer로 컨볼루셔널 네트워크 (CNN)을 구현한 예제로 컨볼루셔널 레이어와, 맥스풀링, 드롭아웃, ReLu 엑티베이션 함수등을 사용하였다. 각 레이어는 tf.layers 라이브러리 하나씩으로 간단하게 구현되었다.


Estimator

일반적으로 머신러닝 개발은 다음과 같은 구조를 갖는다


개발한 모델에 Input,Labels 데이타를 넣은 후, 학습(Training), 테스트(Evaluation), 예측(Prediction)을 한후, 학습이 완료된 모델을 저장(Export)하여 배포한다.  거의 모든 모델 개발이 위의 구조를 반복하기 때문에, 이러한 구조를 추상화 해놓은 것이 Estimator 이다.


이 추상화를 통해서 Estimator에 데이타를 넣게 되면, Estimator는 Training, Evaluation, Prediction, Export를 위한 인터페이스를 제공한다. 텐서플로우 그래프 구축이나 세션 관리등은 모두 Estimator 안으로 추상화 한다.


Estimator는 직접 개발자가 모델을 직접 구현하여 Estimator를 개발할 수 도 있고 (Custom Estimator) 또는 이미 텐서플로우 tf.contrib.learn에 에 미리 모델들이 구현되어 있다. 딥네트워크 기반의 Classifier나 Regressor (DNNClassifieir, DNNRegressor), SVM, RNN, KMeans 등이 있기 때문에 간단하게 불러다 사용하기만 하면 된다.

Estimator 예제

Estimator 예제로 간단한 LinearRegression Estimator를 사용하는 예제를 보자

학습용 데이타

먼저 학습용 데이타와 라벨을 생성하였다.

import numpy as np

num_points = 300

vectors_set = []

for i in xrange(num_points):

 x = np.random.normal(5,5)+15

 y =  x*2+ (np.random.normal(0,3))*2

 vectors_set.append([x,y])

 

x_data = [v[0] for v in vectors_set ]

y_data = [v[1] for v in vectors_set ]


import matplotlib.pyplot as plt

plt.plot(x_data,y_data,'ro')

plt.ylim([0,100])

plt.xlim([5,35])

plt.xlabel('x')

plt.ylabel('y')

plt.legend()

plt.show()


데이타 분포는 아래와 같다.


모델 코드

데이타 리더

Estimator 를 사용하려면 데이타를 읽어서 Estimator 에 넣어주는 입력 함수를 구현해줘야 한다. 아래는  numpy 배열에서 데이타를 읽어서 리턴해주는 입력 함수이다.


input_fn_train = tf.estimator.inputs.numpy_input_fn(

   x = {"x":np.array(x_data[:200],dtype=np.float32)},

   y = np.array(y_data[:200],dtype=np.float32),

   num_epochs=100000,

   batch_size=50,

   shuffle=True

)


x_data 배열에서 0~200까지의 데이타를 학습용 데이타로 사용하였고, y_data 0~200을 라벨로 사용하였다. 한번에 50 개씩 리턴하도록 배치를 설정하였고, 100K epoch를 지원하고 데이타를 랜덤하게 리턴하도록 셔플 처리를 하였다.


input_fn_eval = tf.estimator.inputs.numpy_input_fn(

   x = {"x":np.array(x_data[200:300],dtype=np.float32)},

   y = np.array(y_data[200:300],dtype=np.float32),

   num_epochs=100000,

   batch_size=50,

   shuffle=True

)


input_fn_predict = tf.estimator.inputs.numpy_input_fn(

   x = {"x":np.array([15,20,25,30],dtype=np.float32)},

   num_epochs=1,

   shuffle=False

)


같은 방법으로 테스트용 데이타와 예측에 사용할 데이타 입력 함수를 같이 정의하였다.

모델 정의

column_x = tf.feature_column.numeric_column("x",dtype=tf.float32)

columns = [column_x]

읽어온 데이타에서, 어떤 컬럼을 학습에 사용할지, 그리고 그 컬럼의 데이타 타입 (연속형인지 분류형인지)를 정한다.  tf.feature_column.numeric_column("x",dtype=tf.float32) 는 컬럼 명 x를 학습 데이타로 사용하고 x는 연속형 변수로 지정하였다.

다음 columns에 피쳐로 사용할 컬럼 목록을 정한다.


LinearRegression Estimator를 정의하고, 여기에, column을 정해준다.  Optimizer나 Learning Rate등은 지정이 가능하다.

estimator = tf.contrib.learn.LinearRegressor(feature_columns=columns,optimizer="Adam")

학습과 예측

학습은 .fit 이라는 메서드를 사용하면 되고, 입력 함수와 학습 스텝을 정해주면 된다.

estimator.fit(input_fn = input_fn_train,steps=5000)

estimator.evaluate(input_fn = input_fn_eval,steps=10)

result = list(estimator.predict(input_fn = input_fn_predict))


마지막으로 예측은 predict 를 이용하면 된다.

x=15,20,25,30에 대해서 예측 결과는 다음과 같다.

[31.193062, 41.855644, 52.51823, 63.180817]



그래프와 비교해보면 유사 값이 나오는 것을 확인할 수 있다.



전체 코드 
https://github.com/bwcho75/tensorflowML/blob/master/HighLevel%20API%201.%20Linear%20Regression%20Estimator.ipynb






저작자 표시 비영리
신고