CNN 을 이용하여 얼굴보고 남녀 구분하기




안녕하세요? 요즘 딥러닝을 이용하여 이것저것 시도해 보고 있습니다.

다들 해보셨을 MNIST 는 해 보셨을 겁니다.


그런데 하고나면, '그래 잘 되는구나' 라는 생각은 드는데,

정말 내가 원하는 문제를 풀 수 있는지 의구심이 듭니다.


데이터 세트도 제공해 주고, NN도 정해져 있으니까요.





사진을 보고 남자와 여자를 구분해 보자



그래서 저는 직접 데이터 수집부터 트레이닝까지 다 해 보고 싶어졌습니다.

그렇게 밑바닥부터 다른 도움없이 스스로 해냈을때, 비로서 뭔가 할 수 있을 것 같았습니다.



목표는 "남자와 여자를 판별하는 봇 만들기' 로 정했습니다.

그런데 시작과 동시에 커다란 장벽에 부딪혔습니다.


제가 어디서 듣기론 Classifier 문제에서 데이터는 클래스당 최소 1,000 개 내외는 나와야 한다고 했습니다.

그럼, 안경 쓴 사람 + 안 쓴 사람 = 2,000 장의 이미지를 구해야 합니다.


1000 장은 어디까지나 최소이고, 정적 수량은 1만여 장 입니다.

그 이상인 경우, 오히려 학습이 더 안좋아 진다고 하네요



아무튼 그래서 생각해낸 방법이 구글링 하기 입니다.





삽질의 시작



일단, 저는 매우 게으릅니다. 그래서 dataset, face 등으로 검색을 시도했습니다.

누군가 잘 만들어 정리해 놓은 데이터 세트가 필요했거든요. :)


생각해보면 모순된 이야기네요. 직접 해 보고 싶다면서 데이터세트를 구하는 노가다는 직접 하기 싫었거든요 ㅎ.


그렇게 열심히 검색해서 아래와 같은 이미지들을 주워 모았습니다.





이렇게 남자, 여자 각각 500 여장씩 사진을 모았습니다.

네, 1,000 장은 무리입니다. 일단 사진 모으는건 둘째 치고 라벨링 하는것 자체도 엄청 힘듧니다.


아시다시피 제가 하려는건 지도학습이므로 먼저 제가 정답(남/녀)을 구해 놓아야 하니까요.



그래서 생각해낸 방법이 구글링 하기 입니다.





삽질의 서막



먼저 제가 학습하려는 형태는 이러합니다.

각 사진을 '남자' 와 '여자' 폴더에 각각 저장해 놓습니다.

이후 폴더명을 변경합니다. '남자' 는 '0', '여자' 는 '1' 로 말입니다.


눈치 채셨겠지만, softmax 이후에 tf.argmax() 를 호출하면 index 가 나옵니다.

2개로 분류하는 문제에서는 0과 1 둘중 하나로 나오겠지요.

그렇기 때문에 폴더명을 애초에 라벨로 삼았습니다. 0 과 1 이라는 폴더명으로요.


물론 Train 용과 Eval 용으로 각각 준비합니다. 

구조를 대강 그려보자면,,


- Train

   - 0

   - 1

-Eval

  - 0

  - 1


이런 형태입니다.

Train 과 Eval 에 들어있는 사진들은 당연히 중복되지 않고, 고유한 사진들입니다.



자 이제 NN(Graph) 을 구성해 봅시다.

MNIST 할때 구글에서 받았던 Expert 코드를 수정하기로 합니다.

NN 구성도 대충 따라합니다.



자!! 이제 학습시켜 봅시다!!



결과는.........참담합니다.


Training 결과 Accuracy 가 90 이 넘었는데, Eval 데이터로 돌려보면 56% 정도가 나옵니다.

읭?????

찍어도 확률이 50% 인데 56% 라고????






원인이 뭘까?



원인은 '딥러닝은 사기다' 입니다.

네 사깁니다. MNIST 는 짜고치는 각본입니다.

NN도 MNIST 용으로 철저히 계산된 네트워크이고, 데이터도 거기에 맞게 들어 있는 것입니다.


라고 구글 음모론을 만들기 시작했습니다.

그 당시에 그랬다는 겁니다 :)



그러나..

그럴리가 없잖아..... ㅠㅠ



문제의 원인을 찾기 시작했습니다.

그리고 이런 생각에 다달았습니다.


"컴퓨터는 멍청하다. 딥러닝은 마술이 아니다. 사람이 못하면 컴퓨터도 못한다."


저는 트레이닝 데이터에 의심을 갖기 시작했습니다.

자 생각해 보세요.


