Toil의 사전적인 뜻은 “노역"이라는 뜻을 가지고 있는데, 비속어를 사용해서 표현하자면 운영 업무에서의 “노가다" 정도로 이해하면 된다. Toil 에 대한 정의를 잘 이해해야 하는데, Toil은 일종의 반복적인 쓸모없는 작업 정도로 정의할 수 있다.
경비 처리나, 회의, 주간 업무 보고서 작성과 같은 어드민 작업은 Toil에 해당하지 않는다. Toil은 운영상에 발생하는 반복적인 메뉴얼 작업인데, 다음과 같은 몇가지 특징으로 정의할 수 있다.
메뉴얼 작업이고 반복적이어야함
Toil의 가장 큰 특징은 사람이 직접 수행하는 메뉴얼 작업이라는 것이다. 그리고 어쩌다 한번이 아니라 지속적으로 발생하는 반복적인 작업이다.
자동화 가능함
자동화가 가능하다는 것은 자동화가 가능한데, 시간이 없어서(?) 자동화를 못하고 사람이 작업을 하고 있다는 것이다. 즉 사람이 하지 않아도 되는 일을 시간을 낭비하면서 하고 있다는 것인데, 서버 배포를 테라폼등으로 자동화할 수 있는데, 자동화 하지 않고, 수동으로 작업하고 있는 경우 Toil에 해당한다.
밸류를 제공하지 않는 작업
Toil작업은 작업을 하고나도, 서비스나 비지니스가 개선되지 않는 작업이다. 작업 전/후의 상태가 같은 작업인데, 장애 처리와 같은 것이 대표적인 예에 속한다. 장애 처리는 시스템을 이전 상태로 돌리는 것 뿐일뿐 새로운 밸류를 제공하지 않는다.
서비스 성장에 따라서 선형적으로 증가하는 작업
Toil은 보통 서비스가 성장하고 시스템이 커지면 선형적으로 증가한다. 애플리케이션 배포나, 시스템 설정, 장애 처리도 시스템 인스턴스의 수가 늘어날 수 록 증가하게 된다.
정리해보자면, Toil이란 성장에 도움이 되지 않으면서 시간을 잡아먹는 메뉴얼한 작업이고, 서비스의 규모가 커지면 커질 수 록 늘어나는 자동화가 가능한 작업이다. 일종의 기술 부채의 개념과도 연결시켜 생각할 수 있다.
Toil을 왜? 그리고 어떻게 관리해야 하는가?
그러면 Toil을 어떻게 관리해야 할까? Toil은 의미없는 작업이 많지만, Toil을 무조건 없애는 것은 적절하지 않다. 예를 들어서 일년에 한두번 발생하는 작업을 자동화하려고 노력한다면, 오히려 자동화에 들어가는 노력이 더 많아서 투자 대비 효과가 떨어진다. 반대로 Toil이 많으면 의미있는 기능 개발작업이나 자동화 작업을할 수 없기 때문에 일의 가치가 떨어진다.
그래서 구글의 SRE 프랙티스 에서는 Toil을 30~50%로 유지하도록 권장하고 있다. Toil을 줄이는 방법 중에 대표적인 방법은 자동화를 하는 방법이다. 자동화를 하면 Toil을 줄일 수 있는데, 그러면 남은 시간은 어떻게 활용하는가? 이 시간을 서비스 개발에 투자하거나 또는 다른 서비스를 운영하는데 사용한다.
위의 그림이 가장 잘 설명된 그림인데, 초반에는 Service A에 대해서 대부분의 Toil이 발생하는데, 자동화를 하게 되면, Toil 이 줄어든다.그러면 줄어든 Toil 시간을 다른 서비스를 운영하는데 사용을 하고 결과적으로는 여러 서비스를 동시에 적은 시간으로 운영할 수 있도록 된다.
이 개념을 확장해보면 Devops의 목표와도 부합이 되는데, Devops는 개발과 운영을 합쳐서 진행하는 모델인데, 개발이 직접 운영을 하기 위해서는 플랫폼이 필요하다. 즉 개발자가 직접 하드웨어 설치, 네트워크 구성등 로우 레벨한 작업을 하는것이 아니라, 자동화된 운영 플랫폼이 있으면, 개발팀이 직접 시스템을 배포 운영할 수 있게 된다.
<그림, Devops에서 개발자와 Devops 엔지니어의 역할>
그래서 개발팀이 이러한 플랫폼을 이용해서 Devops를 한다면, Devops엔지니어는 개발팀이 사용할 운영플랫폼을 개발하는데, 운영플랫폼이란 자동화된 플랫폼을 이야기 한다.
Toil을 측정해가면서 각 서비스별로 자동화 정도를 측정하고, 자동화가 될 수 록, 그 서비스에서 빠져가면서 새로운 서비스로 옮겨가는 모델이라고 볼 수 있다.
Toil를 어떻게 측정할것인가?
이제 Toil이 무엇인지, Toil을 줄여서 어떻게 활용하는지에 대해서 알아보았다.
그러면, 이 Toil을 어떻게 측정하는가?
Toil의 종류를 보면, 크게 배포나 장애처리 등으로 볼 수 있는데, 장애처리의 경우, 장애 발생시 장애 티켓을 버그 시스템에 등록한후에, 처리가 완료될때까지의 시간을 측정하면, 장애 처리에 대한 Toil을 측정할 수 있다.
메뉴얼 배포와 같은 경우에는 특별한 시스템 (Task management)을 사용하지 않는 이상 정확하게 측정하기가 어려운데 그래서 이런 경우에는 snippet (주간 업무보고)나 또는 주기적인 설문 조사를 통해서 측정하는 방법이 있다.
본 예제는 텐서플로우 1.1과 파이썬 2.7 그리고 Jupyter 노트북 환경 및 구글 클라우드를 사용하여 개발되었다.
준비된 데이타
학습에 사용한 데이타는 96x96 사이즈의 얼굴 이미지로, 총 5명의 사진(안젤리나 졸리, 니콜키드만, 제시카 알바, 빅토리아 베컴,설현)을 이용하였으며, 인당 학습 데이타 40장 테스트 데이타 10장으로 총 250장의 얼굴 이미지를 사용하였다.
사전 데이타를 준비할때, 정면 얼굴을 사용하였으며, 얼굴 각도 변화 폭이 최대한 적은 이미지를 사용하였다. (참고 : https://www.slideshare.net/Byungwook/ss-76098082 ) 만약에 이 모델로 학습이 제대로 되지 않는다면 학습에 사용된 데이타가 적절하지 않은것이기 때문에 데이타를 정재해서 학습하기를 권장한다.
CSV 파일을 순차적으로 읽은 후에, train_images와 train_labels라는 배열에 넣은 다음 tf.train.slice_input_producer를 이용하여 큐를 만들어냈다. 이때 중요한 점은 shuffle=True라는 옵션을 준것인데, 만약에 이 옵션을 주지 않으면, 학습 데이타를 큐에서 읽을때 CSV에서 읽은 순차적으로 데이타를 리턴한다. 즉 현재 데이타 포맷은 Jessica Alba가 40개, Jolie 가 40개, Nicole Kidman이 40개 .. 식으로 순서대로 들어가 있기 때문에, Jessica Alba를 40개 리턴한 후 Jolie를 40개 리턴하는 식이 된다. 이럴 경우 Convolutional 네트워크가 Jessica Alba에 치우쳐지기 때문에 제대로 학습이 되지 않는다. Shuffle은 필수이다.
read_data()
input_queue에서 데이타를 읽는 부분인데 특이한 점은 input_queue에서 읽어드린 이미지 파일명의 파일을 읽어서 데이타 객체로 저장해야 한다. 텐서플로우에서는 tf.image.decode_jpeg, tf.image.decode_png 등을 이용하여 이러한 기능을 제공한다.
마지막으로 read_data_batch() 함수 부분이다.get_input_queue에서 읽은 큐를 가지고 read_data함수에 넣어서 이미지 데이타와 라벨을 읽어서 리턴하는 값을 받아서 일정 단위로 (배치) 묶어서 리턴하는 함수이다. 중요한 부분이 데이타를 뻥튀기 하는 부분이 있다.
이 모델에서 학습 데이타가 클래스당 40개 밖에 되지 않기 때문에 학습데이타가 부족하다. 그래서 여기서 사용한 방법은 read_data에서 리턴된 이미지 데이타에 대해서 tf.image.random_xx 함수를 이용하여 좌우를 바꾸거나, brightness,contrast,hue,saturation 함수를 이용하여 매번 색을 바꿔서 리턴하도록 하였다.
그리고 마지막 부분에 label을 tf.one_hot을 이용해서 변환한것을 볼 수 있는데, 입력된 label은 0,1,2,3,4 과 같은 단일 정수이다. 그런데, CNN에서 나오는 결과는 정수가 아니라 클래스가 5개인 (분류하는 사람이 5명이기 때문에) 행렬이다. 즉 Jessica Alba일 가능성이 90%이고, Jolie일 가능성이 10%이면 결과는 [0.9,0.1,0,0,0] 식으로 리턴이 되기 때문에, 입력된 라벨 0은 [1,0,0,0,0], 라벨 1은 [0,1,0,0,0] 라벨 2는 [0,0,1,0,0] 식으로 변환되어야 한다. tf.one_hot 이라는 함수가 이 기능을 수행해준다.
모델 코드
모델은 앞서 설명했듯이 4개의 Convolutional 계층과, 2개의 Fully connected 계층 그리고 Dropout 계층을 사용한다. 각각의 계층별로는 코드가 다르지 않고 인지만 다르니 하나씩 만 설명하도록 한다.
Convolutional 계층
아래 코드는 두번째 Convolutional 계층의 코드이다.
FLAGS.conv2_layer_size 는 이 Convolutional 계층의 뉴런의 수로 32개를 사용한다.
FLAGS.conv2_filter_size 는 필터 사이즈를 지정하는데, 3x3 을 사용한다.
다음 Weight 값 W_conv2 와 Bias 값 b2를 지정한후에, 간단하게 tf.nn.conv2d 함수를 이용하면 2차원의 Convolutional 네트워크를 정의해준다. 다음 결과가 나오면 이 결과를 액티베이션 함수인 relu 함수에 넣은 후에, 마지막으로 max pooling 을 이용하여 결과를 뽑아낸다.
Fully connected 계층은 단순하게 relu(W*x + b) 함수이기 때문에 이 함수를 위와 같이 그대로 적용하였다.
마지막 계층
Fully connected 계층을 거쳐 나온 데이타는 Dropout 계층을 거친후에, 5개의 카테고리에 대한 확률로 결과를 내기 위해서 final_out 계층을 거치게 되는데, 이 과정에서 softmax 함수를 사용해야 하나, 학습 과정에서는 별도로 softmax 함수를 사용하지 않는다. softmax는 나온 결과의 합이 1.0이 되도록 값을 변환해주는 것인데, 학습 과정에서는 5개의 결과 값이 어떤 값이 나오던 가장 큰 값에 해당하는 것이 예측된 값이기 때문에, 그 값과 입력된 라벨을 비교하면 되기 때문이다.
즉 예를 들어 Jessica Alba일 확률이 100%면 실제 예측에서는 [1,0,0,0,0] 식으로 결과가 나와야 되지만, 학습 중는 Jessica Alaba 로 예측이 되었다고만 알면 되기 때문에 결과가 [1292,-0.221,-0.221,-0.221] 식으로 나오더라도 최대값만 찾으면 되기 때문에 별도로 softmax 함수를 적용할 필요가 없다. Softmax 함수는 연산 비용이 큰 함수이기 때문에 일반적으로 학습 단계에서는 적용하지 않는다.
이제 각 CNN의 각 계층을 함수로 정의 하였으면 각 계층을 묶어 보도록 하자. 묶는 법은 간단하다 앞 계층에서 나온 계층을 순서대로 배열하고 앞에서 나온 결과를 뒤의 계층에 넣는 식으로 묶으면 된다.
# build cnn_graph
def build_model(images,keep_prob):
# define CNN network graph
# output shape will be (*,48,48,16)
r_cnn1 = conv1(images) # convolutional layer 1
print ("shape after cnn1 ",r_cnn1.get_shape())
# output shape will be (*,24,24,32)
r_cnn2 = conv2(r_cnn1) # convolutional layer 2
print ("shape after cnn2 :",r_cnn2.get_shape() )
# output shape will be (*,12,12,64)
r_cnn3 = conv3(r_cnn2) # convolutional layer 3
print ("shape after cnn3 :",r_cnn3.get_shape() )
# output shape will be (*,6,6,128)
r_cnn4 = conv4(r_cnn3) # convolutional layer 4
print ("shape after cnn4 :",r_cnn4.get_shape() )
# fully connected layer 1
r_fc1 = fc1(r_cnn4)
print ("shape after fc1 :",r_fc1.get_shape() )
# fully connected layer2
r_fc2 = fc2(r_fc1)
print ("shape after fc2 :",r_fc2.get_shape() )
## drop out
# 참고 http://stackoverflow.com/questions/34597316/why-input-is-scaled-in-tf-nn-dropout-in-tensorflow
# 트레이닝시에는 keep_prob < 1.0 , Test 시에는 1.0으로 한다.
r_dropout = tf.nn.dropout(r_fc2,keep_prob)
print ("shape after dropout :",r_dropout.get_shape() )
# final layer
r_out = final_out(r_dropout)
print ("shape after final layer :",r_out.get_shape() )
return r_out
이 build_model 함수는 image 를 입력 값으로 받아서 어떤 카테고리에 속할지를 리턴하는 컨볼루셔널 네트워크이다. 중간에 Dropout 계층이 추가되어 있는데, tf.nn.dropout함수를 이용하면 간단하게 dropout 계층을 구현할 수 있다. r_fc2는 Dropout 계층 앞의 Fully Connected 계층에서 나온 값이고, 두번째 인자로 남긴 keep_prob는 Dropout 비율이다.
r_dropout = tf.nn.dropout(r_fc2,keep_prob)
print ("shape after dropout :",r_dropout.get_shape() )
모델 학습
데이타를 읽는 부분과 학습용 모델 정의가 끝났으면 실제로 학습을 시켜보자
def main(argv=None):
# define placeholders for image data & label for traning dataset
먼저 학습용 모델에 넣기 위한 image 데이타를 읽어드릴 placeholder를 images로 정의하고, 다음으로 모델에 의해 계산된 결과와 비교하기 위해서 학습데이타에서 읽어드린 label 데이타를 저장하기 위한 placeholder를 labels로 정의한다. 다음 image_batch,label_batch,fle_batch 변수에 배치로 학습용 데이타를 읽어드린다. 그리고 dropout 계층에서 dropout 비율을 지정할 keep_prob를 place holder로 정의한다.
각 변수가 지정되었으면, build_model 함수를 호출하여, images 값과 keep_prob 값을 넘겨서 Convolutional 네트워크에 값을 넣도록 그래프를 정의하고 그 결과 값을 prediction으로 정의한다.
keep_prob = tf.placeholder(tf.float32) # dropout ratio
prediction = build_model(images,keep_prob)
# define loss function
loss = tf.reduce_mean(tf.nn.softmax_cross_entropy_with_logits(logits=prediction,labels=labels))
학습용 데이타가 아니라 검증용 데이타를 VALIDATION_FILE에서 읽어서 데이타를 validate_image_batch,validate_label_batch,validate_file_batch에 저장한다. 다음, 정확도 체크를 위해서 학습에서 예측된 라벨값과, 학습 데이타용 라벨값을 비교하여 같은지 틀린지를 비교하고, 이를 가지고 평균을 내서 정확도 (accuracy)로 사용한다.
학습용 모델과, 테스트용 데이타 등이 준비되었으면 이제 학습을 시작한다.
학습을 시직하기 전에, 학습된 모델을 저장하기 위해서 tf.train.Saver()를 지정한다. 그리고, 그래프로 loss와 accuracy등을 저장하기 위해서 Summary write를 저장한다.
다음 tf.global_variable_initializer()를 수행하여 변수를 초기화 하고, queue에서 데이타를 읽기 위해서 tf.train.Corrdinator를 선언하고 tf.start_queue_runners를 지정하여, queue 러너를 실행한다.
#build the summary tensor based on the tF collection of Summaries
summary = tf.summary.merge_all()
with tf.Session(config=tf.ConfigProto(allow_soft_placement=True, log_device_placement=True)) as sess:
saver = tf.train.Saver() # create saver to store training model into file
안녕하세요~ 올려주신 글 잘 참고하고 있습니다! 제가 이번에 모바일로 얼굴인식을 해보고 싶어서 올려주신 글 참고해서 만들어 보고 있는데요! 모바일에 최적화된 모델을 제작하기위해 optimize_for_inference 과정을 거쳐야 하는 걸로 알고 있습니다! 그 과정에서 입력이름과 아웃풋레이어이름을 인자로 실행해야 한다고 알고 있는데요~
tensorflow/bazel-bin/tensorflow/python/tools/optimize_for_inference \
--input=input_graph.pb \
--output=optimized_graph.pb \
--input_names=? \
--output_names=?
여기서 input_names와 output_names이 각각 image와 final_out이 맞을까요? ㅠㅠ
그리고 해당 코드를 실행했을때ㅠㅠ
tf.train.batch 부분에서
OutOfRangeError (see above for traceback): FIFOQueue '_3_batch/fifo_queue' is closed and has insufficient elements (requested 100, current size 0)
와 같이 에러가 발생하는데요 ㅠㅠ 어떻게 해야할까요? 제 데이터 셋은 classes=5 한 클래스당 사진은 50장입니다 ㅠㅠ
대협님 좋은글 잘보았습니다.
다만,
여쭙고 싶은 중요한 부분이 있는데요.
필터를 초기화 하는부분에서
2x2x3차원(rgb)을 갖는 필터를 30개를 생성해야한다고하면,
대협님 글대로 쓰면[2,2,3,30] 이렇게 생겻는데,
[2,2,3,30] 이면 3x30짜리 2개를쓰는 필터를 2개를 생성하라 라는 의미로 생각이됩니다.
실제로 돌려봐도 텐서가 그렇게나옵니다.
이부분은 중요한 문제인것같은데,, 어떻게 된것인지 알려주세요 ㅠ
감사합니다.
마이크로소프트 azure의 jupyter notebok으로 돌리며 train:validate = 7:3으로 조정, 18명 연예인에 한 연예인당 50장의 사진을 이용했습니다.
그런데 다음과 같이 나옵니다. 어떻게 해야 할까요?
OutOfRangeError: FIFOQueue '_23_batch_4/fifo_queue' is closed and has insufficient elements (requested 70, current size 0)
[[Node: batch_4 = QueueDequeueManyV2[component_types=[DT_UINT8, DT_INT32, DT_STRING], timeout_ms=-1, _device="/job:localhost/replica:0/task:0/cpu:0"](batch_4/fifo_queue, batch_4/n)]]
댓글을 달아 주세요