어떤 사진이 두 부류로 나뉘어져 있습니다.

그리고 친구에게 보여줍니다.

물어봅시다.


"철수야. 내가 어떤 기준으로 사진을 나누어 놓은것 같니?"


똑똑한 친구는 "응, 남자와 여자로 나눈건가?" 라고 하겠죠.

안타깝지만 철수는 그렇게 똑똑하지 않습니다.


"크기대로 나눈거야? 동양인과 서양인? 노인과 젊은이?"

라고 철수가 말합니다.



이쯤에서 다시 생각해보죠.

Classification 문제의 동작 원리는 수많은 사진을 반복 학습하면서,

그 분류 기준을 컴퓨터가 유추해 내는 것 입니다.



사진 두 장이 있다고 가정해보죠.

두 사진을 다른점이 너무나 많습니다.



어떤 기준같나요?

남자와 여자인가요?


아닙니다. 너무나 많습니다. 사실 모든게 다 다릅니다.

'남자와 여자' 도 맞는 이야긴데, '실내와 야외', '빨간옷과 줄무늬옷', '수염이 있고 없고' 등등등...


이런 상황에서 컴퓨터가 두 부류의 사진만 보고 분류 기준(남자와 여자)를 유추할 수 있으려면 어떻게 해야 할까요?

네 맞습니다. 엄청나게 많은 데이터가 있으면 됩니다.

수천장의 사진들이 '남자와 여자' 라는 차이를 제외하고는 나머지 공통점이 하나도 없으면 됩니다.



그러면, 문제가 트레이닝 데이터가 적었기 때문일까요?

여기서 한발 더 나아가 생각했습니다. 그리고 그 결론은


'불필요한 정보는 방해만 될 뿐이다.'

라는 것입니다.


제가 하려는건 사람의 얼굴 정보에 따른 분류입니다.

머리 말이에요. 머리. 눈, 코, 입, 귀, 머리카락을 포함한 머리요.


나머지 배경들은 방해만 될 뿐입니다.




결론은 "학습 데이터가 매우 중요하다."







올바른 데이터를 수집하자



평소 귀찮은 일을 싫어하는 저는, 이번만큼은 성실히 데이터를 모아보기로 했습니다.

물론 좀 편한 방법으로요.


제가 생각한 수집 시나리오입니다.



1. 이미지 구글링을 합니다.

 - 여기에 팁이 있습니다. 단지 얼굴만 필요하다면 구글 이미지 검색에서 '얼굴' 로 검색하면 되겠지요.

만약 야외에서 촬영한 얼굴 사진이 필요하면 어떻게 할까요?


"야외 얼굴"로 검색하면 이렇게 나옵니다.




네, 얼굴보다 잡다한 물건들이 90% 정도 차지하네요.

방법은 검색옵션입니다.


유형을 '얼굴'로 필터링하면 구글에서 얼굴 사진만 뽑아줍니다 :)



2. 저장은 귀찮아. 크롬 플러그인이 있지 않을까?

 - 크롬 앱스토어에서 이미지 다운로드로 검색해서 적당한 놈을 하나 찾았습니다.

이름은 Fatkun 이라는 놈인데, 꽤 쓸만합니다. 사용법은 각자 설치해 보시면 쉽게 아실겁니다. :)



3. 쓸만한 이미지만 추려보자.

 - 이렇게 모아진 이미지는 각양각색입니다.

저는 얼굴만 제대로 나온 사진이 필요합니다. 얼굴을 가리거나 옆모습이 찍힌 사진, 단체사진들은 필요하지 않습니다.

그리고 배경없이 얼굴만 딱 있어야 합니다.


결국 포토샵을 켜야 .......는 부지런한 사람들이 하는 방법이고, 저는 게으르므로,,

스크립트를 짭니다.


먼저 Yaw, Pitch 값이 10 내외이고, Roll 은 5 이내인 사진만 고릅니다.

또한 얼굴 영역의 사이즈가 짧은 쪽이 100 pixel 이상 되는 사진만 간추려 냅니다.

그리고 얼굴 영역에 맞게 잘라낼 것입니다.


이것을 해결해 줄 녀석은 바로! 구글 클라우드 서비스 API 입니다.

구글에서 Face 관련 API 를 제공해 줍니다.

이것을 이용하면 위에 말한 작업을 간단히 해 낼 수 있습니다.



그 결과 모은 사진..


이렇게 남녀 각각 1,000 여장을 쉽게 모았습니다.


물론 남녀 분류는 직접 해 주었습니다. ㅠㅠ

남녀를 분류해 주는 프로그램은 없었냐고요?


"지금 제가 만들려는게 그거잖아요 -_-"




 "데이터가 매우 중요하다."






데이터를 한번 정제하자



사실 이렇게 데이터를 모은다고 되는게 아닙니다.

쓸데없거나 진짜 사람이 아니거나, 얼굴이 일부 가려진 사진들은 전부 걸러내야 합니다.


퀴즈입니다.


"남자인지 여자인지 애매모호한 사진은 트레이닝 데이터에 포함시켜야 할까요? 말아야 할까요?"



제가 처음에 고민한 부분입니다.

결론부터 말씀드리자면, "트레이팅 데이터는 확실한 것만 사용한다." 입니다.



생각해 보세요.

3 살짜리 조카에게 동그라미와 네모를 가르치려고 합니다.


둘 중 어느 교육방법이 더 좋을까요?


1. 명확한 동그라미와 명확한 네모를 가지고 가르쳐준다.

2. 동그라미도 아닌 것이 네모도 아닌, 애매모호한 모양을 가지고 와서 '어떤' 기준으로 동그라미와 네모로 구분해서 가르쳐 준다.



당연히 전자쪽이 바람직한 교육일 것입니다.

1번 교육방법으로 학습하고 나면, 애매모호한 모양을 봤을때 적당한 나름의 기준에 따라

네모와 동그라미 중 어느쪽에 더 가까운지 판단할 것입니다.


그렇지 않고 후자 방식으로 교육했다면,

모든 것을 '의심'하게 됩니다.

누가봐도 명확한 '네모'를 보고도 '네모처럼 보이지만, 사실 동그라미일수도 있어' 라고 말입니다.


네모와 동그라미 구분하는 봇이 아니라, 의심병 환자가 됩니다.


제가 직접 테스트한 결과입니다. 어디까지나 제 경험입니다.

제가 잘못 생각한 것이라면 코멘트 달아주세요 :)




아무튼 이런 이론에 입각하여, 어정쩡한 중성적인 사진은 모두 트레이닝 데이터에서 삭제했습니다.

다만 evaluation 용으로 남겨 두었습니다.

물론 추후에 이런 '애매한' 문제들만 모아서 '추가학습' 시켜주어도 됩니다.





학습 데이터에 전처리를 하자



이제 모든 준비가 끝났습니다.

학습 데이터도 잘 준비되었고, 네트워크만 구성하면 됩니다.


먼저 학습데이터는 변형을 주어서 사용할 것입니다.

한정된 학습 데이터의 수를 늘리는 목적도 있지만, 무엇보다도 다양한 케이스에 대해서도 인식을 잘 할 수 있게 하기 위함입니다.

저는 일반적으로 쓰이는 'brightness', 'contrast', 'crop', 'horizontal flip'을 적용할 것입니다.


아시겠지만, Brightness 값과 Contrast 값을 바꾸어 학습시키면, 이 두가지 팩터에 대해서 유연한 machine 이 될 것입니다. flip 은 순수하게 학습데이터를 뻥튀기 하기 위한 목적이고요. 

crop 은 약간 다른 관점에서 생각했습니다. 준비된 학습 데이터는 얼굴이 사진 정중앙에 꽉 차있는 것들입니다. 실제로 완성된 머신을 가지고 제3의 이미지를 추론한다고 생각해보죠. 이 이미지가 학습했을때처럼 얼굴이 사진 정중앙에 위치할 경우는 거의 없을 겁니다. 분명 어느 한쪽으로 조금은 치우쳐져 있겠죠. 물론 조금 치우쳐져 있는 것들은 pooling 과정에서 보정될 것입니다. max pool 이나 mean pool 을 통해서 말이죠. 그런데 그보다 좀 더 치우쳐져 있다면 어떻게 될까요? 결과는 좋지 않을 것입니다.

이 문제를 해결하기 위해, 학습 데이터를 애초에 random crop 해서 사용합니다.


[학습 데이터]


[실전용(?) 이미지]


그리고 '남', '녀' 를 구분하는데 Color 는 크게 필요치 않다는 생각을 하였습니다.

흑백 사진이든 컬러사진이든 우리(사람)가 남녀 구분에 있어서 중요치 않기 때문입니다.

이런 이유로 사진을 GrayScale 로 전처리해서 사용합니다.






뉴럴 네트워크를 만들어 보자



아직 Weight 와 Bias 값을 어떻게 초기와 할 것이냐, Cost Function 이나 Normalize 는 어떻게 할 것이냐 등의 문제가 남아 있습니다.

제 생각엔 이 요소들은 좀 더 나은 퍼포먼스와 정확도를 위해 필요한 것이지, 필수적인것은 아닐거라는 생각을 했습니다. 자료나 논문들을 보면, 실제로 이런이런 batch normalization 을 했더니 좀 더 성능이 좋았다라는 식으로 되어 있습니다. A 는 되는데 B 는 안되더라.. 하는 글들은 아직까진 보지 못했습니다. 그러므로 저는 이 값들은 대부분 고민없이 대중적인 값들로 사용했습니다.

나중에 문제가 되거나, 성능이 떨어지면 그때가서 바꾸도록 할 생각입니다.



제가 구성한 NN 과 전처리는 다음과 같습니다.


0. 입력 데이터는 100x100 이미지

1. 각종 전처리 수행 (결과는 80x80)


2. Convolution (8x8 3채널)

3. Relu

4. Average Pool (kernel 2x2, stride 2x2)


5. Convolution (5x5 64채널)

6. Relu

7. Max Pool (kernel 2x2, stride 2x2)


8. Convolution (3x3 128채널)

9. Relu

10. Max Pool (kernel 2x2, stride 2x2)


11. Full Connect (x 1024)

12. Relu

13. Dropout (keep prob 0.7)


14. Full Connect (x 512)

15. Relu

16. Dropout (keep prob 0.7)


17. Full Connect (x 128)

18. Relu

19. Dropout (keep prob 0.7)


20. Softmax (x 2)





보면 Convolution 의 필터크기가 갈수록 작아지는데, 초기 80 x 80 이미지에서는 너무 작을 필요가 없다는 가정하에 그렇게 했습니다. 그리고 점점 피처맵이 작아지니까, 그에 맞게 필터크기도 같이 줄여 나갔고요. Dropout 은 nn 이 좀 복잡할 수 있을것 같아 0.7 정도로 dropout 해 주었습니다. 나머지 weight 는 표준편차 0.01 로 랜덤 초기화 해 주었습니다. bias 는 0.1 로 고정하였고요.



아무튼 이런 식으로 구성하고, batch-size 는 64로, 전체 트레이닝은 20,000 회 수행하였습니다. 그러므로 샘플 수는 64 x 20,000 개가 되겠네요. batch-size 는 128로 하게되면 학습 속도도 속도지만, 제가 쓰고 있는 컴퓨터에 GPU 가 딸려서 학습을 못합니다. ㅋㅋ 그래서 64로 해 보았습니다.


그렇게, 약 4~5시간 뒤...

Accuracy 와 loss 각 각각 1.00 과 0.00 근처에서 머물더니 학습이 끝이 났습니다.

찍어둔 로그를 살펴보죠.



일단 잘 된 것처럼 보입니다. 대략 16,000 번 정도 학습했을 때 안정기에 접어 들었습니다.

사실 이게 잘 학습된 것일 수도 있고, 아니면 그냥 over fitting 된 결과물일 수도 있습니다.


feature map 들도 한번 살펴보죠. 뭐 사실 눈으로 봐도 잘 모르겠지만.. :)










테스트를 해 보자




이제 준비된 evaluation 용 데이터를 가지고 검증해 봅시다!



검증용 이미지는 이렇게 학습용 데이터처럼 잘 정제된 이미지들입니다.




결과는 94.0 % 가 나왔네요. 처음치고는 나쁘지 않다고 생각됩니다.

멋모르고 덤볐을때 56% 나온것에 비하면 말이죠. :)


위에 [0 0 0 ... 1 0 0] 이라고 찍힌건, 제대로 input 데이터가 들어갔는지 확인하기 위해서 제가 찍은 값들입니다. (의심병이 있어서 ㅎ) (0 은 남자 1은 여자)


일단 남녀 구분 자체는 잘 되는것 같습니다. 이제 좀 더 실질적인 데이터로 테스트 해 보아야 겠습니다. 앞서 우리가 전처리로 Random Crop 했던것 기억하시죠? 그게 잘 먹히나 보도록 하겠습니다.


앞서 보여드렸던 김희선씨 사진입니다.


학습/검증용 이미지와는 다르게 얼굴이 이미지에 꽉 차있지도 않고, 정중앙에 있지도 않습니다. 우측 하단에 치우쳐져 있다고 볼 수 있겠네요. 그 결과는..?



네, 99.73% 여자인 것으로 나왔습니다. :)


측면 사진은 어떨까요? 우리가 학습시킨 데이터는 모두 정중앙을 바라보는 정면사진들입니다. 과연 어떻게 됬을까요? 아래와 같이 측면 사진을 넣고 테스트 해 봅시다.




다소 학습시킬때 사용했던 데이터와는 다르죠? 결과는?



99.99% 남자입니다. :)



그 테스트해본 결과들입니다.


 



 


 




얼굴이 정면이 아니더라도, 고개가 돌아가있고, 정면을 보지도 않고 대충 돌아간 사진들을 넣어보아도 모두 잘 인식됩니다. :)



참, 학습은 100x100 이미지를 80x80 으로 random crop 했었던것 기억하시죠? 테스트할때는 사이즈 제약없이 그냥 넣었습니다. crop 같은건 없습니다. 보시는것처럼 그냥 저 이미지 그대로 들어갔다고 생각하시면 됩니다. (사이즈만 80x80 으로 줄이고, Gray Scaling)









마지막 여러가지 장난질




중성적인 이미지를 넣으면, 어떻게 인식될까요? 궁금해졌습니다.

그래서 확인해 보았습니다.





98.7% 남자





99.9999 % 여자





57.0 % 여자




99.99% 남자





어떤가요?

잘못된것을 발견 하셨나요?


네 맞습니다. 엠버.. 사람이 봤을때, 여자인가? 남자인가? 헷갈린다면 머신역시 헷갈려 합니다.

엠버의 첫번째 사진을 보면, 100% 여자라고 확신하지 못하고 57% 정도로만 여자로 예상했습니다.

일반적인 남자의 사진의 경우, 99% 가까이 수치가 나왔던것에 비교하면 매우 낮은 수치이죠.


그리고 두번째 엠버 사진은 아예 남자라고 예상해 버렸습니다. 그것도 99.9% 로 말이죠. ㅎ.

이런 점을 볼때, 머신러닝은 매우 사람과 흡사한것 같습니다.


사람이 헷갈리면, 머신도 헷갈립니다. 물론 이런 중성적인 사진에 대해서 학습을 추가로 시키면 잘 인식되겠지요.

사실 엠버를 전혀 모르는 외국인이 엠버 사진을 본다면, 머신처럼 '남자' 라고 생각할 것입니다. :)



마지막으로.


첫번째 남자분. 사실 여자입니다. ㅋㅋㅋ

머신은 신이 아닙니다. 그냥 사람처럼 보고 판단할 뿐입니다 :)






마무리




이렇게 처음으로 딥러닝을 직접 해 보았습니다. 데이터 수집에서 학습, 검증까지.

사실 94% 라는 정확도는 그리 높은건 아니라고 생각합니다. CNN 쪽에서(MNIST 인가? ImageNet 인가?) 사람의 오차률은 5% 내외라고 본 것 같습니다. 그에 견주해보면 사람보다 조금 덜한 정도로 보여지네요.


이 부분에 대해서는 좀 더 많은 학습 데이터와 오랜 반복, normalize 등을 통해 개선할 수 있을거라고 봅니다. :)







추가내용




해당 포스팅 이후에, 여러가지 방향으로 개선 및 수정하였습니다.

그 결과 남/여 분류에 대해서 99.2% 의 신뢰도를 얻는 모델을 만들 수 있었습니다.


바뀐점이라고 한다면,


1. NN 을 좀 수정하였고, 

 - Input Image Size : 88 pixels

 - Grayscale

 - Model Size: 6.9 MB

 - 




2. Image 의 형태를 아래처럼 수정하였습니다.



3. 그리고 남/여 각각에 대해서 Training Data 를 1만장으로 늘렸습니다. (총 2만장 + 검증용 2천장)








소스파일 (via Low Level API)




소스를 공유해 달라는 글이 많아서, 올립니다.

코드 정리를 해서 올리려다가, 시간적 여유가 없어서 정리없이 올립니다.

이후에 제가 다른 작업들을 하느라, 포스팅이랑 무관한 코드이 섞여 있습니다.


nn_gender.py 가  포스팅의 Gender Classification 하기위한 network 입니다. (같은 network 인지, 이후에 수정되었는지 가물가물)

runner.py 가 실행을 위한 EntryPoint 입니다.

cnn.py 는 일종의 tiny platform 같은 녀석입니다. 

runner 가 cnn.py 를 실행하고 cnn.py 는 command line parameter 에 따라서 특정 network(위 경우 nn_gender)를 실행합니다.


nn_gender, nn_car, nn_hand 등등 feature 에 따라서 network 가 다양한데,

nn_gender 만 올려드립니다. :)



crystalcube.zip






소스파일 (via High Level API with slim)





기존에 올린 소스코드는 tensorflow 의 low level api 를 이용하여 구현한 것 입니다.

이번에 slim 이라는 녀석을 이용하여 간략하고 가독성 좋게 수정하였습니다.

+ 모델,기능,전처리 모두 수정되었습니다










Crystalcube_slim.zip