Search

'분류 전체보기'에 해당되는 글 299건

  1. 2018.05.01 우분투 16.04 한글 입력하기
  2. 2018.04.08 가끔은 소프트웨어도 둥글다
  3. 2018.04.06 구조설계서 작성 후기 - 구조 검토가 우선
  4. 2018.03.24 컴포넌트 사양 - 구조설계서 작성(4/4)
  5. 2018.03.12 시스템 구조 - 구조설계서 작성(3/4)
  6. 2018.02.11 소프트웨어는 직관적이지 않다
  7. 2018.02.08 UI 문서 - 구조설계서 작성(2/4)
  8. 2018.02.04 기능적/비기능적 요구사항 - 구조설계서 작성 (1/4)
  9. 2018.01.27 ARM64 리눅스 부팅 초기 어셈블리 코드 분석(head.S) (2/2)
  10. 2018.01.21 이기적인 총무 리팩토링 - Database 관리 부분
  11. 2018.01.17 ARM64 리눅스 부팅 초기 어셈블리 코드 분석(head.S) (1/2)
  12. 2018.01.07 이기적인 총무 - 리팩토링 계획 (1)
  13. 2018.01.01 KVM - ARM
  14. 2017.11.18 말하기와 듣기 - 개발자로서 갖춰야할 기본 덕목
  15. 2017.11.11 QEMU와 KVM - 2
  16. 2017.11.01 QEMU와 KVM - 1
  17. 2017.10.12 폴리페이스 - 데이터 학습시키기
  18. 2017.09.12 폴리페이스 - 데이터 구하고 가공하기
  19. 2017.09.04 폴리페이스 -기획
  20. 2017.08.18 뉴럴네트워크(Neural Network)
  21. 2017.08.10 오버피팅(Overfitting)
  22. 2017.08.03 숫자인식 코드 분석해보기 (2)
  23. 2017.08.03 Softmax와 Cross entropy
  24. 2017.07.31 Logistic Regression (로지스틱 회귀)
  25. 2017.07.28 Linear Regression (선형회귀)
  26. 2017.07.20 tensorflow 설치하면서 인공지능을 정의해보자
  27. 2017.07.17 개발자는 이제 인공지능을 공부해야 합니다.
  28. 2017.07.15 총무앱 - 이기적인 총무 런칭 그리고 업데이트
  29. 2017.07.02 총무앱 - 디자인
  30. 2017.06.06 총무앱 - 개발+a

우분투 16.04 한글 입력하기

개발/삽질 기록 2018. 5. 1. 09:37 Posted by 아는 개발자


Ubuntu 16.04 에서 한글 입력기를 설치하는 방법


1. 터미널 창에서 ibus 입력기 및 한글 입력기 설치

ibus 입력기용 한글을 시스템에 추가하는 작업. 터미널을 열고 아래 설치 명령어 입력


sudo add-apt-repository ppa:createsc/3beol

sudo apt-get update

sudo apt-get install ibus ibus-hangul


2. Language Support에 한글 추가

한글을 추가하고 한글 입력이 가능한 입력 방식을 변경하는 작업

  1. 시작 -> keyboard -> Language Support
  2. Keyboard input method system 에서 IBus 모드로 변경하기
  3. Language에 한글이 없다면 Install / Remove Languages로 변경



3. Keyboard에 단축키 설정 변경

한글 키로 이동 할 수 있는 단축키 설정하는 작업. 기존 설정을 모두 지우고 한글 단축키를 설정 할 수 있는 환경을 만든다.

  1. 시작 keyboard -> shortcuts -> typing 진입
  2. 모두 disabled mode로 변경한 후, switch to next source 만 선호하는 short key를 입력



4. 시스템상 한글 입력기 등록 및 단축키 설정

시스템 입력기에 한글을 등록하고 단축키를 설정한다

  1. 시작 -> Text Entry 진입
  2. 좌측 하단 '+' 버튼을 눌러 input source에 Korean(Hangul) (IBus) 추가
    * 설치를 했는데도 Korean (Hangul) (IBus)가 목록에 없는 경우가 있는데 이때는 시스템을 재부팅하면 된다.
  3. Switch to next source using 속성에 사용할 단축키를 입력

5. 한글 사용하기

이 포스트는 IBus 한글 입력기를 이용해서 English와 한글을 번갈아 사용하며 Write 했다. 아주 아주 Comfortable 하다.



728x90

가끔은 소프트웨어도 둥글다

2018. 4. 8. 10:21 Posted by 아는 개발자

 얼마 전 중국의 우주 정거장이었던 텐궁이 다행히 인명피해를 주지 않고 대서양에 추락했습니다. 추락하기 2일 전부터 뉴스에선 우주 위기 위험경보를 알리며 텐궁이 곧 지구에 추락하겠으나 아직 위치는 알 수 없으며 시간은 몇 시께라고 모호한 답변을 내놨습니다. 대기오염뿐만 아니라 이제는 우주에서도 말썽을 부리는 중국을 원망하면서도 한 가지 의문이 들었습니다. 인공지능이 바둑을 이기는 시대인데 어째서 위성 추락 지점 하나 제대로 계산을 못하는 거지? 물론 시속 20,000KM일 정도로 빠르고 변화무쌍한 날씨 외에도 여러 가지 요인에 영향을 많이 받을 수 있으니 그럴 수도 있다고 칩시다, 그런데 추락 12시간 전까지도 여전히 정확한 추락 지점을 예측하지 못하고 다행히 우리나라에 떨어지진 않는다라는 짧은 답변만 내놓았죠. 아니 이건 너무 심한 거 아닙니까? 인명 피해가 발생할 수도 있는 시급한 상황인데 아직도 예측만 하고 있다니요!


그림 1. 텐궁 추락 예상 그림


날카롭게 비판을 했지만 한편으론 찔리기도 합니다. 제대로 예측하지 못하는 건 텐궁 추락 지점뿐만 아니라 소프트웨어도 그렇거든요. 사실 소프트웨어는 간단한 실수로 인해 천문학적 비용이 발생한 유구한 역사를 가지고 있습니다. 1996년 유럽이 2000년대 세계상업위성시장 석권을 위해 야심 차게 내놓았던 위성 적재 로켓 아리안 5호는 발사 1분도 안돼 공중 폭발했었습니다. 유럽도 인간을 우주에 보낼 수 있다는 꿈을 안고 프랑스를 주축으로 유럽 12개국이 약 6조 7천억 원을 투자했는데 1분도 안돼서 꿈이 산산조각이 난 셈입니다. 그런데 손해액과 자존심에 금이 간 것에 비해서 원인은 참 간단했습니다. 바로 전 모델인 아리안 4호의 로켓 항법 장치 코드를 그래도 옮겨왔는데 이 코드에서 64비트 Double형 값을 16비트로 받아 Overflow가 일어난 것이 문제였습니다. 이 정도 실수는 처음 소프트웨어를 배우신 분들이 주로 저지르는 실수입니다. 세계 최고의 전문가들이 이런 실수를 했다는 게 아이러니하죠?


그림 2. 아리안 5호 폭발. 10년의 노력이 물거품이 되는데는 48초면 충분했습니다.


2014년 급발진 문제로 기업 역사상 최대 위기를 맞은 도요타도 SW 결함이 원인이었습니다. SW 컴포넌트 간에 정보를 전달하기 위해 ECU(차량 내 CPU로 보면 됩니다) 내의 메모리 영역에서 공유 메모리 기능을 사용하는 도중 특정 지점에 간섭 현상이 일어났고 이것이 잘못된 지시로 이어져 급발진이 일어나게 된 것이죠. 공유 메모리를 사용할 때는 Lock을 이용해 간섭을 배제하는 것이 원칙이라고 운영체제를 수강한 학부생이라면 기본적으로 알고 있는데 어떻게 이런 실수를 저지를 수 있었을까요? 그것도 사람 생명을 위협할 수 있는 자동차 소프트웨어에서 말이죠.


그림 3. 오랜시간 발뺌한 것에 비하면 1조 2천억원은 관대한 벌금입니다


+) NASA 엔지니어까지 매수하며 끝까지 발뺌하던 도요타는 결함을 시인했고 당시 자동차업체 역사상 최고의 벌금인 12억 달러(약 1조 3천억원)를 물게 됐습니다. 


일반인들의 눈에는 이런 실수를 한 기업들은 바X, 멍X이 정도로 보일 것입니다. 결함의 원인이 전문적인 지식이 필요한 것이 아니라 기본적인 원칙을 준수하지 못해서 발생한 것이었으니까요. 나라면 저 정도 실수는 하지 않을 것이라고 생각합니다. 정말 그럴까요? 한번 만들어 봅시다. 버스 터미널이나 열차 역에 설치된 무인 티켓 발매기의 소프트웨어를 만든다고 해보죠. 예상 시나리오는 간단합니다. 사용자가 원하는 목적지를 선택하고 신용카드를 이용해 결제를 한 후 티켓과 영수증을 출력해주는 시스템을 만든다고 해봅시다. 개발의 편의를 위해 현금을 받는 경우는 제외합시다. 목적지 정보와 비용 데이터 베이스, 유저 인터페이스는 이미 개발된 상태고 카드사들과 결제 시스템도 이미 연계돼있습니다. 우리가 할 일은 각 모듈을 연결시켜 무인 발매기 안에 넣고 혹시 모를 예외 상황에 대해서 처리를 해주기만 하면 됩니다. 어떤 예외상황들이 있을까요?


유효기간이 지난 신용카드를 사용한다거나 목적지 버튼을 누른 후 오랜 시간 동안 결제를 하지 않는 경우에 대한 예외 처리는 자주 나오는 답변 중에 하나입니다. 그런데 혹시 티켓과 영수증은 생각해보셨나요? 만약 결제를 다 했는데 용지랑 잉크가 없다면 어떻게 해야 할까요? 이건 소프트웨어가 처리할 일이 아니라고 하시면 곤란합니다. 지금 용지와 잉크가 부족하니 역무원을 불러오라고 디스플레이에 출력하던지 아니면 사전에 결제를 막던지 했어야 합니다. 아무런 처리도 하지 않는다면 사용자는 비용은 지불했고 승차 시간은 5분밖에 남지 않았는데 버스표는 나오지 않는 매우 난감한 상황에 처하게 됩니다. 명절처럼 버스표가 대부분 매진일 때를 생각해보면 매우 아찔하네요. 빗발친 민원을 감당한 역무원은 분노에 찬 얼굴로 손해 비용을 청구할 것입니다. 그리고 만든 개발자의 신뢰는 추락할 겁니다.


경력이 5-6년이 넘은 소프트웨어 개발자도 잉크와 프린트가 없는 경우에 대해선 예측하지 못했습니다. 이분들은 업계에선 꽤 유능하다고 인정받던 분들이었습니다. 이 사례가 의미하는 바는 명료합니다. '똑똑한 사람도 모든 경우를 예측할 수는 없다'는 것입니다. 법을 만들고 시행하는 엘리트 공무원들도 미꾸라지 같은 편법까지 막지 못하는 것처럼 소프트웨어 개발자도 모든 예외 상황을 막지 못합니다. 가끔은 소프트웨어는 물 샐 틈 없이 꼼꼼히 막아놨다고 생각했는데 어딘가에서 물이 새고 있는 배와 같기도 합니다. 만들기 전에 튼튼히 설계를 하고 운행 중 문제가 생기면 최대한 빨리 구멍을 막는 것 또한 중요합니다. 윈도우 업데이트도 예측하지 못했던 구멍을 메우는 작업과 같습니다.


그림 4. 제 PC에 설치된 윈도우 업데이트 

'공은 둥글다'는 표현은 공이 어디로 튈지 모르기 때문에 압도적인 전력을 가진 팀도 승리를 보장 할 수 없다는 뜻으로 쓰입니다. 전 무슨 일이든 완벽히 예측 할 순 없다는 점을 빌려 이 표현을 소프트웨어에도 적용할 수 있을 것 같습니다. 하지만 완벽한 소프트웨어를 만들려는 개발자들의 노력을 생각해 사족 한마디만 덧붙이겠습니다.


가끔은 소프트웨어도 둥글다


아래의 링크에서 사진과 자료를 가져왔습니다


1. "도요타 급발진은 SW 결함이 원인" 도요타 조사 보고서 파장, http://www.etnews.com/20140320000188?rccode=lvRc, 

2. 도요타 리콜 사태 - 나무위키 https://namu.wiki/w/%ED%86%A0%EC%9A%94%ED%83%80%20%EB%A6%AC%EC%BD%9C%20%EC%82%AC%ED%83%9C

3. 아리안5호 로켓 발사실패 따른 파장, http://news.joins.com/article/3279252

4. 지구로 추락중인 중국의 우주정거장 '텐궁1호' http://principlesofknowledge.kr/archives/54310

5. 토요타, '급발진' 1조 2천억원 보상 합의 http://mn.kbs.co.kr/news/view.do?ncd=2588592

728x90

총 네번의 포스트로 작성한 구조 설계서의 원본 파일을 드.디.어 깃허브에 업로드했다 -> 링크. 이 포스트를 몇 분이나 읽고 계실지는 모르겠는데 관심 있으신 분들은 얼마든지 환영이다(욕설만 하지 않는다면 태클도 받는다) 아직 채워야할 내용도 있을 뿐만 아니라 문장이 깔끔하지 않고 오타도 있는 부실한 문서지만 본래 소프트웨어 개발자는 선 릴리즈 후수정이 원칙이고(응?) 난 지금 마무리 포스트를 쓰고 싶은 관계로 일단은 올렸다. 한 달 후의 내가 꼭 수정 하기를 바란다. 혹시나 독자가 생긴다면 더 일찍 하게 될 지도 모르겠다.


처음 이기적인 총무의 구조설계서를 작성하기로 할 때는 열정이 가득차 있었던 것 같다. 한달 간의 회사 교육 프로그램을 통해 구조설계의 중요성을 뼈저리게 배웠고 심화해서 공부해보고자 교육을 마칠즈음 만들어본 소프트웨어의 구조 설계 문서를 작성 하기로 마음 먹었었다. 그때도 이미 설계서 만드는 일이 쉽지 않다는 건 알고 있었지만 그래도 제로베이스가 아니라 이미 구현되어 있는 것을 문서로 옮기는 것이기 때문에 힘들지언정 지겨움은 느끼지 않을 것 같았다.


초반에 품은 마음 가짐 덕분인지 기능적/비기능적 요구사항 작성까지는 괜찮았다. 그런데 시스템구조를 작성하는 일부터는 문서 작성이 너무 힘들었다. 이미 구조를 다 짠 상태로 그것을 문서로 옮기기만 하면 되는 일인데도 이때부터는 노트북을 켜는 것 자체가 힘들었다. 내가 게을러지기도 했고 읽을 사람도 없는 무의미한 문서를 만드는 일에 싫증이 나기도 했지만 무엇보다 잡일이 많다는 점이 컸다. 구조설계 작업에선 요구사항을 토대로 다양한 후보구조를 도출하고 꼼꼼히 검토해 최종 구조를 선정하는 것이 주요한 일인데 예상외로 이 구조들을 일일이 다이어그램으로 옮기고 문서에 설명하는 일에 손이 많이 갔다. 총 시간만 본다면 구조를 검토하는 시간보다 문서 자체를 작성하는데 시간을 더 오래 소요 했으니 정작 중요한 작업은 뒷전으로 물러난 셈이다. 시작 하기 전에 나름 간소화 한다고 UML, Class 다이어그램 문법은 무시하고 그린건데 만약 꼼꼼히 챙겨서 그렸다면 더 오랜 시간이 소요 됐을 것이다. 만약 그랬다면 난 아마 중간에 포기했을지도 모르겠다.


모두다 잘하면 좋겠지만 시간이 촉박하다면, 구조 설계서 작성은 구조 검토와 문서 작업 사이에서 적정선을 찾는 것이 중요한 것 같다. 주된 작업은 요구사항 분석을 통한 후보구조 도출이지만 다른 사람이 읽는 산출물을 만든다는 점에서 문서 작업 또한 가볍게 여길 순 없다. 그러나 가독성을 지나치게 중시해 다이어그램의 문법을 꼼꼼히 체크하고 문서의 퀄리티를 높이는데 치중한다면 정작 중요한 구조 검토는 제대로 하지 못하는 주객 전도의 상황이 발생하게 된다. 양쪽에서 얼만큼 비중을 두느냐에 따라 훌륭한 설계이지만 사람들이 읽을 수 없는 문서가 될 수도 있고 문서 퀄리티는 훌륭하나 정작 소프트웨어 결과물은 엉망이 될 수도 있다.


다시 작성 한다면 문서 작업 자체는 좀 느슨하게 하게 하고 구조 검토에 중점을 두게 될 것 같다. 문서 작업 중에서도 꽤 오랜 시간을 잡아먹는 일은 다이어그램을 그리는 일을 많이 줄이게 될 것 같다. 클래스, 시퀀스 다이어그램은 현 시스템에 대해 모르는 이해 관계자들에게 쉽게 설명하기 위한 용도로 사용되는데, 소프트웨어에 문외한 이해 관계자는(주로 고객에 해당한다) 다이어그램이 있는 시스템구조까지 읽는 경우가 흔치 않고 이 챕터를 주로 읽는 개발자들은 다이어그램에서 클래스와 컴포턴트의 이름만 추출하고 구체적인 기능은 직접 코드로 보는 것을 선호한다. 문법대로 클래스 다이어그램에 속성과 함수명까지 빼곡히 입력하고 시퀀스 다이어그램에 루프까지 넣어가며 입력하지만 실제로 참고하는 경우는 미미한 것 같다. 각 클래스간의 매핑 관계와 역할만 둬도 충분 할 것 같다. 중요한 것은 훌륭한 구조를 만드는 것이지 이쁜 문서를 만드는 것이 아니다.

728x90

시스템구조에서 소프트웨어의 전반 구조를 추상적으로 정리 했다면 컴포넌트 사양에서는 전반 구조를 구체적으로 채우는 작업을 하게 된다. 앞선 작업에서는 클래스의 이름과 역할을 간단히 소개하고 속성(Attribute)와 동작(Operation) 칸은 비워 두었다면 지금부터는 직접 코드 상에서 구현하게 될 클래스의 함수의 이름과 속성을 명시하고 각각의 사용 목적, 기능을 설명해야 한다. 함수 내부 코드만 없을 뿐 헤더 파일에 전역 변수 명과 함수를 선언하는 것과 동일 하다. 클래스 내부 속성을 구체적으로 작성하고 난 후에는 각 클래스들의 시스템 동작하는 형태 결정한다. 여기서 말하는 '동작 형태'란 프로세스, 쓰레드 또는 핸드러와 같이 시스템 상에서 특정 작업을 수행하는 주체의 종류를 말한다. 다소 전문적인 용어 일 수 있지만 개발자들이라면 충분히 이해 할 수 있는 선에서 작성하면 무난하다.


그림 1. 각 클래스에 들어갈 속성과 함수를 명시하는 작업이라고 보면 된다.


시스템 구조를 작성 할 때 꼼꼼히 검토해서 작성 했다면 여기서 할 일은 속성과 동작 칸이 비어있는 클래스에 기능적 요구사항에 있는 기능들을 채워넣는 작업만 하면 될 것이다. 그런데 꼼꼼히 작성하지 않았다면 중간에 막히는 경우가 종종 생긴다. 예를 들어 간단한 기능 A 기능을 구현하려고 클래스들에 함수를 채워 넣었는데 현재 구조 상으로는 구현이 불가능 하거나 또는 하나의 클래스 내에 넣기에 객체지향에 어긋나는 경우가 있다. 이럴 때는 시스템 구조로 돌아가 동작 할 수 있도록 구조를 변경하고 난 후 다시 돌아와서 작성해야 한다. 컴포넌트 사양 작성이 까다로운 이유중 하나다. 이기적인 총무처럼 소규모 소프트웨어에서는 거의 발생하지 않는데 OS나 서버처럼 대규모 소프트웨어에서는 자주 발생하니, 다들 알만한 소프트웨어 회사에서는 최대한 삽질을 방지하기 위해 시스템 구조 단계에서 심사숙고하게 검토를 한다고 한다. 잘못하면 몇백 명이 삽질을 하게 될 수도 있으니까. 그런데 대충하고 몇백 명이 삽질하게 두는 곳도 있다.


이기적인 총무는 앞서 시스템 구조에서 크게 MVC 구조 및 이를 적용하는 방법과 시스템에서 필요한 Database 관리 기법에 대해서 소개 했었다. 컴포넌트 사양에서도 읽는 독자들이 예측하기 쉽게 앞선 작업의 연장선으로 MVC와 Database 관리 부분으로 나누어 작성했다.


1. MVC 구조 구체화


클래스 다이어그램을 명시화하는 일이라고 바로 그림 1에 있는 클래스들에 함수와 속성값을 대입해버리면 독자들이 이해하기 어렵다. 기승전결을 따르는 소설처럼 천천히 밑밥을 던지면서 전체 결과를 볼 수 있도록 전개해야 하는데 바로 결과물부터 던져주면 처음부터 직접 따라가야해 읽기가 쉽지 않다.


독자들이 시스템 구조를 읽고 가장 궁금해 했을 요소부터 시작하는 것이 좋다. 시스템 구조는 추상적으로 작성하기 때문에 개발자는 읽는 도중에 의문점이 많이 생기기 마련이다. 이 부분에 대한 궁금증을 먼저 채우면서 채우는 도중에 생길 수 있는 의문점으로 다시 채우는 방식으로 전개하는 것이 좋다.


그림 2. Controller 기본 구조


시스템 구조에서 MVC 구조를 설명하면서 Controller에 대해서 주구장창 설명을 했었는데 이들이 기본 골격에 대해서는 설명하지 않았다. 아마 독자들이 읽으면서 '도대체 이 시스템의 Controller는 어떤걸 말하는 것일까?'라고 생각 할 것 같아 먼저 Controller의 기본 구조에 대해서 설명했다. onCreate 함수를 통해 생성시 수행하는 작업을 하고 SetView 함수를 통해 화면을 출력한다. 그리고 createController를 함수를 통해 다른 Controller를 생성 할 수 있다. 아마 이렇게만 읽으면 생성 작업에서 어떤 일이 발생하는지 의문점이 생길 것이다. 이를 위해 시퀀스 다이어그램을 준비했다.


그림 3. Controller 생성 작업 시퀀스 다이어그램


그림 3은 Controller가 생성 될 때 일어나는 작업을 설명한 것이다. Parent Controller는 create Controller 함수를 부르면서 set Argument For Created Controller 함수를 통해 생성할 Controller에게 전달할 데이터 정보를 세팅한다. 그러면 생성된 Controller는 이걸 받고 메시지를 보내고 on Create 함수를 실행하고 화면을 띄우고 블라블라~ 사실 그림만 봐도 대강 예측은 가능하다. 읽는 독자도 그림보고 대강 이해가 됐으면 슬쩍 넘어가는 것이 좋다. 텍스트까지 읽으려면 읽어야할 글자 수가 너무 많다.


시퀀스 다이어그램 후에는 각각의 Controller들을 구현 할 떄 고려할 사항들을 정리했다. 이것들을 여기에 모두 쓰는건 무리고 나중에 마무리 포스트에서 깃허브에 정리해 올리도록 하겠다.


2. 데이터베이스 관리 


그림 4. 테이블 속성 명시


시스템 구조에서는 어떤 테이블이 있는지와 각각의 역할 및 매핑 관계에 대해서 간략하게 소개했다면 여기서는 구체적으로 어떤 속성을 자기조 있으며 각각의 속성이 어떤 형태인지 그리고 어떤 기능을 가지고 있는지를 명시해야 한다. 이미 코드상에 있는 create Table 쿼리문을 가져와서 각각에 대해서 썼다. 구현 할 때는 분명히 각각의 의미를 생각하고 만든것 같았는데 문서로 옮기다보니 어떤 목적으로 썼는기 기억이 잘 나지 않았다. 미리미리 주석으로 정리했으니 금방 글을 썼지 그렇지 않았다면 코드 뒤져보느라 시간이 오래 걸릴 뻔 했다. 포기 했을 지도 모르고..


그림 5. 테이블 관리 Controller


앞서 설명한 테이블을 관리하는 주체다. . 이미 구현한 코드상에는 있었는데 시스템 구조에는 빠져있어서 쓸지 말지 고민하다가.. 귀차니즘을 극복하고 시스템 구조에 필요성을 명시하고 다시 썼다.. 시스템 구조를 꼼꼼히 해야하는 이유다.

728x90

소프트웨어 구조설계서 작성 과정은 그림을 그리는 것과 비슷하다. 문서의 앞부분인 기능적/비기능적 요구사항을 도출하고 사용자 화면을 구상하는 작업은 어떤 그림을 그릴지 고민하는 작업으로 볼 수 있고 시스템 구조와 컴포넌트 사양을 작성하는 일은 앞에서 구상한 그림을 직접 도화지에 표현하는 작업으로 볼 수 있다. 표현하는 작업은 스케치 작업과 스케치 한 바탕에 색을 칠하는 작업으로 나눌 수 있는데 이중 시스템 구조를 작성하는 일은 스케치 하는 작업에 해당한다.  전체 그림의 윤곽을 그리는 스케치 작업처럼 시스템 구조를 작성하는 일 또한 거시적인 시각에서 동작하는 소프트웨어의 전반적인 틀을 짜는 작업이다.


스케치는 한 번 마무리 되면 다시 되돌리기가 어렵다. 색을 입히는 도중 그림 상에서 배치가 마음에 안들면 도화지를 찢고 다시 그려야 한다. 편리한 소프트웨어를 이용해 그리더라도 이미 색을 입힌 다른 영역을 지워야 하는 수고를 감수해야 한다. 그렇기 때문에 색을 입히기 전까지 신중히 고민하게 되고 지우는 작업을 반복하기도 한다.  시스템 구조를 작성하는 일도 동일하다. 전체 구조를 바꾸는 일은 쉽지 않기 때문에 처음에 구조를 짤 때 다양한 요구사항을 고려해서 만들어야 한다. 구현 단계에 진입 한 후에는 사소한 요구사항을 수정하기 위해 전체를 바꿀 수 없기 때문에 충분한 시간을 들여 난감할 수 있는 상황을 미리 대처 하는 것이 중요하다. 개인적으로 나는 설계 문서 작성 작업 중에서 가장 힘든 작업이라고 생각한다.


그래도 이기적인 총무처럼 네이티브 애플리케이션인 경우에는 안드로이드에서 기본적으로 제공하는 구조를 따라가면서 설계해가면 된다. 그런데 OS나 서버처럼 대규모의 시스템인 경우엔 스케줄러처럼 커널 내부의 요구사항과 그래픽 라이브러리처럼 미들웨어단이 모두 엮여 있는 경우가 있어 쉽지가 않다. 다양한 요구사항을 훌륭하게 반영한 시스템 구조는 최적화가 잘 돼 편리하게 사용할 수 있을 것이고 그렇지 못한 소프트웨어는 기능은 모두 동작 할지 모르나 업데이트 하기도 어렵고 좀 시간이 지나면 버벅 거리게 될 수도 있다. 소프트웨어의 품질은 시스템 구조를 작성하는 데서 판가름 난다고 봐도 무방하다.


그런데 나처럼 혼자서 미리 만들어 본 사람들은 다른 종류의 어려움을 겪는다.. 바로 매우매우 귀찮다는것. 사실 처음부터 하는 것도 아니고 이미 만들어 놓은 코드를 보고 밑그림 하는 일인데도 마우스가 생각보다 움직이질 않았다. 공부도 열심히 해보겠다고 기계식 키보드도 샀는데 이때 만큼은 치기가 싫어질 정도니. 기능적/비기능적 요구사항을 작성 할 때는 의지에 가득 차 있었는데 UI 문서를 만들 때부터 조금씩 싫증나기 시작하더니 클래스 다이어그램까지 그려야 하는 시스템 구조를 작성 할때는 과거의 나를 책망하게 되기도 했다. 내가 왜 이런 일을 한다고 설쳤을까. 교육 받은지 꽤 오랜 시간이 흘러서 약발이 떨어진 것 같기도 하고. 첫 문장을 쓰기 전까지는 '이거 굳이 꼭 해야할까?'라는 생각 뿐이었다.


그래도 한번 하기로 마음먹은 일인데 끝장은 봐야하지 않겠나. 문서 만들고 나면 뿌듯한 기분이 들 것 같기도 하고 교육 받았던 내용을 복습하는 효과가 있을 것 같기도 하고. 정 안되면 두툼한 포트폴리오라도 만들자는 생각으로 월요일 밤마다 꾸역꾸역 작성해갔다. 훗날 이직할 곳에서 과연 높게 봐줄지 모르겠다. 뭐 적어도 없는 것 보다는 낫겠지. 혼잣말이 길었다.


시스템 구조는 큰 그림을 설명하는 작업이기 때문에 추상적으로 설명하게 된다. 특히 첫 장부터 문서별로 Distributed니 Layered Architecture니 하며 그림만 딱 보여주는 경우가 많다. 그림만 보고선 구현의 방향만 짐작하게 될 뿐 구체적인 구현 방법은 감이 오지 않는데 이는 당연한 일이다. 그림을 그릴 때도 스케치로는 대충 어떤 그림이 나오겠거니 예상은 할 수 있지만 무슨 색으로 그려질지, 어떤 물감을 사용할 지는 알 수 없다.. 설계 문서 또한 세부 적인 요소들을 거시적인 관점에서 요약해야 하기 때문에 각 요소들을 꼼꼼하게 작성하는 일은 한계가 있다. 시스템의 윤곽을 소개하는 것이 이 문서의 목적이다.


단, 세부적으로 표현하진 못할지라도 독자에게 뒤에 컴포넌트 사양에서 어떻게 다뤄질 지 예상할 수 있도록 작성해야 한다. 그림 그리는 일과는 다르게 구조설계서를 작성하는 일은 다른 개발자에게 설명하는 것이 목적이다. 스케치는 작가 혼자서 이해하고 색칠할 수 있으면 그만이지만 시스템 구조는 작성자 뿐만 아니라 같이 개발할 동료 또한 시스템에 대해서 이해 해야 한다. 위 목적에 충분히 부합해서 작성할 수 있다면 잘 쓴 시스템 구조라 볼 수 있다. 이렇게 글을 쓰고 나니 검토해야 할 것 같은 의무감이 솟는다...


그림 1. MVC 패턴 개념도


구현한 사항을 반영해 작성하다 보니 이기적인 총무는 여러 개의 화면을 가지고 있고 각 화면에 표시할 데이터와 이 둘을 통제하는 컨트롤러가 존재하는 기본적으로 MVC 구조를 따르는 시스템이었다. 기본적으로 문서 작성은 모든 시스템 환경을 포괄 할 수 있도록 썼고 세부 단락에서 안드로이드에 해당하는 경우 구현 방법에 대해서 작성 했다.


그림 2. Android향 MVC


Controller에서는 Android App의 생명 주기의 요소인 Activity, Fragment 요소를 넣었다. View는 기본적으로 사용하는 XML 파일과 추가로 필요한 기능인 Additional Feature 내용을 넣었다. Model에서는 기본적인 데이터베이스 요소화 Activity에서 제공하는 모델 요소를 교집합으로 표현했다. 그림을 보고난 후에 글을 읽으면 이해하기 쉬울 것 같다는 느낌이 든다.


그림 3. 클래스 다이어그램


시스템에 존재하는 MVC를 클래스로 보고 각 클래스들의 요소와 각각의 관계를 표현한다. 그림을 보면 클래스들이 매우 많고 각각의 관계도 복잡해보이는데 실제로 시스템 상에서 존재하는 요소들이다. 이렇게 까지 표현했다면 꽤 꼼꼼히 스케치를 그렸다고 본다. 색 칠할 때는 각 클래스들이 갖고 있는 변수와 함수를 표현하기만 하면 된다.


그림 4. 테이블 관계도


클래스 뿐만 아니라 모델에 해당하는 데이터베이스에 대해서도 설명해야한다. 여기선 시스템 상에 존재하는 테이블의 종류와 각각의 역할 그리고 이들 간의 매핑 관계를 설명 했다.

728x90

소프트웨어는 직관적이지 않다

2018. 2. 11. 11:54 Posted by 아는 개발자

참으로 이상합니다. 다른 모든 이들이 불가능 할 것이라고 여겼던 사업영역을 과감하게 투자해서 세계적인 반도체, 스마트폰, TV를 만들어낸 국내 S기업이 유독 소프트웨어에서 만큼은 이전과 같은 빛을 보지 못하고 있습니다. 안드로이드와 ioS의 대항마로 야심차게 내놓은 Tizen은 2017년 인도 출시를 끝으로 역사의 뒤안길로 사라지고 있고 스마트폰에 전용 버튼까지 만들어가며 '음성 인식 비서'를 자처한 빅스비는 '불편하고 기대에 못미친다'는 사용자들의 원성을 받고 있습니다. 어깨를 나란히 한다는 기업들은 인공지능 시장에서 알파고와 알렉사를 만들고 있는데 S기업의 존재감은 미미합니다. 도대체 소프트웨어를 개발하는 것이 뭐길래 뭐든 '한다' 하면 정상 근처에 도달하던 기업이 유독 이 영역 만큼은 힘을 쓰지 못하는 걸까요? 특별한 시설도 필요 없이 그냥 성능 좋은 컴퓨터 여러 대만 있으면 되는데 말이죠.


물론 이번 포스트에서 국내 기업의 기업 문화나 개발 방법론을 지적할 생각은 없습니다. 다만 '왜 소프트웨어를 개발하는 것은 어려운 일인지'를 분석해보려고자 합니다. 이를 위해 소프트웨어 개발과 전혀 무관해 보이는 침대를 만드는 일과 비교를 해봅시다. 침대를 만드는 일은 물리적인 실체를 만드는 일입니다. Single 침대이냐, Super Single 이냐, 2인용이냐 그리고 1층 침대냐 2층 침대냐에 따라서 침대의 전반적인 크기가 결정됩니다. 크기가 적당한지 아닌지는 직접 눈으로 보고 판단할 수 있습니다. 소비자가 사용하기에 좁거나 집안에 배치하기에 너무 크다면 그에 맞게 수정 하면 됩니다. 더 가볍고 단단한 침대를 만들기 위해 특수한 재료를 사용합니다. 가볍고 단단한지는 직접 들어보고 두들겨보면 알 수 있습니다. 목수가 괴력을 지니지 않는 이상 사용자와 어느정도 공감할만한 수준으로 측정 할 수 있습니다. 가끔 공유 침대처럼 각도 조절 기능이 들어간 경우도 있는데 이것 또한 물리적 실체의 일종입니다. 침대 하부 어느지점에서 각도 조절을 시작하는것이 편리할 지를 판단하고 만들면 됩니다.


사진 1. 도깨비에 나와서 유명해진 공유 침대 입니다. 각도조절이 있는게 신기하죠?


물리적인 실체는 직관적입니다. 그렇기 때문에 침대를 만드는 사람들은 어렵지 않게 문제점을 판단하고 수정 할 수 있습니다(물론 편안한 잠자리를 만드는 일 또한 매우 어려운 일입니다) 그러면 소프트웨어는 어떨까요? 소프트웨어도 우리가 볼 수 있는 물리적 실체가 있을까요? 아마 소프트웨어 개발자가 아닌 일반인이라면 본적이 있다고 반문하실 겁니다. 집에 Widows10 데스크탑을 키면서 매일 아래와 같이 소프트웨어를 본다고 말이죠.


사진 2. 윈도우 10 바탕화면입니다. 전작보다 훨씬 깔끔해진 느낌입니다.


위 하면에서는 날씨 정보를 확인 할 수 있고 마우스 클릭으로 뉴스, 계산기, 크롬을 실행 할 수 있는 버튼을 다룰 수 있습니다. 즉 소프트웨어에서 제공하는 기능을 사용할 수 있는 인터페이스가 일반인들에겐 물리적인 실체입니다. 그렇다면 이것을 만든 사람들은 어떻게 생각할까요? 아마 개발자들은 위 화면은 그저 개발한 기능을 실행 할 수 있도록 구현한 UI 화면일 따름이라고 말할 겁니다. 그리고 물리적 실체로 아래와 같은 화면을 작업창에서 (자랑스럽게) 보여줄겁니다.


사진 3. 리눅스 커널 코드의 일부 입니다. 이 코드는 무슨일을 할까요?


사진 3은 리눅스 커널의 일부 코드를 스크린샷 한 것입니다. 네 맞습니다. 소프트웨어를 개발한 사람의 입장에선 이것이 물리적 실체입니다. 위 사진에 작성한 코드들이 컴파일 돼 이진수의 파일로 만들어지고 프로세서와 그래픽 카드는 이 파일을 실행해 사진2와 같은 인터페이스를 출력합니다. 날씨를 보여주는 화면과 크롬을 실행 할 수 있는 바탕화면은 프로그래밍 언어가 프로세서와 그래픽카드에서 유기적으로 동작한 결과물입니다.


그런데 위 코드가 무슨 기능을 하는지 느낌이 오시나요? 아마 비전공자들은 바로 읽는 것을 포기하셨을 것 같습니다. 이건 당연합니다. 그런데 전공자중에서도 커널을 다뤄본 사람이 아니라면 코드를 읽다가 금새 포기합니다. 한 번도 경험한 적이 없는 코드이니까요. 이쪽 분야의 전문가가 아니라면 코드만 봐선 무슨 일을 하는 것인지 알 수가 없습니다. 개발자가 변수와 함수명을 나름 직관적으로 표현하려 했지만 경험하지 못한 개발자에게는 깜깜합니다. 끈기 있고 경력이 많은 개발자라 하더라도 수만 라인을 단번에 분석 할 수는 없습니다. 이미 알고 있는 사람의 설명을 듣거나 또는 개발 문서를 읽고나서 그제서야 "아~" 하며 감을 잡게 됩니다. 그러고 나서 직접 로그 파일을 분석 해보면서 전체 시스템이 어떻게 동작하는지 파악하지요. 그렇다 하더라도 실제 개발까지 하려면 시간이 꽤 오래 소요됩니다.


침대를 만드는 일에 비하면 소프트웨어를 개발하는 일은 직관적이지 않습니다. 사용자의 시각에선 버튼의 크기를 바꾸는 일은 침대를 싱글 사이즈에서 더블 사이즈로 바꾸는 것처럼 느껴질 수 있지만 이것을 경험하지 못한 소프트웨어 개발자는 코드 내부에서 버튼의 크기를 결정하는 변수를 찾느라 오랜 시간을 보냅니다. 운좋게 변수를 빨리 찾더라도 변수 값의 변경이 다른 요소에 미치는 영향까지 고려해야하기 때문에 쉽사리 크기를 바꿀 수 없습니다. 버튼 크기를 바꿨다가 자칫 주변 버튼의 영역을 덮어써서 누르지도 못하게 될지도 모르니까요. 이를 위해선 UI 시스템이 어떻게 동작하는지 파악할 필요가 있습니다. 간단해 보이는 일이지만 꽤 오랜 시간이 소요됩니다. 버튼 변수의 위치와 미치는 영향을 단번에 파악할 수 있다면 쉽게 끝날 일인데 말이죠.


시스템을 개발한 사람이라면 능숙하게 할 수 있을지도 모릅니다. 그런데 직접 개발에 참여한 사람이라 하더라도 본인이 작성한 코드가 1천 라인이 넘어가면 예전에 작성한 코드들이 생소해집니다. 예전에 분명히 짠 코드 였는데 남이 짠 코드인것 마냥 느껴지죠. 만든 사람이 모른다니 일반인으로선 참으로 당황스럽지만 이런 경우는 꽤 빈번합니다. 주로 "누가 이렇게 짰어?!"라고 큰소리 치다가 본인이 수정한 이력을 확인하고 멋쩍여 하는 상황으로 연출됩니다. 그때부턴 처음 보는 사람인 것처럼 코드를 하나하나 분석하게 됩니다. 그리고 '내가 이런 코드도 짰었어?' 하며 소름 돋기도 하죠. 이처럼 소프트웨어는 만든 사람에게도 직관적이지 못합니다. 그런데 혼자가 아니라 여러 사람이 개발한 소프트웨어는 어떨가요? 상상만해도 무섭네요.


사진 4. 블루스크린 화면 많이 보셨죠? 개발 도중 블루스크린 화면을 보면 숨이 턱 막힙니다.


리눅스나 윈도우처럼 수십만 라인이 넘어가는 운영체제 소프트웨어는 매우 복잡합니다. 작업에 참여한 사람도 많고 서로 다른 요소들이 많이 얽혀 있기 때문에 간단한 수정 사항이 시스템 전반에 영향을 미쳐 사진 4처럼 시스템이 다운 되는 경우가 빈번합니다. 그런데 로그파일을 분석해보면 시스템이 다운된 원인은 꽤 단순하고 명료합니다. 침대로 치면 2층 침대의 사다리를 없앤 것이 원인이 되는 경우가 많죠. 침대를 만든 사람들의 입장에선 어처구니 없는 실수 일 것입니다. 그런데 개발할 때는 이런 문제가 발생할 것이라고 전혀 예상하지 못합니다. 문제가 터지고 나서야 자신이 바보같은 실수를 했다는 것을 깨닫고 자괴감에 빠집니다. 같은 개발자로서 책임감을 통감하면서도 전 짧은 변명을 하고싶습니다 


"코드상에선 침대의 사다리로 보이지 않아요


오늘도 직관적이지 못한 프로그래밍 코드와 씨름하는 소프트웨어 개발자 여러분들을 응원합니다.




아래의 링크에서 사진을 빌려왔습니다

  • 사진 1. ihttp://m.iloom.com/product/iloomView.do?goodsCd=HSSE500&colorCd=CCBGM&beforeNm=%EB%AA%A8%EC%85%98%EB%B2%A0%EB%93%9C(%EA%B8%B0%EB%B3%B8%ED%98%95)
  • 사진 2. https://www.theverge.com/2015/7/28/9045331/microsoft-windows-10-review
  • 사진 4.  https://www.lifewire.com/how-to-fix-a-blue-screen-of-death-2624518


728x90

앞 포스트에선 이기적인 총무 소프트웨어를 만들게된 계기(시스템 개요)와 시스템이 제공하는 기능 및 품질(요구사항)에 대해서 소개했다면 지금부터는 앞 문서에서 설명한 소프트웨어가 사용자에게 화면으로 보여지는 형태를 정리한 문서, 즉 User Interface 가이드 문서(이하 UI 문서)를 만들어야 한다.


개발자가 아닌 일반인들도 들어보진 못했어도 한번 쯤은 봤을 것이다. 주로 전자제품을 산 후 사용설명서에서 접해보게 된다. 겨울철이니 가습기를 예로 들어 보자. 가습기 사용 설명서에는 어떤 단추를 눌러야 가습기를 실행 할 수 있으며 분무량을 조절하는 버튼은 어디 있는지 그리고 세척 할때는 어떤 부품들을 분해해서 세척할 수 있는지 설명되어 있다. 아무런 안내가 없으면 사용자가 직관적으로 판단해야하는데 전자제품을 아주 잘 다루는 고객을 제외하면 대부분 사용에 어려움을 겪게 된다. 소프트웨어도 똑같다. 이기적인 총무를 사용하는 사용자가 어떤 버튼을 눌러야 '결제 내역 추가'기능과 '정산하기'기능을 이용 할 수 있는지 소개하는 짤막한 화면이 필요하다. 애플리케이션을 처음 다운로드 받으면 반투명한 소개페이지가 나오는데 그것이 UI 문서로 봐도 무방하다.


이렇게만 생각하면 UI 문서는 오로지 사용자를 위한 문서인것 같으나 꼭 그렇지만은 않다. 개발자는 UI 문서를 통해 요구사항 분석에 있는 기능들이 화면에 어떻게 적용되는지 확인 할 수 있고 이를 통해 시스템의 전반적인 플로우를 파악 하게된다. 여기서 파악한 시스템 플로우는 화면을 관리하는 객체(Android의 경우는 Activity로 보면 된다)를 선정하고 각각의 기능을 명시할 때 중요한 요소가 된다. 또한 문서에 명시된 화면을 구현 할 때 특수한 기능이 필요한 경우가 종종 있는데 이런 요구사항들은 기능적 요구사항에는 명시되어 있지 않는 것이라 개발의 뒷발목을 잡는 경우가 많다. 이런 경우를 방지하기 위해선 개발 기한을 산정할 때 위 문서를 꼼꼼히 읽어보고 화면을 구현할 때 어떤 어려움이 있을지 미리미리 확인하는 것 절차가 필요하다. 이 작업을 통해 화면 기능 요구사항이 추가로 만들어진다. 이 작업이 완료되고 나면 개발자는 도출한 객체와 기존에 있던 기능적 요구사항 및 추가적으로 도출된 화면 요구사항을 꼼꼼히 분석하고 각각의 요소들을 어디에 배치 할 지 시스템 세부 설계 할 수 있게 된다. 


사실 UI문서는 커널이나 미들웨어처럼 콘솔화면 외엔 화면에 보여질게 없는 소프트웨어에선 필요 없기도 하고 Web처럼 html에서 View가 막강한 권한이 있는 경우 크게 중요하진 않기도 하다. 그런데 Android처럼 MVC(Model View Controller)패턴을 이용해서 구현해야 하는 경우 Model과 Controller 의 권한을 부여할 때 중요한 요소가 되고 View에서 할 수 있는 기능이 제한되어 있는 경우(xml로는 여러개 탭도 못만든다)는 매번 직접 기능을 구현해야해 예상치못한 기술적 난제가 되기도 한다. 이런 경우엔 UI 문서를 통해서 미리미리 어려움을 예측하는 것이 중요하다. 실제로 이기적인 총무 앱을 만들 때도 기능적 요구사항을 구현하는 것은 그다지 어렵지 않았으나 미리 계획한 UI문서를 만들때는 많은 어려움을 겪었다.


문서내용은 심플하다. 사용자에게 보여줄 화면들을 쭈욱 나열하고 요구사항에서 명시된 기능을 사용 할 수 있는 인터페이스를 설명하면 된다. 개발자가 이해할 수 있는 정도로만 쓰면 된다. Word 형식 보다는 ppt로 여러 개의 슬라이드로 설명하는 것이 훨씬 간편하고 이해하기 쉽다.



문서를 통해 문서는 총 3개의 탭으로 이뤄져 있으며 각각의 탭은 버튼을 클릭하는 것 뿐만 아니라 손가락 슬라이딩으로 좌우로 이동할 수 있어야 하는 것을 확인했다. 기능적 요구사항에는 없지만 실제로 구현해보려고 하면 생각보다 꽤 오래 걸린다. 위 화면을 구현하려고 꽤 오랜 시간을 구글링 하는데 썼던 것 같다.

위 화면을 통해 기능적 요구사항에 명시한 기능 '결제내역 추가/수정/삭제'이 사용자 화면에 어떻게 적용되는지 확인할 수 있었다.

728x90


소프트웨어 설계 문서는 프로그래밍 언어의 문법처럼 반드시 따라야하는 규칙이 있는것은 아니다. 설계 문서의 주 목적은 소프트웨어 시스템을 모르는 사람에게 설명하는 것이므로 어떤 양식을 사용하든 위 목적에 충분히 부합한다면 잘 작성한 설계 문서라고 할 수 있다. 그러나 '충분히 설명할 수 있는 문서'라는 기준은 애매모호하다. 문서를 작성한 사람이나 소프트웨어를 개발한 사람은 전문적인 용어에 익숙하고 객체들 사이의 관계를 명확히 이해하고 있기 때문에 시스템을 추상화한 다이어그램을 이해하는것이 어렵지 않겠지만 시스템을 경험하지 못한 사람에겐 전문적인 용어는 외계어일 것이고 다이어그램은 초현실주의 작품으로 보이게 된다.


설계자와 독자의 간극을 해소하고자 오래 전부터 학계에서는 '어떤 문서가 바람직한 소프트웨어 문서인지'를 놓고 연구를 했었다. 학자들마다 뚜렷한 소신을 가진 탓에 바람직한 소프트웨어 문서의 절대적인 기준(사람의 의견은 매우 주관적이니까)은 내놓지는 못했으나 설계 문서라면 반드시 다뤄야 하는 내용과 이것을 표현하는 방법에 대해선 어느정도 공감대를 이뤘다. 예를 들어 설계문서라면 소프트웨어가 가진 기능을 설명하는 것이 필요하다고 생각해 기능적 요구사항이란 항목을 작성하게 됐고 위 항목은 UML(Unified Modeling Language)을 이용해 작성하는 것이 좋다고 여기게 됐다. 학계에선 기능적 요구사항을 도출하는 방법을 가지고 '내가 옳니 네가 틀리니' 하며 논쟁하고 있지만 산업계에선 어느정도 공감대가 형성된 요소들을 토대로 설계문서를 작성하고 있다.


이기적인 총무는 소프트웨어 설계 문서라면 반드시 포함되어야할 내용을 충실하게 작성하는 것을 목표로 뒀다. 비주얼 스튜디오나 크롬처럼 대형 소프트웨어도 아니기 때문에 특수한 내용이 추가될 일이 없기도 하다. 예전에 과제로 쓰던 설계 문서의 템플릿에서 반드시 필요하다고 생각하는 것들만 더 추려서 작성했다. 크게 네개 항목으로 나뉜다.


1. 시스템 개요


시스템을 처음 접한 사람에게 소프트웨어에 대해서 대략적으로 설명하는 항목이다. 소프트웨어를 만든 목적, 시스템이 동작하는 환경과 주요한 기능에 대해서 다룬다.



이 항목에서는 소프트웨어가 어떤 수익을 줄 수 있는지, 사업적인 측면을 중점으로 서술하기도 하는데 이기적인 총무는 돈을 벌려고 만든 시스템은 아니기 때문에 시스템이 갖고 있는 기능을 충실하게 작성하는 것에 초점을 뒀다.


아래의 그림은 시스템이 동작하는 환경(System Boundary)과 소프트웨어를 개발하는 범위를 다룬 그림이다. 시스템과 상호 통신하는 외부의 주체를 설명하고 이들 각각이 어떤 역할을 하는지 설명한다. 위 그림에서는 Actor1(졸라맨처럼 생긴)로 표현된 모바일 사용자가 이기적인 총무를 사용하는 것을 표현했다. 사용자를 제외하면 시스템이 별도로 외부와 통신하는 일이 없기 때문에 간단하게 표현 할 수 있었다. 만약 정부가 관리하는 서버와 통신한다면 또다른 Actor를 추가해야 할 것이다.


만약 이기적인 총무에서 외부 서버를 만든다면 이것은 Actor로 표현해야할까? 이때 기준은 설계 문서에서 서버에 대해서 다루는지로 판단한다. 설계 문서가 서버에 대해서 요구사항과 개발에 필요한 구조를 도출한다면 이것은 개발해야할 범위중 하나이므로 Actor로 표현해선 안된다. 그렇지 않고 이미 완성된 서버를 사용하는 거라면 Actor로 표현해 밖으로 빼낸다. 네모칸 안은 우리가 개발 할 영역이고 밖에 Actor는 시스템과 통신하는 주체로 파악하면 쉽다.


Use Case는 시스템에서 제공하는 기능을 말한다. 예를들어 Virtual Box라면 'Host PC에서 다른 OS를 Virtual Machine으로 실행 할 수 있다'가 Use Case가 된다. Use Case Diagram에서는 시스템 경계의 네모상자 안에 개발해야할 주요 내용들을 채워 넣는다. 설계자는 시스템에서 필요한 Use Case를 도출하고 네모안에 타원으로 기록한다.


그림에서 보면 여러 개의 Use Case가 <<include>>로 연결된 것을 볼 수 있는데 이는 각각의 Use Case들 끼리 포함되는 관계인 것을 표현한 것이다.


2. 요구사항(Requirement)


요구사항(Requirement)이란 말 그대로 시스템이 갖춰야 할 요건들을 말한다. 정산하기나 모임 관리처럼 시스템이 갖고 있는 기능은 기능적 요구사항이라하고 정산하는 속도, 시스템의 메모리 사용량처럼 기능은 아니나 측정해서 제한을 두고 시스템이 만족하도록 해야 하는 것은 비기능적 요구사항이라 한다.


2.1 기능적 요구사항


Use Case Diagram에서 타원형으로 기록한 Use Case에 대해 세부적으로 작성한다. 각 Use Case 별로 시스템이 어떤 동작을 하게 되는지를 단계별로 설명하고 각 단계별로 발생 할 수 있는 예외 케이스를 정리한다.



원래는 UML로 표현하는데 소규모 소프트웨어는 표로 표현해도 이해하는데 어려움이 없을 것 같아서 위 그림처럼 표현했다. 위 그림은 지출내역을 생성하는 Use Case에 대해서 다룬 기능적 요구사항이다. 기본 동작은 총 9개의 단계로 나뉘며 체크로 표시한 부분은 동작 수행시 발생 가능한 예외 케이스에 대해서 처리 방법을 설명한 것이다. 예외 케이스를 꼼꼼하게 작성 할 수록 개발할 때 편리해진다.


2.2 비기능적 요구사항


비기능적 요구사항은 성능과 가용성처럼 기능적 요구사항에서 다루지 못한 품질적인 요소를 다룬다. 예를 들면 '페이스북 좋아요는 0.5초 이내에 업데이트 돼야 하고 시스템은 24시간 사용이 가능해야 한다'같은 것이다. 반드시 만족해야하는 요구사항이기 때문에 항목들은 객관적으로 측정 할 수 있어야 하고 제약사항은 현실적으로 구현 가능한 수준이어야 한다. 측정 방식이 객관적이지 못하면 SE 부서와 개발자마다 측정 방식이 달라 릴리즈 단계에서 애를 먹을 수 있고 제약 사항이 비현실적이면 애초 구현 불가능한 것을 삽질한 셈이 된다. 



이기적인 총무는 주요 기능이 회계이므로 시스템에서 정산한 금액이 정확해야 한다. 측정하는 방법은 시스템에서 계산하는 금액이 실제로 회계에서 정산한 금액과 동일한지로 판단했고 제약사항은 이 둘이 반드시 동일하도록 했다. 동일하지 않다면 회계 기능은 신뢰할 수 없는것이 되므로 비기능적 요구사항을 충족하지 못한것이 된다. 기능적 요구사항을 개발하면서도 비기능적 요구사항을 생각하면서 개발해야 한다.


728x90

앞 포스팅에서 다루지 못한 부분들을 마저 분석해보자. 처음에는 ARM 어쎔 코드도 생소했고 Exception Level 개념도 없어 많이 헤맸는데 이젠 어느정도 훈련도 되어 있고 앞에서 했던 것들 보다 내용 도 적을 뿐만 아니라 상대적으로 익숙한 작업들이라 쉽다.


4. setup_boot_mode_flag


set_cpu_boot_mode_flag:
	adr_l	x1, __boot_cpu_mode
	cmp	w0, #BOOT_CPU_MODE_EL2
	b.ne	1f
	add	x1, x1, #4
1:	str	w0, [x1]			// This CPU has booted in EL1
	dmb	sy
	dc	ivac, x1			// Invalidate potentially stale cache line
	ret


현재 실행 되고 있는 cpu의 Exception Level Mode를 설정한다. VHE기능이 도입되면서 리눅스 커널도 EL2에서 돌아갈 수 있게 됐는데 그 기능을 위한 코드인 것 같다. __boot_cpu_mode 변수에 값을 대입하는것 외에는 별다른 작업이 없다.


5. __create_page_tables


페이지 테이블을 생성하는 작업이다. 그런데 모든 커널 영역에 대해서 매핑하는건 아니고 MMU가 켜지기 전에 필요한 일부 영역들을 1:1로 매핑한다. 위 영역으로는 idmap 과 kernel image 앞 부분의 몇 MB 영역이 있다. kernel 부팅으로 넘어가기 전에 필요한 최소한의 작업인가보다


	/*
	 * Create the identity mapping.
	 */
	adrp	x0, idmap_pg_dir
	adrp	x3, __idmap_text_start		// __pa(__idmap_text_start)
        /* Skip ... */
	create_pgd_entry x0, x3, x5, x6
	mov	x5, x3				// __pa(__idmap_text_start)
	adr_l	x6, __idmap_text_end		// __pa(__idmap_text_end)
	create_block_map x0, x7, x3, x5, x6


	/*
	 * Map the kernel image (starting with PHYS_OFFSET).
	 */
	adrp	x0, swapper_pg_dir
	mov_q	x5, KIMAGE_VADDR + TEXT_OFFSET	// compile time __va(_text)
	add	x5, x5, x23			// add KASLR displacement
	create_pgd_entry x0, x5, x3, x6
	adrp	x6, _end			// runtime __pa(_end)
	adrp	x3, _text			// runtime __pa(_text)
	sub	x6, x6, x3			// _end - _text
	add	x6, x6, x5			// runtime __va(_end)
	create_block_map x0, x7, x3, x5, x6


idmap 영역은 idmap_text_start ~ idmap_text_end 영역으로 선언 되어있는 반면 kernel image 영역은 swapper_pg_dir로 되어있다. kernel image의 앞부분을 swapper 영역이라고 하는건가보다. 그런데 뭐에 쓰는 녀석인지는 아직 잘.... 뭐지?


6. __cpu_setup


함수 콜은 head.S에 있지만 구현은 mm/proc.S 에 있다. 이곳에선 kernel 시작 하기 전에 CPU의 system register값을 설정해준다.


ENTRY(__cpu_setup)
	tlbi	vmalle1				// Invalidate local TLB
	dsb	nsh

	mov	x0, #3 << 20
	msr	cpacr_el1, x0			// Enable FP/ASIMD
	mov	x0, #1 << 12			// Reset mdscr_el1 and disable
	msr	mdscr_el1, x0			// access to the DCC from EL0
	isb					// Unmask debug exceptions now,
	enable_dbg				// since this is per-cpu
	reset_pmuserenr_el0 x0			// Disable PMU access from EL0

	ldr	x5, =MAIR(0x00, MT_DEVICE_nGnRnE) | \
		     MAIR(0x04, MT_DEVICE_nGnRE) | \
		     MAIR(0x0c, MT_DEVICE_GRE) | \
		     MAIR(0x44, MT_NORMAL_NC) | \
		     MAIR(0xff, MT_NORMAL) | \
		     MAIR(0xbb, MT_NORMAL_WT)
	msr	mair_el1, x5

	adr	x5, crval
	ldp	w5, w6, [x5]
	mrs	x0, sctlr_el1
	bic	x0, x0, x5			// clear bits
	orr	x0, x0, x6			// set bits

	ldr	x10, =TCR_TxSZ(VA_BITS) | TCR_CACHE_FLAGS | TCR_SMP_FLAGS | \
			TCR_TG_FLAGS | TCR_ASID16 | TCR_TBI0
	tcr_set_idmap_t0sz	x10, x9

	mrs	x9, ID_AA64MMFR0_EL1
	bfi	x10, x9, #32, #3
#ifdef CONFIG_ARM64_HW_AFDBM
	mrs	x9, ID_AA64MMFR1_EL1
	and	x9, x9, #0xf
	cbz	x9, 2f
	cmp	x9, #2
	b.lt	1f
	orr	x10, x10, #TCR_HD		// hardware Dirty flag update
1:	orr	x10, x10, #TCR_HA		// hardware Access flag update
2:
#endif	/* CONFIG_ARM64_HW_AFDBM */
	msr	tcr_el1, x10
	ret					// return to head.S
ENDPROC(__cpu_setup)


코드가 길어서 주석은 다 뺏다. 중요하다고 여겨지는 부분들만 체크해보자.


cpacr_el1의 값을 써주는 작업은 해당 CPU에 FP(Floating Point), ASIMD(Advanced Single Instruction Multiple Data) 작업을 허용하겠다는 작업이다. 컴퓨터 아키턱처 시간에 배운 용어들이 새록새록 떠오른다. FP, ASIMD에 대한 세부 구현은 CPU 제조사들이 하는 거고 개발자들은 그냥 기능만 켜주면 되니까 편리하다.


sctlr_el1은 System Control Register다. 여기선 값을 다시 써주기 보단 값을 읽고 필요한 정보를 가져오는 작업만 하고 있다.


ID_AA64MMFRx_EL1는 현재 하드웨어의 메모리 모델 및 정보를 확인할 수 있는 플래그다. 값을 새로 입력하는 건 불가능하고 값을 읽어오는 것만 가능하다. 위 정보를 통해 PARange bits(커버 할 수 있는 최대 메모리의 크기)와 Access bit, Dirty bit를 Hardware로 업데이트 할 수 있는지에 대한 플래그 값을 읽어온다.


요약하면 커널 내부에서 동작 할 수 있도록 cpu의 system register 값을 입력하는 작업을 한다.


7. __primary_switched


head.S 의 마지막 작업. kernel 함수로 점프하기 전에 필요한 레지스터 값을 복구하는 일을 한다. 마지막 줄에 b start_kernel 작업으로 kernel 함수를 실행하게 된다.


728x90

구조설계서에 시스템에서 사용할 클래스 다이어그램을 그리다 보니 작성한 내용 그대로 실제로 시스템이 적용 할 수 있을지 의문이었다. 문서의 내용은 그럴듯 한데 여기에 안드로이드 시스템 특유의 feature들을 넣다보면 중간에 엉키게 되는 부분이 상당수 있을 것 같은 느낌이 드었다. 나중에 반복해서 고치는것 보단 직접 먼저 코드를 수정한 후 현실 가능성을 검증하고 옮기는 것이 나을것 같아 예전부터 가장 먼저 손대기로 했었던 DatabaseHelper 클래스를 리팩토링 했다.


수정전 DatabaseHelper.java 클래스는 이랬다.


그림 1. 초기 DatabaseHelper 클래스 다이어그램


DatabaseHelper 클래스는 이름 그래도 이기적인 총무의 데이터베이스를 관리할 수 있는 기능을 제공하는 클래스다. 그런데 여기에 Party, Pay, Person, Pay-Participation 테이블에 대해서 각각 CRUD 명령을 모두 넣다보니 위 클래스가 갖는 함수가 너무 많다. 클래스는 하나의 Responsibility만 가져야 하는데 위 경우에는 4개의 Responsibility를 가지게 됐으며 이런 경우 한 영역의 수정이 다른 영역의 수정에 영향을 미치게 된다. 객체지향적 관점에서 부적합한 설계 방법론이다. 위 클래스가 갖고 있는 Responsibility를 최소화하기 위해 총 4개의 subclass로 분리했다.


1. 역할에 맞춰서 클래스를 분리한다


그림 2. 1차 수정 버전


DatabaseHelper 클래스를 abstract 클래스로 두고 Table 별로 서브클래스를 두었다. 이렇게 하니 각각이 기능성으로 분류돼 각 클래스 별로 하나의 기능만 존재하게 됐다. 그런데 위 디자인을 적용해서 설치해보니 실행중 에러가 발생한다. 테이블이 생성되지 않았다는 에러다.


테이블을 생성하는 작업은 onCreate() 함수 안에서 담당한다. 그림 1에선 onCreate 함수 내에 모든 테이블을 생성하도록 했고, 그림 2에서는 각 클래스내에 존재하는 onCreate 함수에 테이블을 생성하도록 했다. 그런데 이렇게하니 문제가 발생했다. onCreate 함수는 애플리케이션내에서 데이터 베이스 함수를 열 때(getWritableDatabase 함수를 실행할 때) 딱 한번만 실행 된다. 각각이 현재 분리돼 있지만 시스템 내에서 실행되는 경우는 딱 한번 뿐이다. 그래서 PartyDatabseHelper에서 먼저 onCreate 함수를 실행 했다면 나머지 클래스에선 모두 생략된다.


위 문제는 시스템을 시작할 때 테이블을 생성하는 작업을 실행하도록 해 간단히 해결 할 수 있다. 그런데 테이블을 업그레이드 해야하는 경우는 문제가 생긴다. 업그레이드 명령인 경우 현재 데이터베이스 버전 번호와 최신 데이터베이스 버전 정보가 필요한데 이런 경우에는 기존 onUpgrade 루틴을 벗어나서 버전 정보를 읽어오는 것이 불가능하며 설사 가능하더라도 기존에 있는 기능을 중복해서 만드는것도 바람직한 코딩이 아니다.


마침 다른 블로그에 여러개의 테이블을 관리하는 코드가 있어 그 코드를 참조해서 리팩토링 했다.


2. 기존 create/upgrade 루틴과 호환되도록 한다.


그림 3. 2차 수정 버전


Party 데이터 베이스 관리 클래스가 DatabaseHelper 클래스로의 서브 클래스였는데 이제는 모두 별도의 클래스가 됐고 onCreate/onUpgrade 함수 대신 createTable(), upgradeTable()이 생겼다. 이 함수들은 static으로 선언해 외부에서도 불러줄 수 있도록 했다. DatabaseHelper에서는 기존대로 onCreate 루틴을 타는데 이때 각 서브클래스에서 선언된 createTable를 불러주도록 했다. onUpgrade 일때는 각 서브클래스에서 선언된 upgradeTable을 부른다. 


    @Override
    public void onCreate(SQLiteDatabase db) {
        PartyDatabaseManager.createTable(db);
        PayDatabaseManager.createTable(db);
        PersonDatabaseManager.createTable(db);
        PayParticipateDatabaseManager.createTable(db);

        Log.d(LOGTAG, "Create table is called");
    }

    @Override
    public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
        PartyDatabaseManager.upgradeTable(db, oldVersion, newVersion);
        PayDatabaseManager.upgradeTable(db, oldVersion, newVersion);
        PersonDatabaseManager.upgradeTable(db, oldVersion, newVersion);
        PayParticipateDatabaseManager.upgradeTable(db, oldVersion,newVersion);

        onCreate(db);
    }


기존 생성/업데이트 루틴을 타게 됐으니 앞서 말한 문제점이 깔끔하게 해결 된 것 같지만 이 코드도 문제점이 없는건 아니다. 만약 시스템에서 관리하는 테이블이 추가된다면 DatabaseHelper 클래스에 함수의 테이블 생성 작업과 업그레이드 작업을 추가해야 한다. 그런데 DatabaseHelper가 시스템 테이블의 구체적인 정보까지 알고 있는 것은 바람직하지 않다. DatabaseHelper는 각 테이블에게 추가 또는 업데이트를 명령할 뿐이지 구체적으로 어떤 테이블이 있는지는 알 필요가 없다.


차라리 외부에서 DatabaseHelper가 관리 해야하는 테이블을 등록하고 생성/업데이트 할 때 등록된 테이블만 관리하는 방법이 깔끔한 것 같다. 그래서 다음과 같이 변경했다.


3. DatabaseHelper에는 세부적인 내용을 넣지 않는다.


그림 4. 3차 수정 버전


 
// startActivity.java 내에 관리할 테이블 정보를 등록하는 코드.
        DatabaseHelper databaseHelper = new DatabaseHelper(this);

        databaseHelper.registerTableManager(new PartyTableManager());
        databaseHelper.registerTableManager(new PayJoinTableManager());
        databaseHelper.registerTableManager(new PayTableManager());
        databaseHelper.registerTableManager(new PersonTableManager());
 
// DatabaseHelper 클래스는 등록된 테이블 별로 create/update 함수를 부른다
    @Override
    public void onCreate(SQLiteDatabase db) {
        for (TableManager tableManager : tableManagerList)
            tableManager.createTable(db);
    }

    @Override
    public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
        for (TableManager tableManager : tableManagerList)
            tableManager.upgradeTable(db, oldVersion, newVersion);

        onCreate(db);
    }


TableManager라는 인터페이스를 둬 DatabaseHelper가 각 클래스의 세부 내용을 모르더라도 함수를 호출 할 수 있도록 했고 각각의 Table을 관리하는 클래스는 TableManager 인터페이스를 구현하도록 했다. DatabaeHelper 생성시 관리할 테이블을 등록해 안에 세부적으로 구현하지 않고 생성/업그레이드 작업을 수행할 수 있도록 했다. 관리할 테이블이 추가되면 startActivity에 테이블을 추가로 등록하면 되고 테이블을 삭제할 경우에는 등록하는 부분의 테이블을 지우면 된다. 이전보다 훨씬 쉽게 변경이 가능한 코드가 됐다.

728x90

최근에 ARM 64bit 리눅스의 초기 부팅 어셈블리 코드(arch/arm64/kernel/head.S)를 분석할 일이 있었다. 학교 다닐 때 x86 어셈블리로 코딩을 해본적도 있고 예전에 ARM 32bit miniOS 초기 부분도 분석해본 경험이 있어서 금방 할 줄 알았는데... ldr 같은 기본적인 명령어도 오랜만에 보니 생소했고 ARM 64bit만의 고유한 레지스터가 있어 레퍼런스를 뒤적거리면서 찾게 되다 보니 생각보다 오랜 시간이 소요됐다. ARM32와 ARM64가 원래 겹치는 영역이 별로 없는건지 아니면 내 머릿속에 남아 있는게 별로 없어서 겹칠게 없어진건지.


고생한 만큼 쉽게 잊혀질 수 있기 때문에(응?) 포스트로 이번에 공부한 내용들을 짧게나마 정리해보려한다.


1. ENTRY(stext)


 
ENTRY(stext)
	bl	preserve_boot_args
	bl	el2_setup			// Drop to EL1, w0=cpu_boot_mode
	adrp	x23, __PHYS_OFFSET
	and	x23, x23, MIN_KIMG_ALIGN - 1	// KASLR offset, defaults to 0
	bl	set_cpu_boot_mode_flag
	bl	__create_page_tables
	/*
	 * The following calls CPU setup code, see arch/arm64/mm/proc.S for
	 * details.
	 * On return, the CPU will be ready for the MMU to be turned on and
	 * the TCR will have been set.
	 */
	bl	__cpu_setup			// initialise processor
	b	__primary_switch
ENDPROC(stext)


stext에 있는 영역은 커널 바이너리 이미지가 압축 해제된 후 PC값이 첫번째로 가리키는 영역이다. 아키텍처와 무관한 리눅스 코드를 실행하기 전에 ARM에서 필수적으로 해야하는 작업을 여기서 처리한다. 브랜치의 이름만으로도 대략 무슨일을 하는지 짐작 할 수 있다.


2. preserve_boot_args


preserve_boot_args:
	mov	x21, x0				// x21=FDT

	adr_l	x0, boot_args			// record the contents of
	stp	x21, x1, [x0]			// x0 .. x3 at kernel entry
	stp	x2, x3, [x0, #16]

	dmb	sy				// needed before dc ivac with
						// MMU off

	mov	x1, #0x20			// 4 x 8 bytes
	b	__inval_dcache_area		// tail call


Bootloader에서 kernel을 올려주면서 x0~x3에는 특정한 argument를 전달한다. 그런데 x0~x3 레지스터는 연산 할 때 자주 사용되는 레지스터이므로 값이 변경될 소지가 있다. 그래서 넘어 온 값들은 boot_args의 주소 영역에다가 값을 저장하도록 한다. 나중에 common 폴더에 있는 리눅스 초기 작업을 실행하기 전에 복구해야한다.


3. el2_setup


만약 리눅스가 EL2에서 시작이 됐다면 기존에 EL1에서 돌아가도록 짠 리눅스 코드가 실행되는데 문제가 없도록 미리 몇몇 작업을 처리할 필요가 있다. 구체적으로 무슨일을 하는지는 코드를 차근차근 따라가보자.


ENTRY(el2_setup)
	msr	SPsel, #1			// We want to use SP_EL{1,2}
	mrs	x0, CurrentEL
	cmp	x0, #CurrentEL_EL2
	b.eq	1f
	mrs	x0, sctlr_el1
CPU_BE(	orr	x0, x0, #(3 << 24)	)	// Set the EE and E0E bits for EL1
CPU_LE(	bic	x0, x0, #(3 << 24)	)	// Clear the EE and E0E bits for EL1
	msr	sctlr_el1, x0
	mov	w0, #BOOT_CPU_MODE_EL1		// This cpu booted in EL1
	isb
	ret

1:	mrs	x0, sctlr_el2
CPU_BE(	orr	x0, x0, #(1 << 25)	)	// Set the EE bit for EL2
CPU_LE(	bic	x0, x0, #(1 << 25)	)	// Clear the EE bit for EL2
	msr	sctlr_el2, x0

#ifdef CONFIG_ARM64_VHE
	/*
	 * Check for VHE being present. For the rest of the EL2 setup,
	 * x2 being non-zero indicates that we do have VHE, and that the
	 * kernel is intended to run at EL2.
	 */
	mrs	x2, id_aa64mmfr1_el1
	ubfx	x2, x2, #8, #4
#else
	mov	x2, xzr
#endif


mrs/msr 은 co-processor의 primary register 값을 읽고 쓰는데 사용하는 명령어다. 주로 p13,14,15 의 레지스터값을 읽는다. ARM 32bit 버전에서는 인자가 5개일 정도로 명령어가 복잡했는데 ARM64 들어오면서 각각에 특정 레지스터 값을 입력하는 것으로 바뀌었나보다. 그래선지 예전에 분석 할 때 보다는 쉬웠던 것 같다. 레지스터가 저장되는 방향은 꼭 기억하자. 오른쪽에 있는 레지스터 값이 왼쪽 레지스터에 저장된다!


상단부 ret 명령어 전까지는 리눅스가 어떤 모드에서 실행됐는지를 확인한다. EL2로 실행되면 1: 로 PC 값이 이동하고 그렇지 않으면 Endian bit를 세팅하고 넘어간다. 현재 EL1에서 실행되고 있는 것이 확실하므로 EL1에서 사용할 수 있는 System Control Register에 현재 바이너리 형식이 Little Endian인지 Big Endian인지 세팅한다.


하단부는 VHE 유무를 세팅한다. VHE인 경우는 뭔가를 좀더 많이 하는데... 현재 VHE를 사용 할 수 있는 보드는 이세상에 없으므로 패쓰. 더 밑으로 내려가보자


	/* Hyp configuration. */
	mov	x0, #HCR_RW			// 64-bit EL1
	cbz	x2, set_hcr
	orr	x0, x0, #HCR_TGE		// Enable Host Extensions
	orr	x0, x0, #HCR_E2H
set_hcr:
	msr	hcr_el2, x0
	isb

	cbnz	x2, 1f
	mrs	x0, cnthctl_el2
	orr	x0, x0, #3			// Enable EL1 physical timers
	msr	cnthctl_el2, x0
1:
	msr	cntvoff_el2, xzr		// Clear virtual offset

#ifdef CONFIG_ARM_GIC_V3
	/* GICv3 system register access */
	mrs	x0, id_aa64pfr0_el1
	ubfx	x0, x0, #24, #4
	cmp	x0, #1
	b.ne	3f

	mrs_s	x0, SYS_ICC_SRE_EL2
	orr	x0, x0, #ICC_SRE_EL2_SRE	// Set ICC_SRE_EL2.SRE==1
	orr	x0, x0, #ICC_SRE_EL2_ENABLE	// Set ICC_SRE_EL2.Enable==1
	msr_s	SYS_ICC_SRE_EL2, x0
	isb					// Make sure SRE is now set
	mrs_s	x0, SYS_ICC_SRE_EL2		// Read SRE back,
	tbz	x0, #0, 3f			// and check that it sticks
	msr_s	SYS_ICH_HCR_EL2, xzr		// Reset ICC_HCR_EL2 to defaults


Hyp configuration 이라고 되어 있는데.. 사실 앞에서 VHE를 사용하는게 아니라면 하이퍼바이저를 사용하기 위한 작업은 아니라고 봐도 될 것 같다. x0 레지스터에 현재 EL1은 64bit로 돌아가고 있다고 세팅한 후 VHE 모드가 켜져 잇는지 확인하고(x2는 앞서 세팅 했었다) set_hcr로 넘어간다. 그리고 hcr_el2, Hypervisor Configuration Register의 값을 세팅한다. 그리고 그 아래엔 cnthctl_el2 레지스터의 값을 바꿔서 Non-secure EL1에서 physical counter, timer를 읽을 때 EL2로 trap이 되지 않도록 한다. 리눅스는 EL1으로 돌아가야 하니까 그런듯 하다. 그 아래 CONFIG_ARM_GIC_V3 지시어가 있는 부분도 동일하다. EL2로 트랩되지 않도록 처리하고 넘어가버린다. ICC_SRE_EL2는 GIC에 있는 레지스터다. 


	
install_el2_stub:
	/*
	 * When VHE is not in use, early init of EL2 and EL1 needs to be
	 * done here.
	 * When VHE _is_ in use, EL1 will not be used in the host and
	 * requires no configuration, and all non-hyp-specific EL2 setup
	 * will be done via the _EL1 system register aliases in __cpu_setup.
	 */
	/* sctlr_el1 */
	mov	x0, #0x0800			// Set/clear RES{1,0} bits
CPU_BE(	movk	x0, #0x33d0, lsl #16	)	// Set EE and E0E on BE systems
CPU_LE(	movk	x0, #0x30d0, lsl #16	)	// Clear EE and E0E on LE systems
	msr	sctlr_el1, x0

	/* Coprocessor traps. */
	mov	x0, #0x33ff
	msr	cptr_el2, x0			// Disable copro. traps to EL2

	/* SVE register access */
	mrs	x1, id_aa64pfr0_el1
	ubfx	x1, x1, #ID_AA64PFR0_SVE_SHIFT, #4
	cbz	x1, 7f

	bic	x0, x0, #CPTR_EL2_TZ		// Also disable SVE traps
	msr	cptr_el2, x0			// Disable copro. traps to EL2
	isb
	mov	x1, #ZCR_ELx_LEN_MASK		// SVE: Enable full vector
	msr_s	SYS_ZCR_EL2, x1			// length for EL1.

	/* Hypervisor stub */
7:	adr_l	x0, __hyp_stub_vectors
	msr	vbar_el2, x0

	/* spsr */
	mov	x0, #(PSR_F_BIT | PSR_I_BIT | PSR_A_BIT | PSR_D_BIT |\
		      PSR_MODE_EL1h)
	msr	spsr_el2, x0
	msr	elr_el2, lr
	mov	w0, #BOOT_CPU_MODE_EL2		// This CPU booted in EL2
	eret


install_el2_stub 로 오기 전에 debug 옵션을 켜주는 코드가 있는데 그 부분은 생략했다. 내가 분석한 코드랑은 조금 달랐다.. 버전상의 차이가 있는 건가. 아마 맥락은 비슷할 것이다. "PMU가 있는지 확인해보고 있으면 디버그를 키고 아니면 스킵한다." 이 정도만 확인하면 될 것 같다.


사실 요 브랜치에서 주의깊게 봐야하는 코드는 install_el2_stub 의 작업이다. ARM에서 가상화가 지원되면서 EL1에서 실행되는 리눅스가 초기화 작업중 EL2로 모드를 변경 할 때 어떻게 trap을 잡아낼 것인가가 주요 관심사였다. 당시 논문에서는 arm 초기화 부분에서 임시로 vector table을 두어 처리 한다고 했었는데 실제로도 vbar_el2(Vector base address register) 레지스터를 hyp_stub_vector로 세팅하도록 해서 처리하고 있다. vector table의 코드를 보면 부팅시에만 임시로 사용하는 테이블이어서 EL1에서 오는 trap 말고는 모두 invalid하게 처리해뒀다. 실제로 사용할 vector table을 세팅하기 전까지만 사용하는 코드라 그런것 같다.


그 아래는 spsr_el2, elr_e2 값을 세팅하는 작업이다. spsr은 Saved Program Status Register로 프로세스 실행중 Exception이 일어 날 때 현 프로세스의 상태(pstate)를 저장해둬 Exception 처리 후 원래 상태를 복구 할 때 사용하는 레지스터고 elr 레지스터는 exception 처리후 이동할 pc값을 저장하고 있다. 음 그런데 뭔가 이상하다. 지금 Exception이 일어날 상황이 아닌데 왜 spsr 값과 elr 값을 지정하는거지?


eret 명령어를 확인하면 왜 spsr, elr 값을 변경했는지 알 수 있다. eret는 exception 처리후 원래 상태로 돌아갈 때 사용하는 명령어다. eret가 실행되면 spsr값이 pstate 값으로 바뀌고 pc 값은 elr을 가리키게 된다. lr은 원래 el2_setup으로 넘어오기 전에 다음에 실행할 명령어를 세팅해뒀을 것이다. 즉 eret가 실행되면서 el2_setup 다음의 명령어로 이동하게 된다.


프로그램 상태는 spsr에 저장한 값으로 세팅한다. 오 그런데 spsr 값 마지막에 PSR_MODE_EL1h라고 세팅해뒀다. 즉 원래 프로그램이 EL1로 실행되고 있었다고 속이는 것이다. 이렇게 하면 eret가 실행되면 CPU는 EL1 모드로 변경돼 실행하게 된다. 즉 원래 EL1에서 실행됐던 리눅스의 루틴을 따르도록 모드를 변경하는 작업인 것이다. 대충 분석했으면 모드 변경 코드를 찾지 못했을 것 같은데. 이렇게 깜쪽같이 슬쩍 넣어놓다니. 원래 이런 방식으로 했던걸까?


정리하다보니 내용이 너무 길다. 나머지 내용은 다음 포스트에!

728x90

단순히 기능적 요구사항만 만족하는 소프트웨어가 아니라 변경 용이성, 성능, 사용 편의성과 같은 품질 요소와 객체 지향적인 관점을 고려해서 구현하는 것이 구조적으로 튼튼한 소프트웨어를 만드는 일이다. 이기적인 총무를 처음 만들때는 빠르게 출시 해보는것이 목적이어서 기능 구현과 UX 디자인 이외의 것들을 생각하지 못했다. 그러다보니 몇몇 기기에서는 정상적으로 작동하지 않고 있고 수정 하려고 해도 어디서 부터 손대야 할지 엄두가 나지 않는다. 이미 출시 된지 꽤 많은 시간이 흘러 조금 늦은 감이 있지만 앞으로 두달 간은 그동안 놓쳐 왔던 것들을 계획을 세워서 보완해 볼 생각이다.


1. 구조설계서 작성


가장 중요한 일임에도 불구하고 빠르게 개발하는데 급급해 문서 남기는 일을 생략했다. 그 결과 마지막으로 업데이트한지 4달이 된 지금, 블로그를 제외하곤 아무런 문서가 없으니 처음부터 끝까지 개발한 나도 어떤 기능적 요구사항이 있었는지 그리고 이건 왜 이렇게 만들었는지 헷갈릴 지경이다. 적어도 나를 위해서도 문서를 남겨 뒀어야 했었는데.


그리고 문서를 만드는 과정이 없어 그런지 품질 요구사항에 대해서 진지하게 고려해보지 못했다. 이기적인 총무에 새로운 기능을 제안해주시는 분들이 있는데 개발 할 때 확장성과 변경 용이성을 고려하지 않아서 어떻게 수정해야 할 지 엄두가 안난다. 클래스 다이어그램이라도 그려 뒀으면 구조 파악이 쉬웠을텐데 말이다.


하지만 문서를 이쁘게 만드는데 비중을 두진 않을 것이다. 구조설계서의 주요 독자는 나와 혹시 모를 이기적인 총무앱의 동업자이며 소프트웨어를 잘 만들기 위해 작성하는 문서지 다른 사람들을 설득하기 위해 작성하는 문서가 아니다. 그리고 다른 개발자가 오독하지 않는 선에서 다이어그램의 문법을 적당히 지키려고 한다. 띄어쓰기를 지키지 않아도 무슨 말을 하는지 이해할 수 있는 것처럼 다이어그램도 개발자가 읽고 이해 할 수 있는 수준이기만 하면 된다.


2. 객체 지향 프로그래밍


나름 객체 지향의 관점으로 구현했는데 SE 수업을 듣고나니 이렇게 많은 기법이 있을 줄이야. 왜 90년대 이후로 객체지향/객체지향/객체지향 하는지 이제 알 것 같았다. 학자들이 연구한 내용을 보니 용어도 다 기억하기 어려울 정도로 수많은 원칙/프로세스가 있었다. 물론 다들 비스무리한 것들을 말하는 것 같지만, 이런 비스무리한 것들을 구현 할 때는 고려하지 못했다.


모든 원칙을 적용하기는 어려울 것 같아 핵심적인 것들만 간추려서 적용해보려고 한다. 현재 개발된 코드에 GRASP와 SOLID 원칙을 적용해 객체를 다시 명세한 후 Design Pattern이 적용될 부분을 찾아 개발에 옮길 생각이다. 이것만으로도 충분 한 것 같다.


이것들에 대해서 짤막하게 소개하자면...


GRASP - 시스템에서의 클래스를 나누는 기법을 말한다. 즉 시스템 내에서 새로운 객체를 만들고 역할을 부여할 때 판단의 기준이 된다


SOLID - 객체 지향 프로그래밍의 가장 기본적인 5가지 원칙.

  • 클래스는 하나의 책임(Responsibility)만 가지고 있어야 하고(SRP)
  • 확장에는 열려 있어야 하고 수정에는 닫혀 있어야 하며(OCP)
  • 객체는 시스템의 정확도를 깨뜨리지 않으면서 상속 할 수 있어야 하고(LSP)
  • 여러개의 범용 인터페이스보다는 특정화된 인터페이스가 나으며(ISP)
  • 객체는 인터페이스에 의존해야지 구체화된 것에 의존하면 안된다(DIP) 가 있다.

Design Pattern - 객체 지향 기법을 이용해서 설계시 자주 발생하는 문제들의 해결 방법. 23가지가 존재한다.


3. Metric 측정


CCM, LCOM처럼 객체지향 프로그래밍에 다양한 지표가 있는 줄 몰랐다. 아마 이런 걸 알았더라면 막 짜지는 않았을 것 같은데 말이다. 그리고 내가 무의식적으로 얼마나 이런 지표를 잘 지키면서 구현했는지 궁금하기도 하다. 


2번 과정을 하기 전에 먼저 지표를 측정해보고 적용 후의 지표를 측정해볼 생각이다. 리팩토링 결과 코드의 품질을 수치적으로 비교할 수 있는 기록이 될 것 같다.

728x90
TAG 총무앱
  1. juju 2018.01.22 15:03  댓글주소  수정/삭제  댓글쓰기

    "혹시 모를 이기적인 총무앱의 동업자"를 위한 문서 ㅎㅎ 좋네요 ㅋㅋㅋ 심지어 Metric이라니! 세상에...Fully 이해 가능한 글을 여기서 보다니....!! ㅋㅋㅋㅋ 화이팅입니다.!

KVM - ARM

기술/가상화 2018. 1. 1. 11:41 Posted by 아는 개발자

아주아주 먼 옛날 가상화 기술이 핫 할때 Intel과 ARM 같은 제조사들은 자사의 칩에서 동작하는 가상화 소프트웨어의 성능을 높이고자 하드웨어단에서 여러 옵션을 추가 했다. 소프트웨어 개발자들은 제조사들이 제공하는 옵션을 활용해 하이퍼바이저를 만들었는데 KVM 또한 이때 만들어진 하이퍼바이저중 하나다. 좀더 구체적으로 말하면 QEMU같은 Type2 소프트웨어가 하드웨어의 가상화 확장 기능을 쉽게 사용 할 수 있도록 인터페이스의 역할을 하는 커널의 모듈이다.


그런데 제조사들은 가상화 기술의 성능을 높이기 위해 어떤 기능을 제공하고 있을까? 여러 OS를 동작하는 작업인 만큼 매우 오버헤드가 심할텐데 어떤 옵션이 있었기에 VMware로 리눅스가 쌩쌩 잘 돌아가는거지? 그리고 KVM은 하드웨어의 기능을 어떻게 반영 했을까? 기능을 반영하기 위한 특별한 구조적 의사 결정 과정은 없었을까? 파일과 코드의 양은 상당한데 말이다.


궁금증을 해소하고자 아키텍처단의 양대 산맥인 ARM과 x86에서 제공하는 가상화 기능을 살펴보고 이들의 특성이 어떻게 KVM에 적용됐는지를 아주 차근차근 분석해보려고 한다. 이번 포스트는 ARM에 대한 내용이다.


포스트를 작성하기 위해 KVM/ARM: The Design and Implementation of the Linux ARM Hypervisor 논문을 참조했다. 이 논문은 ARM에서 제공하는 가상화 기술이 나오게 된 계기와 작동 방식을 다루었고 위 특성을 반영한 KVM 코드를 리눅스 커널 메인라인에 반영하는 작업을 쭉 정리했다. 하드웨어 옵션을 쓰기 위한 여러 후보 구조를 도출하고 각각의 장단점을 분석한 후 선택하는 구조적 의사 결정 과정도 정리했는데 혹시 관심 있는 분들은 읽어보면 좋을 것 같다. ARM 아키텍처를 심도있게 이해하는데도 도움이 된다. 이번 포스트에서는 구조적 의사 결정의 결과만 다룰 예정이다.


꼼꼼하게 여러번 읽어 봤지만 글쓴이의 역량 부족으로 아직 온전히 이해하지 못했다. 잘 이해가 되지 못하는 부분은 두루뭉실 하게 쓴 것 같은데 이런 부분은 논문은 참조 해주셨으면 좋겠다. 친절하게 링크도 남긴다. 15달러는 못드린다.


1. CPU Virtualization, HYP Mode


HYP Mode는 SVC, USR 모드보다 더 높은 권한을 갖고 있는 CPU 모드중 하나다. SVC가 USR에서 동작하는 프로세스의 Trap을 받는 것처럼 HYP 모드는 SVC, USR의 Trap을 대신 받아 처리할 수 있으며 SVC, USR의 레지스터값들을 읽고 수정 할 수 있다. Exception Level 2로 불리기도 한다.


ARM에서 HYP 모드를 추가하게 된 배경은 Xen과 같은 standalone 하이퍼바이저 때문이다. Host OS 없이 동작하는 Type-1 하이퍼바이저의 경우에는 위에 동작하는 Guest OS보다 더 높은 권한을 쥐고 말썽을 부리는 경우 적극적으로 개입해 통제하는 역할을 해야한다. 그런데 CPU가 SVC, USR인 경우 이런 요구사항을 아키텍처적으로 설계하기가 애매하다. 하이퍼바이저의 권한을 살려 SVC 모드에 하이퍼바이저만 동작하게 하고 USR 모드에 Guest OS를 올리자니 SVC 모드에서 동작하는 걸 염두해두고 설계된 OS가 실행할 수 없게 된다. 그렇다고 Guest OS와 하이퍼바이저가 SVC 모드에 공존하게 하자니 Guest OS가 말썽 부리는 경우 대처 할 방법이 없다. 개념적으로는 훌륭한 구조였으나 SVC, USR 모드만으로는 설계가 불가능하다.


이런 문제를 해결하기위해 ARM은 SVC와 USR보다 권한이 더 높은 HYP 모드를 만들고 소프트웨어 개발자들로 하여금 하이퍼바이저를 HYP 모드에 넣도록 했다. 이것 덕분에 교통 정리가 끝났다. 이제 하이퍼바이저가 Guest OS에 제대로 통제권을 쥘 수 있게 되고 Guest OS는 SVC모드에서 동작하는 코드를 그대로 실행 할 수 있게 됐다.


KVM처럼 Type-2 하이퍼바이저는 좀 애매하다. Host OS의 모듈의 형태로 동작하기 때문에 일부 기능을 Host API에서 가져와야하는데 만약 Host가 EL1으로 동작하고 있다면 KVM또한 EL1에서 동작하고 있어야 한다. 그런데 Guest 보다 더 높은 권한을 가지려면 EL2에서 동작하기도 해야한다. 참 골치가 아픈 상황이다.


KVM은 두 가지 요구 사항을 반영해 구조를 Lowvisor/Highvisor로 두가지로 분리했다. Lowvisor는 HYP Mode에서 동작하는 Hypervisor이며 VM의 instruction을 trap하고 이를 Highvisor에게 전달한다. Highvisor는 Kernel Mode에서 동작하며 Host에 있는 커널 코드를 사용해 VM 운영중 필요한 기능을 제공한다.


Lowvisor에서 동작하는 코드는 Highvisor에 비하면 매우 적다. EL2 벡터테이블과 World Switch 사용되는 코드 일부 정도다. 코드의 양만 보면 Type1에 비하면 활용도는 적은것 같다.



그림 1. ARM/KVM 구조도. 논문 내용을 참조해서 그렸다.


논문에선 위와 같은 두개의 구조로(Highvisor/Lowvisor) 만들기 전에 아예 커널을 HYP Mode에서 부팅하는 작업도 고려했었다. Mode 변경으로 인한 cost를 줄일 수 있는 방법인데 왜 위 구조를 채택하지 않았는지는 논문을 참조해보길. 생각보다 간단한 이유였다.


* ARM은 하이퍼바이저를 넣으라고 HYP 모드를 설계 했는데 정작 퀄컴이나 삼성같은 칩 제조사들은 여기에 하드웨어 기능을 추가/보완 할 수 있는 소프트웨어 모듈을 넣고 있다. 역시 인생은 의도대로 흘러가지 않는 것 같다


2. Memory Virtualization


ARM은 Stage-2 Translation이란 기능을 제공했다. 간단히 말해서 Virtual Machine 내에서 도출한 물리 메모리 주소(IPA: Intermediate Physical Address)를 하드웨어 물리 주소(PA: Physical Memory)로 변환 할 수 있는 기능을 하드웨어 단에서 제공한다. 위 기능을 사용하기 전에는 IPA를 매번 소프트웨어적으로 물리 주소를 찾아가야 했고 Fault가 발생하면 처음부터 찾아야해 성능이 저하될 수 있는 문제가 있었다. 그런데 메모리 가상화 기능이 추가되면서 IPA 주소를 MMU에서 PA로 변환해줄 수 있게 됐다. KVM은 Host와 VM 간의 World Switch가 일어날 때 Stage-2 Translation 기능을 On/Off만 하면 된다. MMU는 Stage-2 Table의 register 주소 값을 참조해 변형한다.


그림 2. ARM/KVM 메모리 translation 작업.


3. Interrupt Virtualization


Interrupt Virtualization을 도입 하기 전에는 GIC의 CPU Interface를 VM과 공유하기 때문에 하드웨어에서 전달하는 Interrupt를 어디서 trap해 처리할 것인지가 주요한 이슈였다. Kernel 모드에서 모두 trap 한다면 효율적이나 Hypervisor가 하드웨어 통제권을 갖지 못하는 문제가 있고 HYP 모드에서 모두 처리한다면 매번 Interrupt를 처리하기 위해 HYP 모드로 변경해야 하는 오버헤드가 발생한다. 


이 문제를 해결하고자 ARM에서는 CPU가 VM과 통신 할 수 있는 Virtual CPU Interface를 제공한다. 위 인터페이스를 활용해 CPU는 별다른 Mode 변경 없이 interrupt를 Kernel Mode에서 동작하는 VM에 전달 할 수 있게 된다. Hypervisor는 더이상 인터럽트를 대신 받고 에뮬레이션 해주지 않아돼 성능적으로 우수하며 또한 VM이 VGIC과 바로 연결돼 있기 떄문에 여전히 통제권을 가지고 있는 것으로 볼 수 있다. VM과 Host가 분리되어 있기 때문에 따로 운영이 가능하며 초기 VM을 생성할 때 VGIC과 잘 연결만 시켜주면 된다.


그림 3. VGIC interface


단 CPU간의 interrupt인 경우(Inter Processor Interrupt, IPI) Distributor를 거쳐야 하는에 이부분은 여전히 Host와 공유하는 영역이라서 Hypervisor의 통제를 받는다. KVM은 Highvisor에 Virtual Distributor를 두어서 받은 interrupt를 에뮬레이션해 전달한다.


4. Timer Virtualization


OS에서 스케줄링 할 때 주기적으로 프로세스가 CPU에서 동작 할 수 있는 시간을 할당하고 사용한 시간을 확인하게 되는데 이때 사용하는 기기가 타이머다.. 스케줄링의 주기는 ms 단위로 짧기 때문에 Host는 타이머를 주기적으로 자주 이용하고 있으며 VM 또한 내부적으로 스케줄링을 위해 에뮬레이션된 타이머에 자주 접근하게 되는데 이를 Hypervisor에서 모두 처리해준다면 매번 Mode Change가 일어나서 시스템 전반 성능에 부담이 된다.


이에 ARM에서는 Virtual Timer라는 것을 두어서 VM이 주기적으로 Virtual Timer 값을 읽을 수 있도록 했다. VM은 trap 모드로 이동하지 않도 타이머 값을 읽을 수 있어 Mode Change의 부담을 덜 수 있게 됐다. 단 Timer가 expire 돼 CPU에 interrupt를 보내야 하는 경우에는 HYP 모드로 이동해서 하이퍼바이저를 통해 Virtual Interrupt를 보낸다. 이는 아키텍처적인 고려사항이라고 한다.



사진 출처


그림 3. http://slideplayer.com/slide/7615418/

728x90

'기술 > 가상화' 카테고리의 다른 글

VFIO, Passthrough  (0) 2018.06.30
QEMU 성능 문제 - 개론  (0) 2018.05.30
KVM - ARM  (0) 2018.01.01
QEMU와 KVM - 2  (0) 2017.11.11
QEMU와 KVM - 1  (0) 2017.11.01
6. XenStore, Xenbus  (0) 2017.01.22

말하기와 듣기 - 개발자로서 갖춰야할 기본 덕목

2017. 11. 18. 16:56 Posted by 아는 개발자

  대학교 1학년 이었던 것 같습니다. 학창시절 리눅스는 커녕 윈도우 커맨드 창 한번 두들겨 본 적이 없을 정도로 개발에 무관심 했던 저는 오로지 취업 안정성 하나만을 바라보고 컴퓨터 공학부에 입학 했습니다. 전공 선택의 목적이 컴퓨터에 대한 흥미나 학문의 대한 열망 보단 먹고 사는 문제를 해결하는 것이었고 당시 신문과 뉴스에선 대기업 개발자들의 희망 퇴직, 사오정 같은 고용불안정에 대한 기사를 쏟아냈기 때문에 자연스럽게 저의 학부 시절의 목표는 '뛰어난 개발자가 되어 이 직업으로 오래오래 먹고 사는 것'이 됐습니다. 다소 엉뚱하고 현실적인 목표를 세우면서도 철학적인 질문이 들었습니다. 뛰어난 개발자'란 어떤 사람을 일컫는 것일까요?


 참 속시원하게 답하기 힘든 질문인 것 같습니다. 도대체 어떤 기준으로 개발자들의 뛰어남을 측정 할 수 있을까요? 개발자 랭크를 매기는 사이트가 따로 있는것도 아닌데 말이죠. 이 질문에 대한 답변에 앞서 다른 직업 영역에서 정한 뛰어남의 기준을 생각해봅시다. 호날두와 메시 같은 축구선수들은 매우 객관적인 수치로 평가 받습니다. 공격수는 넣은 골의 수로, 공수를 조율하는 미드필더는 볼 점유율로, 수비수와 골키퍼는 실점 수로 판단 받습니다. 경기가 끝날 때마다 해설위원들이 선수별 개인 평점도 매겨줍니다. 해설 위원들 마다 선호하는 스타일이 있지만 대체로 공통된 의견으로 수렴됩니다. 의사와 변호사는 평점과 같은 객관적인 수치는 없지만 평가 척도는 명확합니다. 의사의 본 업무는 환자의 건강을 보살피고 약과 도구를 통해 병을 치료하는 일이고 변호사는 의뢰인의 법률적인 고민을 해결하는 일을 합니다. 물론 훌륭한 성품까지 지니면 더 좋겠지만 본업을 잘하는것 만으로도 뛰어난 의사 또는 변호사라고 평가하는 것에는 별다른 이견이 없을 것 같습니다. 


사진 1. 축덕들의 유명한 떡밥인 메시vs호날두 입니다. 스탯 상으론 메시가 좀더 앞서는것 같네요(2015년기준)


  모든 직업 마다 자신이 현재 하고 있는 일을 잘하는 사람을 뛰어난 사람을 평가 할 수 있는 것 같습니다. 개발자도 동일한 기준을 적용해 봅시다. 개발자는 개발을 하는 사람이니 개발을 잘하는 사람이 뛰어나다고 할 수 있겠지요?. 그리고 개발을 잘한다는 것의 기준은 다양한 기술 지식을 겸비하며 다른 사람들이 해결하지 못한 기술적인 어려움들을 척척 풀어내는 사람을 뛰어난 개발자라고 명할 수 있을 것 같습니다. 학부시절엔 이 기준에 맞춰 뛰어남을 평가 했던 것 같습니다. MacOS와 비교했을 때 윈도우가 얼마나 볼품없는 운영체제인지를 설명하던 친구가 새삼 대단해 보였고(지금 생각해보면 터무니없는 근거들 이었습니다) 학점은 바닥이었지만 알고리즘 대회에서 우수한 성적을 거두고 목에 힘주고 다니는 친구들이 부러웠습니다. 이런 친구들이 장차 뛰어난 개발자가 될 것이라고 생각했습니다.


 그런데 막상 실무에 들어가면 개발을 잘한다는 것의 기준이 매우 모호해집니다. 의사와 변호사와 다르게 개발자는 개인의 역량이 미치는 크기가 다소 제한적이기 때문입니다. 물론 혼자 일하는 경우나 소수 팀에선(2-3명) 영향력이 크겠으나 중규모 팀(5명 이상)에서 부터는 미미해집니다. 이유는 간단합니다. 혼자서 모든 일을 할 수 없기 때문입니다. 팀원의 수가 늘어나면서 프로젝트의 스케일은 커지게 되고 필요한 도메인 지식은 깊고 넓어집니다. 인간의 뇌가 아무리 무한한 능력을 지니고 있다곤 하지만 이 모든걸 혼자 습득하고 기술적 난제들을 해결하긴 쉽지 않습니다.


  실무에서 맞서게 되는 기술적인 문제는 교과서의 범위내에서 출제되는 수능 시험 문제와 다릅니다. 그쪽 방면의 지식이 없어 어쩌면 쉽게 풀릴 수 있는 문제를 며칠 째 골골 싸매고 있을 수도 있고 쉽게 풀릴 것이라고 생각 했던 것이 전혀 예상하지 못했던 에러로 당황하게 될 수도 있습니다. 리눅스 오디오 드라이버를 개발하는 팀을 예로 들어 봅시다. 리눅스 오디오 드라이버를 개발하기 위해선 일단 오디오에 대해서 정확하게 알고 있는 개발자가 필요합니다. 개발하려는 드라이버가 리눅스 타입이니 리눅스에도 정통한 사람이면 좋겠지요? 하지만 여기서 끝이 아닙니다. 리눅스에서 진동수를 관리하는 메커니즘과 오디오 정보의 전달 방식에 대한 것도 빠삭해야 합니다. 이것 저것 다 고려하니 인터럽트도 알아야하고 DMA도 알아야 하게 됐습니다. 리눅스와 오디오만 알면 될 줄 알았는데 예상외로 공부 할 것이 많습니다. 혼자 하기엔 벅찬 양입니다


사진 2. 마크 저커버그 혼자선 페이스북의 모든 기능을 만들 순 없었을 것입니다.


 결국 개발할때는 내가 모르는 것을 아는 사람과 같이 일해야 합니다. 이러면서 대두되는 것이 개발과는 전혀 무관 할 것 같았던 말하기와 듣는 능력입니다. 우리의 약점이기도 하지요? 실무에서 개발하게 되면 다른 사람이 물어보는 것을 설명해주기도 하고 내가 어떤 것을 모르는 것을 질문하기도 해야 합니다. 그런데 참 쉬워보이는게 예상외로 무척 어렵습니다. 내 머릿속에선 이미 완벽한 메커니즘이 잡혀있는데 이걸 옆에 있는 사람에게 설명하자니 뭐 부터 말해야 할지 감이 오지 않습니다. 들을 때도 속이 타긴 마찬가지입니다. 동료는 전문적인 용어를 쏟아내면서 말하는데 내겐 생소한 단어들입니다. 5분 넘게 대화했는데 머릿속에 남는건 없습니다. 이해가 되지 않아 다시 찾아갑니다. 이제 슬슬 짜증나는 티를 내기 시작합니다. 끝내 이해는 했지만 자존심이 상합니다.


사진 3. 사실 말하고 듣는건 모두 초등학교때 배웠던 내용입니다


  '말 할때는 청자의 입장을 고려해서 말하고 들을 때는 상대방의 말을 끝까지 집중해서 들어야 한다'고 배웠습니다만 개발팀에선 잘 이뤄지지 않고 있습니다. 개발자 특유의 아집 때문인지 남들이 모르는 지식을 전달이 아니라 뽐내고 싶어하기도 하고 상대방의 발언 중에 잘못된 것이 있으면 발언자에게 망신을 주면서까지 잡아내기도 합니다. 망신을 준 사람이 아무리 기술적 깊이가 뛰어난다고 해도 그 사람과는 아무도 같이 일하고 싶어하지 않게 됩니다. 이런 사람이 한사람이라도 있으면 팀내 전체 분위기가 흐트러집니다. 결과적으로 팀원들의 개인별 역량은 출중할지도 모르지만 팀의 역량은 그 합이 아니라 그 이하가 되버리고 말겁니다. 


 아직 어떤 사람을 뛰어난 개발자라고 말할 수 있을지는 모르겠습니다. 하지만 기술적 난제를 척척 해결하고 도메인 지식이 깊고 넓더라도 말하기와 듣는 능력이 부족한 사람을 뛰어난 개발자라고 할 순 없을 것 같습니다. 개발은 혼자 할 수 있는 일이 아닙니다. 운동경기만큼 팀플레이가 중요한 일입니다. 나 혼자 잘났다고 다른 팀원을 밟고 올라서려 한다면 개인플레이로 팀을 망치는 선수일 뿐입니다. 혼자선 즐거울 수 있지만 결코 팀에는 도움이 되지 못하는 개발자이지요. 뛰어난 개발자가 되기에 앞서 과연 나는 팀에 도움이 되는 개발자인지 고민해볼 필요가 있지 않을까요?


사진 1. 메시vs호날두 끝나지 않은 전쟁 http://news.donga.com/3/05/20150509/71146083/1


728x90

QEMU와 KVM - 2

기술/가상화 2017. 11. 11. 11:03 Posted by 아는 개발자

KVM(Kernel-based Virtual Machine)


그림1. KVM 공식 로고다. 펭귄이 던지고 있는 공은 VM을 의미하는 것 같다.


"KVM은 리눅스 커널을 하이퍼바이저로 변환하기 위한 가상화 인프라스트럭처의 하나이다"라고 위키 백과에선 설명하는데 이것만 가지곤 KVM의 제공하는 기능을 이해하긴 힘들다. KVM을 공부하기 전에 같이 사용되는 하이퍼바이저, QEMU에 대해 먼저 공부해보면 KVM의 사용 목적에 대해서 더 쉽게 이해할 수 있다. QEMU 포스트 읽어보기


Intel과 ARM같은 하드웨어 개발 회사들은 컴퓨터 내에서 가상화 기술을 지원하기 위한 장치들(Intel VT 또는 AMD-V )을 넣어뒀다. 이런 장치들은 가상화 기술의 고질적인 성능 저하 문제를 해결 하기 위해 만들어졌는데 이 장치들을 적절히 이용한 하이퍼바이저는 장치를 Emulation 해서 만든 방식보다 뛰어난 성능을 보였다(몇몇 기능은 Host OS와 거의 동일한 성능을 내기도 했다). 하지만 유저영역에서 동작하는 가상화 소프트웨어는 가상화 지원용 하드웨어를 직접 사용 할 수 없다. 하드웨어에 접근하는 일은 모두 커널을 거쳐야 하기 때문이다.


그림2. KVM 사용시 시스템 구조


KVM은 제조사에서 만든 가상화 하드웨어 기능을 범용성 있게 사용할 수 있도록 최적화하고 유저영역에서 동작하는 소프트웨어가 최적화한 기능을 쉽게 사용할 수 있도록 인터페이스를 제공한다. 인터페이스 접근은 간단하게 입출력 제어 함수(ioctl)를 호출해서 할수 있다. QEMU 소스 코드를 보면 실제로 kvm에 ioctl 함수를 보내는 코드들을 볼 수 있다.


지금까지는 하드웨어 제조사의 가상화 솔루션을 사용하기 위해 KVM이 사용되고 있다고 설명했는데 이것 말고도 다양한 기능을 제공하고 있다. 여기서 KVM이 제공하는 기능들 중 중요한 것 두가지만 살펴보자.


1. Virtual Machine 생성 및 관리


VM 생성 및 관리하는 작업을 하이퍼바이저가 아니라 KVM에서 관리 하기도 한다. 이런 경우 VM들이 커널내에서 관리되기 때문에 유저 영역에서 동작 할 때 보다 상대적으로 안정적이며 생성된 VM들은 커널 프로세스의 형태로 존재하기 때문에 유저프로세스로 존재할 때보다 전반적인 성능도 향상된다.


2. 장치 Emulation


CPU, Memory, Interrupt Handler처럼 VM 성능에 결정적인 장치들을 Emulation 해준다. 앞서 설명한 VM 생성 관리 작업처럼 이 장치들도 유저 영역에서 존재 할때보단 커널 영역에 있을 때 더 좋은 성능을 보이며 뿐만 아니라 하드웨어장치를 컨트롤 할 수 있는 함수를 호출 할 수 있는 장점이 있다. 여기서 Emulation 된 장치들은 제조사에서 추가한 가상화 솔루션을 지원하는 장치들이다.


이외에도 KVM이 제공하는 기능들을 적절하게 사용한다면 유저영역 하이퍼바이저에서 할 일은 "생성할 VM의 파라미터를 잘 받고 KVM 모듈의 함수를 잘 호출 하는 것"과 "KVM에서 제공하지 못한 장치들을 Emulation 하는 것"이다. 그래픽같은 경우 가상화 기술을 상용화 할 때 가장 크리티컬한 요소인데 아쉽게도 KVM에선 따로 지원하지 않는다. 이런 경우엔 하이퍼바이저에서 그래픽 관련 요소들을 처리 할 수 있는 솔루션을 제공해야한다.


사진 출처


- https://sort.veritas.com/public/documents/vie/7.3/linux/productguides/html/infoscale_virtualization/ch01s03.htm (그림 2)

728x90

'기술 > 가상화' 카테고리의 다른 글

QEMU 성능 문제 - 개론  (0) 2018.05.30
KVM - ARM  (0) 2018.01.01
QEMU와 KVM - 2  (0) 2017.11.11
QEMU와 KVM - 1  (0) 2017.11.01
6. XenStore, Xenbus  (0) 2017.01.22
5. 이벤트 채널 소개  (0) 2017.01.07

QEMU와 KVM - 1

기술/가상화 2017. 11. 1. 00:59 Posted by 아는 개발자

QEMU


'Quick Emulator' 풀네임 만으로는 와닿지 않지만 QEMU는 현재 PC에 설치된 운영체제와 다른 여러 개의 다른 운영체제를 구동 할 수 있는, 전가상화(Full Virtualization)를 지원하는 가상화 소프트웨어중 하나다. 전가상화 소프트웨어로는 VirtualBox나 VMware가 대중적으로 알려져있지만 가상화 기술 개발자들 사이에선 QEMU는 빼놓을 수 없는 가상화 소프트웨어중 하나다. 거의 가상화 기술 초창기를 주도 했던 소프트웨어이며 이때 만들어진 개념들도 상용화된 가상화 소프트웨어에서 사용되고 있다. 실제로 VirtualBox에선 상당부분을 QEMU 소스를 사용했다고 한다.


QEMU는 다른 가상화 소프트웨어와는 다르게 오픈소스로 개발됐다(물론 VirtualBox도 오픈소스지만 대부분 Oracle에서 개발했다) 가상화 기술이 한창 붐일때 부터 시작해서 현재 버전 2.10 까지 나왔다. 시간이 꽤 많이 흘렀지만 여전히 많은 개발자들이 참여하고 있는 만큼 질문 피드백이나 하드웨어 업데이트도 빠르고 포럼도 매년 열리고 있다.


VirtualBox나 VMware는 x86용 OS 구동만 지원하는 것에 비해 QEMU는 x86뿐만 아니라 다양한 하드웨어에서 구동 할 수 있도록 지원한다. ARM처럼 모바일 시장을 장악한 아키텍처 뿐만 아니라 지금은 거의 사장되기 싶은 alpha, i386같은 아키텍처도 qemu에선 돌려 볼 수 있으며 뿐만 아니라 동일한 아키텍처 내에서도 라즈베리파이나 삼성 smdk 보드 처럼 특정 하드웨어 머신을 설정할 수 있다. 상용화된 가상화 솔루션들은 비용적인 측면 때문에 지원하지 않은것 같은데 QEMU는 다양한 영역에서 활동하고 있는 개발자들이 참여하고 있기 때문에 인기가 없는 하드웨어들 까지도 서비스되는 것 같다. 물론 A/S는 본인의 몫이다



(QEMU를 이용해 라즈베리파이를 구동한 모습)


Guest OS의 수정없이도 잘 돌아가야 하기 때문에 QEMU내에선 Guest OS가 사용할 CPU, USB, PCI처럼 컴퓨터 본체에 있는 모든 디바이스들을 만든다(Type2 가상화 글을 참고). 이 방식은 모든 OS를 돌릴 수 있다는 장점이 있지만 막상 실행해보면 사용하기 어려울 정도로 매우 느리다.  사실 당연한 것이 Host의 관점에선 QEMU에서 만든 device들은 결국 process나 thread의 일종일 것이다. User process들은 Kernel process에 비해 우선 순위가 낮기 때문에 기존에 사용하던 운영체제와 동일하게 사용하는것이 거의 불가능 하다. 게다가 만약 Host 내에서 처리해야하는 일이 많다면 자연스레 QEMU 작업들은 뒤로 밀릴 수 밖에 없다.  


이런 고민을 소프트웨어 회사만 한 것이 아니다. 한창 가상화 기술이 뜨고 있을 무렵 Intel과 ARM 같은 아키텍처 하드웨어 회사도 어떻게 하면 하드웨어적으로 가상화를 지원 할 수 있을지를 연구를 했고 그에 맞춰 칩을 새롭게 디자인 했다. 리눅스에선 하드웨어 상에서 지원하는 가상화 feature들을 모두 KVM이란 모듈에 통합해서 관리하도록 만들었다.


QEMU는 workload가 높은 작업들을 적절히 KVM으로 돌려서 Host에 가까운 성능을 내도록 했다. 구조적으로는 이렇게 잡아서 사용했다.


(kvm - QEMU 개념도)


자세한 작동 방식은 KVM 포스팅에서!


출처


- https://azeria-labs.com/emulate-raspberry-pi-with-qemu/ - QEMU를 이용해 라즈베리파이를 구동한 모습, 사진

- http://www.admin-magazine.com/CloudAge/Articles/Virtualization-with-KVM - kvm QEMU 개념도, 사진

728x90

'기술 > 가상화' 카테고리의 다른 글

KVM - ARM  (0) 2018.01.01
QEMU와 KVM - 2  (0) 2017.11.11
QEMU와 KVM - 1  (0) 2017.11.01
6. XenStore, Xenbus  (0) 2017.01.22
5. 이벤트 채널 소개  (0) 2017.01.07
가상화기술의 대표적인 보안 문제  (0) 2016.12.10

데이터 구하고 가공하기에서 한 작업은 정치인들의 사진을 인터넷에서 크롤링 한 후 정확도 향상을 위해 사진에서 얼굴 부분만 추려낸 후 이 것을 Tensorflow서 사용 할 수 있는 파일 형식인 tfrecord로 변환하는 작업이었다. 이제는 변환된 데이터를 이용해서 학습을 시키는 일만 남았다. 이번 포스팅에서는 데이터를 학습시키는 과정들을 쭉 살펴보려고 한다.


1. Tensorflow 설치하기 (feat, 그래픽 카드 없이는 못쓴다)


Tensorflow는 내 데스크탑에 그래픽 카드가 있느냐 아니면 없느냐에 따라서 설치 명령어가 달라진다.


(tensorflow)$ pip install --upgrade tensorflow      # for Python 2.7
(tensorflow)$ pip3 install --upgrade tensorflow     # for Python 3.n
(tensorflow)$ pip install --upgrade tensorflow-gpu  # for Python 2.7 and GPU
(tensorflow)$ pip3 install --upgrade tensorflow-gpu # for Python 3.n and GPU


이 프로젝트를 처음 시작 했을 때 내 리눅스 PC는 그래픽 카드가 없었다. 애초에 이 PC는 오직 교육용으로만 사용하기 위해 게임을 할 수 있는 환경 자체를 차단 하려고 했었다. 그래서 맨 처음에 tensorflow를 설치 했을 때는 가장 위에 있는 CPU만 사용 하는 tensorflow 버전으로 설치했었다. CPU만 사용하니 한 번 학습 시키는데(뉴럴 네트워크에서 1회 학습을 하는 경우) 10초 정도 소요됐다. 10000회 정도 학습 시키려면 컴퓨터를 켜 두고 출근해야 하기도 했다.


그런데 회사 선배가 자기가 쓰던 그래픽 카드를 싸게 판다는 말을 듣고 컴퓨터에 설치해서 학습 시켜보니 퍼포먼스가 무려 20배 이상 차이가 났다. CPU만 사용했을 때 10초 정도 소요되던 것이 그래픽 카드를 사용하니까 0.5초면 가능했다. 10000회 학습 시키려고 컴퓨터를 켜두고 출근을 했었는데 이제는 1-2시간 정도면 끝났다. 그동안 내가 얼마나 많은 시간을 학습 과정에서 소비하고 있었던 것일까. 시간이 넉넉한 사람이면 모르겠지만 그래픽 카드의 유무에 따라서 작업의 퍼포먼스 차이가 크다. 초기부터 그래픽 카드를 사용했다면 불필요하게 시간을 허비하지 않았을텐데.


그래픽 카드를 사용했을 때 학습 속도, 1회 학습하는데 0.5초가 소요되고 있다

2. 학습 모델 선택하기


Tensorflow github에선 이미지를 분류 할 수 있는 다양한 학습 모델을 파이썬 코드로 짜뒀다. 개발자는 학습시키는 명령어에 사용할 학습 모델을 옵션으로 추가하면 간단히 학습 시킬 수 있다. 현재 tensorflow에서 만들어둔 학습 모델로는 이런 것들이 있다.


위 사진에 있는 학습 모델들은 이미 논문에 등재된 뉴럴 네트워크를 구글이 tensorflow에서 사용 할 수 있는 형태로 변환 한 것이다. 학습 모델의 이름을 인터넷에 검색해보면 논문 자료를 다운 받을 수 있으니 관심이 있으면 읽어보는 것도 좋다. 물론 내용이 결코 쉽진 않다. 나는 간단히만 읽어봤다.


이미지 인식 모델 중 가장 유명한 모델은 inception 시리즈이다. 이 모델은 현재 네번째 버전까지(inception_v4) 나왔고 imagenet 컨테스트에서 가장 우수한 학습 알고리즘으로 선정됐다. 하지만 서비스를 하려면 정확도 뿐만 아니라 학습하는데 소요되는 시간 및 유지 관리의 측면도 중요할 것 같아서(당시엔 그래픽 카드가 없었기 때문이기도 하다) 이 모든 요소를 절충 할 수 있는 학습 모델을 찾기로 했다. 이때 검증용으로 사용한 데이터는 tensorflow에서 제공하는 flower 데이터를 사용했다. inception_v1을 사용 했을 때는 학습 속도 자체는 나쁘지 않았으나 퍼포먼스가 좋지 않았고 inception_v3, inception_v4는 학습 속도가 너무 느렸다. 결과적으로 inception_v2가 학습시간 대비 가장 훌륭한 퍼포먼스를 보였다. 이미지 학습 모델은 inception_v2로 선정했다.


3. 학습 결과


정치인들 한 명당 대략 100장 정도 사진이 있었고 총 정치인의 수는 99명 이었다. 이 데이터로 inception_v2 모델로 10000회 정도 학습을 시켰는데 정확도가 70%정도 나왔다. 이 정도면 데모용으로 사용하기엔 적합한 수치였다. 기존 논문 대비 적은 양의 데이터임에도 불구하고 퍼포먼스가 좋은 이유는 아마 크롤링한 데이터에서 따로 얼굴 부분만 추려내고 동명 이인인 데이터를 필터링 하면서 순도 높은 데이터를 골라 냈기 때문인 것 같다. 많은 양의 데이터도 중요하지만 순도 높은 데이터도 중요하다는 사실을 확인해 볼 수 있었던 것 같다.

728x90

인공지능 개발시 훌륭한 학습 알고리즘도 중요하지만 양질의 데이터를 구하는 작업 또한 중요하다. 데이터는 되도록 많을 수록 그리고 정확할 수록 학습에 도움이 된다. 이번 포스팅에서는 폴리페이스에서 어떤 방식으로 데이터를 구했고 가공했는지를 정리해보려 한다.


1. 학습에 사용할 데이터 모으기


학습에 사용할 전혀 손때 묻지 않는 RAW 데이터를 구하는 것이 첫번째 과정이다. 폴리페이스의 서비스는 정치인 얼굴 닮은 정도를 측정하는 것이므로 이때 필요한 RAW 데이터는 인터넷 상에 떠도는 정치인의 얼굴이 담긴 사진이다. 가장 간단한 방법은 정치인의 이름으로 구글 이미지 검색을 해서 나오는 이미지들을 다운 받는 것이다. 그런데 한 명당 몇장이나 필요한가가 의문이었다. 딥마인드는 알파고를 학습시키려고 10만기보를 준비했다는데 사진은 10 ~ 20장으로는 택도 없을 것 같다.


구하기에 앞서 한 명당 얼마나 사진이 필요한지를 논문으로 확인해봤는데 논문에선 한명당 최소 500장은 사용하고 있었다고 한다. 인물 한명당 500장의 사진을 다운 받는것 자체가 매우 힘든 일인데 게다가 우리는 100명의 정치인 얼굴을 학습시키는 것을 목표로 했으니 총 합하면 50,000장을 다운 받아야 했다. 직접 검색하며 다운받기엔 너무 방대한 양이다. 개발을 시작하기도 전에 사진 데이터를 모으다가 시간이 다 갈것이다.


몇차례 우여곡절 끝에 친구가 이미지 다운 전용 크롤러를 만들어냈다. 처음에는 bing에서만 사용 할 수 있다느니 몇장 이상은 다운로드가 되지 않는다며 제약사항을 걸었는데 다행이 icrawler라는 패키지에서 여러 검색 포탈을 대상으로 이미지 다운을 할 수 있게 구현해뒀다. 우리는 구글 이미지 검색으로 설정해뒀고 좀더 확장해 파이썬에서 정치인의 명단이 담긴 엑셀파일을 읽고 자동으로 정치인의 사진을 다운 받을 수 있도록 구현했다.


로그메시지가 나오면서 자동으로 다운로드를 시작한다. 받는 양과 속도에 따라서 시간이 오래 걸린다


500장 넘게 받으려고 했는데 대부분의 정치인들의 사진이 100~150장 정도에서 끝났다. 구글에서 500장 이상을 결과로 내놓지 않아서 그런것 같다. 일단 한명당 100장 또는 그 이상을 기준으로 두고 데이터를 구했다.


2. 데이터 가공하기


앞서 말했듯디 크롤러로 받은 데이터는 구글 이미지 검색에서 정치인 이름을 검색했을 때 나온 사진 리스트이다. 문재인이나 안철수처럼 대중성 있는 정치인들은 정확도가 높아 100% 순도 높은 사진들만 나오는데, 연관 인물이 있거나 또는 동명이인으로 연예인이 있는 정치인들은 순도 높은 사진이 나오지 않았다. 예로 유승민을 크롤링한 결과 유승민의 자녀분인 유담씨의 사진이 적지 않게 나왔고 국민의당 김경진의원의 사진 데이터에는 개그맨 김경진의 사진이 상당수 나왔다(김경진 의원님은 좀더 노력하시길) 이 사진들을 모두 학습에 사용하면 정확도가 떨어지기에 따로 분류를 해야 한다. 이건 모두 수작업으로 했다. 100명이나 되다보니 생각보다 시간이 오래 걸렸다. 데이터를 구하면서 가장 힘들었던 일이다.


사람의 얼굴을 학습하는 작업인만큼 사진에서 불필요한 배경 없이 사람의 얼굴만 보여 진다면 학습 오차를 줄일 수 있을 것 같았다. 파이썬 패키지로 된 opencv2를 이용하면 쉽게 사진에서 사람의 얼굴을 추출 할 수 있었고 뿐만 아니라 여러 사람이 있는 사진의 경우에는 얼굴을 여러장 추출 할 수 있었다. 이 기능을 이용해서 정치인 얼굴만 뽑아내는 작업 뿐만 아니라 여러사람이 찍힌 사진인 raw 데이터를 분류 해낼 수 있었다. 여러 사람이 찍힌 raw 데이터는 불순한 데이터로 두고 학습에 사용하지 않았다. 


문재인 대통령 사진에서 얼굴만 추출해낸 작업


이 작업이 전체 학습에 얼마나 도움이 됐는지 수치상으로 확인 할 수는 없다. 하지만 한 명당 500장을 사용한 논문과 학습 결과가 별반 차이가 없는 것을 보면 얼굴 부분을 오려내는 작업이 적지 않은 도움이 됐던 것 같다. 비록 적지만 순도 높은 데이터를 이용해서 학습효과를 극대화 시킨 작업 이었던 것 같다.


3. Tensorflow용 데이터로 전환


얼굴만 추출한 순도높은 데이터를 Tensorflow 학습에 사용할 수 있는 학습데이터(TF Record)로 전환하는 과정이다. 물론 사진 이미지 파일을 그대로 학습에 사용 할 수 있는데 그대로 사용할 시 "터진다"는 로그가 많았고 어차피 Tensorflow에서 제공하는 이미지 학습 모델이 대부분 TF record 형식을 input으로 사용해서 우리도 변환해서 사용하기로 했다.


변환하는 파일은 tensorflow flower 예제 코드를 이용해서 쉽게 만들 수 있었다. 이 코드는 참 좋은것이 학습(training)에 사용할 데이터와 평가(validation)에 사용할 데이터를 자동으로 분리해서 만들어준다. 이렇게 만들어두면 학습할때도 사용 할 수 있고 평가 할 때도 사용 할 수 있다. 정말 편리한 툴인것 같다


train이라고 된 파일은 학습용, validation으로 된 파일은 평가용이다.


10,000장 정도 되는 데이터를 크게 학습과 평가용 데이터로 분류하고 여기서도 데이터를 크게 5개로 분류한다. 이건 어떤 원리인지는 잘 모르겠다. 코드상으로 한번 찾아봐야 할 것 같다.


labels.txt는 학습데이터로 전환된 정치인들의 목록이다. 데이터를 변환 할 때 사진 데이터가 모인 장소를 argument로 전달하는데 사진 데이터들은 모두 특정 폴더 안에 존재하고 이 폴더의 이름이 사진 데이터의 label이 된다. 사소한것 같은데 정말 편리하게 만들어뒀다.

728x90

폴리페이스 -기획

사이드 프로젝트/폴리페이스 2017. 9. 4. 21:13 Posted by 아는 개발자

회사안에서 계속 시대에 뒤쳐진 기술을 부여잡고 살다간 밥벌이를 잃게 될 것 같아서 마음이 맞는 친구와 공부를 해볼겸 새로운 프로젝트를 시작했다. 이름은 폴리페이스(Poliface)다. 


폴레페이스 이름은 정치인(Poliface)와 얼굴(Face)를 합친 말이다. 이름에서 대강 짐작할 수 있듯이 이 프로젝트는 '정치인 닮은 꼴'을 찾아주는 서비스다. 웹크롤러를 이용해 인터넷 상에 있는 정치인들의 얼굴을 모으고 tensorflow에 있는 이미지 학습 기능을 응용해 학습시킨 후 파이썬을 사용하는 서버 프레임워크를 입혀 사용자들이 웹 또는 모바일에서 자신과 닮은 정치인을 찾아 볼 수 있게 하는 것이 이번 프로젝트의 목표다.


(폴리페이스 구상도)


이것으로 특별히 수익을 낼 생각은 없다. 이번 프로젝트는 순수히 배우는 것이 목적이다. 무언가를 공부 할 때는 배운것을 직접 응용하는 것이 가장 학습 효과가 좋다. 이 프로젝트는 '어디가서 인공지능을 해봤다'고 말할 수 있을 정도의 결과물을 내는 것 일차적인 목표이며 세부 목표는 기본적인 인공지능 지식을 습득하고 최신 인공지능 API인 tensorflow를 사용해보며 회사에서는 사용하지 않는 웹 기술(크롤러, 서버 프레임워크)를 적극적으로 사용하는 것이다.


계획은 나름 거창하게 세웠는데 결과가 어떨지는 모르겠다. 대중적으로 사용되진 않더라도 포트폴리오로 내놓아도 손색이 없을 정도만 되었으면 좋겠다.


굳이 닮은 꼴 대상을 정치인으로 고른 이유는 신선함 때문이다. 이미 연예인 닮은꼴 프로그램은 충분히 많다. 우리까지 나서서 연예인 닮은 꼴을 만든다면 아무도 재미삼아라도 사용하지 않을 것 같다. 


어쩌면 정치인 닮은 꼴이 연예인 닮은 꼴 보다 훨씬 만족스러운 결과가 나올 것 같기도 하다. 연예인들은 일반인들과 생김새 차이가 많아(매우 잘생기고/매우 이쁘고/매우 개성이 있고) 나온 예측 결과가 일반인 얼굴과 괴리가 발생할 여지가 많은데 정치인들의 얼굴은 일반인들과 생김새가 비슷해서(다들 평범한 수준) 상대적으로 설득력 있는 결과가 나올 것 같은 예감이다. 물론 내가 싫어하는 정치인이 나와 닮았다면 거부감이 들겠지만.


추가로 정치인 기본 정보까지 볼 수 있는 기능을 넣으려고 한다. 기본적인 정보만 넣어놓고 세부 정보는 위키/나무위키 링크를 넣어서 연동할 계획이다. 정치에 관심이 없는 사람들이라도 자신과 닮은 정치인 만큼은 호기심이 생길 것 같다. 정치인이 어떤 이력을 지닌 인물인지, 무슨 의정활동을 했는지 알게 된다면 정치에 조금은 관심을 가지게 되지 않을까? 특별히 이러려고 만든 것은 아니지만 긍정적인 효과가 있을 것 같기도 하다. 물론 성공적으로 완성을 하는것이 먼저겠지만!

728x90

뉴럴네트워크(Neural Network)

기술/인공지능 2017. 8. 18. 20:45 Posted by 아는 개발자


숫자 인식 코드를 분석 할 때 이미지 사이즈의 크기는 28x28이라서 총 784개의 feature를 두어서 구할 수 있었습니다. 그런데 실제로 사용하는 이미지는 이것보다 훨씬 크기가 큽니다. 바탕화면 해상도는 1920x1020이고 여기에 색깔을 넣기 위해 3차원까지 넣어서 총 요소(Element)들의 개수는 5,875,200(1920*1020*3)개가 됩니다. 요소들의 개수만큼 feature로 놓고 학습을 시킨다면 매우 시간이 오래 걸리겠죠?


그래서 인공지능 과학자들은 다른 방법을 고안했습니다. 과학자들은 연구에서 어려움이 있을 때마다 주로 자연에서 해답을 얻곤 하는데요 인공지능 뇌과학자들은 인간의 뇌의 뉴런구조를 본따서 이 문제를 해결하기로 했습니다. 인간의 뇌가 생각하는 방식을 컴퓨터에 이식 할 수 있다면 이 문제도 쉽게 해결 할 수 있다고 본겁니다. 완전히 발상을 전환했지요?



아마 고등학교때 생물 수업을 들어보신 분은 뉴런 구조에 대해서 간단히 알고 계실 겁니다. 왼쪽 줄기로 된 부분들에서 외부의 전기 자극을 받고 이것의 종합 값이 새로운 전기 자극을 발생시킬 정도가 되는지 확인해서 자극을 전달하는 방식입니다. 이것을 좀더 수학적으로 표현하면 아래와 같습니다.




x1, x2, x3는 외부에서 온 자극의 세기를 말합니다. 그리고 g(x1, x2, x3)는 이 자극들을 어떠한 수식에 의해서 변환된 총합을 의미합니다. 최종적으로 H(x) 함수를 통해 외부의 세기로 전달할 자극의 세기를 결정합니다. 이것으로 어떤 세기로 전달할지가 결정 되는 것이지요. 여기서 g(x1, x2, x3) 이 식은 주로 직선 개별에 가중치 값을 둬서 w1*x1 + w2*x2 + w3*x3로 표현하곤 하는데요 반복적인 학습 작업을 통해서 적절한 w1, w2, w3의 값을 구하게 됩니다.


뉴런 하나 가지고 학습하고 미래를 예측하기엔 턱없이 부족합니다. 하지만 뉴런의 개수가 많다면 더 효율적으로 예측 할 수 있습니다. 실제로 뉴럴네트워크에서는 아래의 그림처럼 여러 개의 뉴런층을 나눠서 학습시킵니다.


neural network에 대한 이미지 검색결과


많은 양의 데이터와 뉴럴네트워크를 사용한 학습모델은 생각보다 '아주 잘' 작동합니다. Feature 개수가 늘어나서 도저히 학습시키기 어려워 보였던 것들도 뉴럴 네트워크를 이용해 금방 해결 할 수 있고 예측도 '생각보다 잘' 합니다. 정말 인간의 뇌를 복사한 것 같기도 합니다. 뉴럴네트워크 덕분에 이미지/음성 인식처럼 불가능해보였던 영역이 많이 발전했습니다. 특히 이미지인식은 알래스카 맬러뮤트랑 시베리언 허스키를 구분 할 수 있는 수준까지 왔다고 하네요


728x90

'기술 > 인공지능' 카테고리의 다른 글

Q-Learning  (0) 2018.08.19
파이썬으로 AI 개발할 때 유용한 패키지 모음  (0) 2018.08.07
뉴럴네트워크(Neural Network)  (0) 2017.08.18
오버피팅(Overfitting)  (0) 2017.08.10
숫자인식 코드 분석해보기  (2) 2017.08.03
Softmax와 Cross entropy  (0) 2017.08.03

오버피팅(Overfitting)

기술/인공지능 2017. 8. 10. 21:18 Posted by 아는 개발자

학습 능률을 높인다는 것은 비용함수(Cost function)의 값을 줄이는 것입니다. 하지만 학습에 사용하는 Feature(쎄타라고 생각하시면 됩니다)의 개수가 일정하면 Gradient Decent와 같은 최적화 작업을 통해 통해 최적의 해에 도달 했다고 해도 절대적인 비용은 어쩔 수가 없습니다.


최대한 학습 데이터를 따르는 직선을 만들었지만 그래도 비용은 어쩔수가 없네요.


그런데 우리가 직선이 아니라 볼록한 함수를 이용해서 학습한다면 어떨까요? 쎄타2를 추가하고 이때의 x는 제곱으로 본다고 합시다. 여러차례 최적화 작업을 거치면서 아래로 볼록한 함수가 나온다면 데이터의 흐름을 잘 따를 수 있을 것 같습니다. 실제로 그림으로 그려보봐도 그렇네요.


데이터 흐름을 더 잘 따라가는 것 같습니다.


실제로 계산해보면 비용함수의 값도 감소합니다. 이로써 Feature의 개수를 늘리면 학습효과가 더 증가 한다는 것을 알 수 있겠네요. 내친김에 극솟값 x축에 있는 학습 데이터도 캐치 할 수 있도록 차원을 더 올려봅시다. 이번엔 아예 다섯제곱까지 늘려보자구요.


이제는 학습에 사용된 모든 데이터들을 캐치 할 수 있게 됐습니다. 아마 비용함수의 값은 0이 나올것 같네요. 학습 성능은 가장 좋은 것으로 판단해도 될 것 같습니다만 X축에서 오른쪽으로 갈수록 값이 곤두박질 치고 있다는게 맘이 걸리긴 합니다.


만약 위 데이터의 원래 흐름이 아래로 블록한 그래프였다면 어땠을가요? 실제로 아래 그림의 노란색 데이터들이 들어온겁니다. 이런 경우 학습을 잘했다고 보기 어렵습니다. 차라리 2차원 블록한 그래프가 비용함수 값은 좀 있더라도 노란색 데이터가 들어 올 것이라고는 더 잘 예측 했을 것 같네요.


이처럼 비용함수의 값을 낮추기 위해 여러개의 Feature를 과하게 넣다보면 오히려 실제 데이터와 괴리되는 현상이 생깁니다. 분명 학습 데이터는 잘 맞추는데 실제로 보면 예측은 전혀 못하고 있는 것이지요. 이런 경우를 오버피팅(Overfitting)이라고 합니다. 학습 모델을 만들 때 위와 같은 경우를 방지하기 위해 비용함수의 수식에 Feature의 크기에 따른 변화값을 두기도 한답니다. 선형회귀에선 수식으로는 이렇게 표현해요!


파란색 원으로 그려둔 부분이 Overfitting을 방지하기 위한 값입니다. 일반적으로 쎄타의 값이 커질수록 Overfitting 발생 정도가 증가합니다. 그래서 이 값의 제곱 값을 더하면 전체 비용함수가 늘어나는 것으로 보고 학습 효과를 떨어뜨리게 되는 것이죠.


람다 값은 이때의 상수입니다. 오버핏 방지 연산에 얼만큼 비중을 둘 것인지를 결정합니다. 이 값이 커질수록 오버피팅이 하지 않게 되겠지만 낮아진다면 학습 데이터도 잘 맞추지 못하게 됩니다. 이 값이 적당할 때 가장 그럴듯한 예측을 하는 학습 모델을 만들 수 있게 됩니다.

728x90

숫자인식 코드 분석해보기

기술/인공지능 2017. 8. 3. 20:49 Posted by 아는 개발자

지난 포스팅에서 인공지능 이론중 가장 간단한 Linear Regression과 Logistic Regression에 대해서 설명했습니다. 이번 포스팅에선 tensorflow 튜토리얼에 있는 숫자 인식 코드를 차근차근 분석해가며 어떻게 우리가 배운 이론이 접목되었는지 공부해보는 시간을 가지려고 합니다.


먼저 숫자 인식 코드는 여기를 공식 튜토리얼 페이지는 여기를 클릭하면 볼 수 있습니다. 


튜토리얼 페이지를 클릭하시면 MNIST라는 단어를 보실 수 있을겁니다. 이건 컴퓨터 비전에 사용되는 데이터 셋을 말하는 용어니 너무 주의깊게 보시지 않아도 괜찮아요. 그냥 이런 이미지를 가지고 있다는 것만 기억하시면 됩니다!



위의 이미지들은 우리가 숫자를 직접 손으로 쓸 때의 이미지들입니다. 우리가 만든 숫자 인식 인공지능은 고딕으로된 숫자 이미지 뿐만 아니라 여러가지 폰트로 된 숫자도 인식해야 하니까 이렇게 만들어두면 학습에 도움이 되겠죠? 우리는 이런 이미지들을 잘 학습시켜서 우리가 어떤 이미지를 넣더라도 정확한 숫자를 출력하게 하고 싶습니다.


이미지를 학습 시키는 것에 앞서 이미지를 학습에 사용될 데이터로 전환하는 작업을 해야합니다. 아마 비전공자라 하더라도 컴퓨터 이미지들은 픽셀의 형태로 이뤄져 있다는 것을 아실겁니다. 수십에서 수만개의 촘촘한 점들에 숫자 값을 대입해서 이 위치에는 검은 색을 또는 다른 위치에는 빨간 색을 표현하는것이 컴퓨터가 이미지를 보여주는 방식입니다.


왼쪽 '1' 그림을 픽셀 단위로 표현 한 것입니다.

이렇게 표현하니 거대한 행렬이라고 볼 수 있을 것 같습니다. MNIST에 있는 모든 데이터는 28x28 단위를 따르고 있습니다. 784개의 Element들을 하나로 쭉 나열하면 각 이미지가 서로 독립적이게 만들어 줄 뿐만 아니라 보기에도 훨씬 편할 것 같네요(Flattening 한다고 합니다). 이렇게 변환한 행렬은 학습 데이터로 사용하기에 매우 편리합니다.


실제 코드 상에서는 아예 784 배열로 변환한 데이터 값 자체를 한번에 받습니다. 

 
# Train
for _ in range(1000):
   batch_xs, batch_ys = mnist.train.next_batch(100)
   sess.run(train_step, feed_dict={x: batch_xs, y_: batch_ys})


학습에 사용할 input data를 만들어뒀으니 이제 학습 모델을 만들어 봅시다. 숫자 인식은 분류(Classification)작업으로 볼 수 있습니다. 그래서 여기선 Logistic Regression을 사용해야 합니다. 784개의 Element들(숫자 이미지에 있는 모든 픽셀)을 보고 특성을 분석해 10개의 분류(0~9 까지 인식해야 합니다)로 만들어주는 인공지능을 만드는 것이 목표입니다. 먼저 Logistic Regression 에 수식을 다시 살펴봅시다.




여기서 우리는 z에 해당하는 수식을 만들어야 합니다. 전에는 쎄타0, 쎄타1 만 만들어서 간단히 했었죠? 그런데 이번에는 쎄타0, 쎄타1 뿐만 아니라 쎄타784 까지 만들어야 합니다. 학습에 사용하는 input 데이터의 element가 총 784개이기 때문이지요. 계산량이 무척 많아지겠지만 그래도 컴퓨터가 대신 해줄 것이니 너무 염려하지 않도록 합시다.


output data는 크기가 10인 배열로 둘겁니다. 결과값이 3이라면 (0, 0, 0, 1, 0, 0, 0, 0, 0, 0) 요렇게 쉽게 표시 할 수 있습니다. 물론 two's complement로 더 공간 효율적으로 할 수 있긴 하지만 그렇게는 안해요. 이렇게하면 벡터로 표시 할 때 훨씬 보기 편하거든요.


입력값(X)이 1x784 로 들어 온다면 우리는 출력값(Y)을 1x10으로 내야 스펙에 맞습니다. 그러면 입력값을 처리하는 행렬(W)의 크기는 784x10이 되어야 합니다. 수학 수식으로 표현해보면 Y(출력값) = X(입력값) * W(쎄타들의 모음) + B(바이어스, 일반 상수에 해당하는 값) 로 볼  수 있습니다.


실제 코드에서도 이렇게 표현 합니다.

 
# Create the model
x = tf.placeholder(tf.float32, [None, 784])
W = tf.Variable(tf.zeros([784, 10]))
b = tf.Variable(tf.zeros([10]))
# y = xW + b
y = tf.matmul(x, W) + b
  • x는 인풋 데이터 형식입니다. C언어로 생각하면 int, char을 설정해주는 것과 비슷해요.
  • W는 쎄타들의 모음입니다. 실제로는 Weight라고 불러요. Input이 784개이고 10개의 output이 있으니 총 7840개의 Weight가 존재합니다. 지금은 모두 0으로 세팅했는데 여러번 최적화 과정을 통해서 적절한 값을 찾아가게 될겁니다. 
  • b는 Bias 값입니다. 일반상수에 해당하는 값이에요
  • y를 matmul 명령어를 이용해 정의합니다. matmul은 벡터 값의 곱을 의미합니다. 결과적으로 y = xW + b 로 표현이 되겠네요.

학습 모델까지 훌륭하게 만들었습니다. 이제 Cost function을 구하고 최적화 작업만 거치면 됩니다. 이 작업은 softmax와 cross entropy로 한방에 해결 할 수 있습니다. softmax 포스팅에서 소개한 코드를 그대로 사용하겠습니다.

 
cross_entropy = tf.reduce_mean(
    tf.nn.softmax_cross_entropy_with_logits(labels=y_, logits=y))
train_step = tf.train.GradientDescentOptimizer(0.5).minimize(cross_entropy)


tf.reduce_mean은 argument의 평균을 구해주는 작업입니다. 이렇게 cost function 구하는 모델을 만들어 두고 바로 밑에 GradientDescentOptimizer 의 minimize argument안에 cross_entropy 모델까지 넣으면 Gradient Decent 방식으로 최적화 할 수 있는 모델 까지 만들어 집니다.


하지만 이렇게 그냥 둔다고 바로 알아서 학습을 하진 않습니다. 지금까지 우리가 한 것 학습을 위한 모델을 만든 것에 불과하니까요. 함수를 정의한 후에는 호출을 해야하는 것처럼 여기도 우리가 만든 학습 모델을 실행하는 코드가 필요합니다.

 
  # Train
for _ in range(1000):
  batch_xs, batch_ys = mnist.train.next_batch(100)
  sess.run(train_step, feed_dict={x: batch_xs, y_: batch_ys})

# Test trained model
correct_prediction = tf.equal(tf.argmax(y, 1), tf.argmax(y_, 1))
accuracy = tf.reduce_mean(tf.cast(correct_prediction, tf.float32))
print(sess.run(accuracy, feed_dict={x: mnist.test.images,
                                    y_: mnist.test.labels}))

주석으로 추천하건데 # Train 아래에 있는 부분이 학습하는 부분 인것 같지요? batch_xs, batch_ys는 학습에 쓰일 이미지 벡터 값과 실제 숫자 벡터 값을 가지고 있습니다. 그리고 바로 아래 sess.run 안의 feed_dict 안에 넣어놓지요. 첫번째 인자인 train_step은 우리가 최적화 작업까지 선언한 학습 모델입니다. 이 두 인자만 넣어주면 바로 tensorflow에 있는 함수들을 이용해서 학습이 진행됩니다.


바로 밑에의 코드는 학습한 데이터를 테스트해보는 작업이네요. correct_prediction은 예측한 값(y)과 실제 값(y_)이 동일한지를 보는 것이고 accuracy는 예측 한 값들에서 평균을 내는 작업입니다. 이것도 어떻게보면 검증하는 '모델'로 볼 수 있겠네요. 첫번째 인자로 예측 모델(accuracy)를 두고 두번째 인자에는 테스트할 이미지와(mnist.test.image)과 결과값(mnist.test.labels)을 대입해서 정확도를 측정 할 수 있습니다.


위 학습 모델로 정확도가 92% 정도 나옵니다. 생각보다 우수하죠? 그런데 딥러닝을 적용하면 정확도가 97%까지 상승한다고 합니다!


참고자료

  • https://www.tensorflow.org/get_started/mnist/beginners


728x90

'기술 > 인공지능' 카테고리의 다른 글

뉴럴네트워크(Neural Network)  (0) 2017.08.18
오버피팅(Overfitting)  (0) 2017.08.10
숫자인식 코드 분석해보기  (2) 2017.08.03
Softmax와 Cross entropy  (0) 2017.08.03
Logistic Regression (로지스틱 회귀)  (0) 2017.07.31
Linear Regression (선형회귀)  (0) 2017.07.28
  1. 이용원 2019.05.21 09:45  댓글주소  수정/삭제  댓글쓰기

    0000~9999까지 4단위의 숫자를 인식하고자
    합니다.
    이미지 취득은 어떤 파일형식도 가능합니다.
    같은 글씨체 이며 같은 폰트 입니다.
    다만 환경 여건상 이미지 상태가 양호하지
    않은 경우가 있습니다.
    머신런닝을 이용한 프로그램 개발 가능 하세요?
    01047041111 이 룡원

Softmax와 Cross entropy

기술/인공지능 2017. 8. 3. 20:36 Posted by 아는 개발자

학습시키는 데이터의 Feature가 3가지이고 이 데이터들을 총 3개의 분류로 나눈다고 해봅시다. 이때 우리는 하나의 feature에 대하여 총 3가지로 분류해줄 weight값이 필요합니다. 만약 데이터의 Feature들을 x1, x2, x3라고 표현하면 x1이 첫번째 분류, 두번째 분류 그리고 세번째 분류로 나눠 질 수 있도록 값을 조정하는 weight값이 필요하게됩니다. 그림으로 표현하면 더 알아보기 쉽습니다.



x1, x2, x3의 집합을 X, Wi,j값의 집합을 W, bi의 집합을 B로 표현하면 위 식은 S = Wx + B로 표현 할 수 있겠네요. 그리고 이 값이 가장 큰 것으로 분류되게 됩니다.


하지만 학습을 하려면 내가 낸 결과물이 얼마나 비슷한지도 알아야합니다. 내가 고른 값이 옳다 하더라도 50% 확률로 맞췄는지, 70% 확률로 맞췄는지에 따라서 얼만큼 잘 맞추고 있는지 확인 할 수 있으니까요. 이때 출력된 데이터 값(S라고 하겠습니다)을 0~1 사이의 값으로 표현해주는 방법이 softmax입니다. 식으로 표현하면 이렇습니다.


추측하는 분류의 S exponential 값에서 전체 S exponential 합을 나눠주는 방식입니다. 만약 S값이 2, 1, 0으로 나왔다면 softmax를 거친다면 0.7, 0.2, 0.1로 변형됩니다. 그래도 2가 가장 큰 것은 변함이 없죠? 단지 70%의 확률로 맞췄다고 보게 되는거지요. 학습데이터에 얼마나 가까워졌는지 정도를 보여주는 방법입니다. 이렇게 데이터를 보는 방식을 바꾸면 나중에 Weight값을 조정할 때 큰 도움이 됩니다.



이 값을 토대로 비용함수를 구하는 방식이 Cross entropy입니다. 위키 백과에 있는 세부 이론까지 세세히 들어가면 너무 복잡하니 간단히 식과 여기에 무엇을 대입해야 할지만 알아보겠습니다.


위 식을 보면 p(x)와 q(x)가 있는데요 여기서 우리는 p(x)에 실제 분류 값을 쓰고 q(x)는 softmax 결과값(Y)를 대입하게 됩니다. 요렇게요



좀더 정확하게 맞출 수록 H(p,q)의 값이 감소하게 됩니다. 즉 위 값, 비용 총합을 최소화하는 것이 위 작업의 목표지요. 이것도 Gradient Descent 방식으로 최적화가 이뤄집니다. 하지만 이건 생략하고 느낌만 가져가죠. 나머지는 tensorflow해서 해줄거니까요. 그냥 이렇게 작동하는구나 정도만 알고 갑시다.


tensorflow에서는 softmax_cross_entropy_with_logits 함수로 한방에 끝내버립니다.

 
 tf.nn.softmax_cross_entropy_with_logits(labels=y_, logits=y))


참고자료

  • https://www.tensorflow.org/get_started/mnist/beginners


728x90

'기술 > 인공지능' 카테고리의 다른 글

뉴럴네트워크(Neural Network)  (0) 2017.08.18
오버피팅(Overfitting)  (0) 2017.08.10
숫자인식 코드 분석해보기  (2) 2017.08.03
Softmax와 Cross entropy  (0) 2017.08.03
Logistic Regression (로지스틱 회귀)  (0) 2017.07.31
Linear Regression (선형회귀)  (0) 2017.07.28

Logistic Regression (로지스틱 회귀)

기술/인공지능 2017. 7. 31. 21:49 Posted by 아는 개발자

앞선 포스팅에서 나이에 따른 연봉 변화로 선형 회귀를 설명했었습니다. 이번에는 예시를 조금 변형해서 연봉이 5000만원이 넘는지 안넘는지를 결정하는 함수를 생각해봅시다. 먼저 연봉이 5000만원을 넘지 않는 경우를 0, 넘는 경우를 1로 생각하고 그래프를 그려볼까요?



y축의 값이 0과 1밖에 없으니 그래프가 상당히 단조로워졌네요. 아마 30-32 사이에서 연봉이 5000만원으로 변화하는 지점이 있는 것 같습니다. 이 정보를 토대로 선형회귀를 해보면 아래 그림처럼 추세선을 만들어 줄 수 있을 것 같습니다.



대략 추세선의 값이 0.5 정도가 넘으면 연봉이 5000만원이 넘는다고 짐작 할 수 있겠네요. 이렇게 하면 뭐 더이상 손댈 것도 없을것 같습니다. 그런데 새로운 학습 데이터가 추가됐다고 해봅시다. 요렇게요.



55세 57세 데이터가 들어오면서 기울기가 낮아져버렸습니다. 아까처럼 y축 값이 0.5 가 넘을때 연봉이 5000만원이 넘을 것이라고 단언하면 30-32세 사이의 데이터를 커버 할 수 없게 됩니다. 골치아프네요. 그렇다고 기준 값을 0.4로 내리자니 다른 데이터로 학습 한 후에는 또 달라질 것이고 머리가 아픕니다. 모든 경우를 커버 해줄수가 없죠.


이런 분류(Classification)의 문제는 선형 회귀로 해결 할 수 없습니다. 차라리 특정 지점을 지나면 분류 되는 것으로 정하면 편할 것 같네요. 인공지능에서도 이렇게 두 가지 경우를 분류합니다. 이때 사용하는 회귀 방법이 Logistic Regression 입니다. 여기서 특정 임계치를 지날 때의 값을 표현하는 함수가 여러가지 있는데요 가장 대표적인 Sigmoid 함수에 대해서 알아봅시다.



그래프를 보니 x축이 증가하는 방향은 1로 수렴하고 감소하는 방향은 0으로 수렴하네요. 쉽게 x>0 인 구간은 1이고 x<0인 구간은 0으로 정의 할 수 있을것 같습니다. 우리는 식을 살짝 변형시켜서 특정 입력값이 0 보다 큰 경우에는 분류가 참인 경우로, 그렇지 않은 경우에는 거짓인 경우로 나눠볼 수 있습니다.



처음보는 수식이 나왔죠? 쎄타 위에 T자가 붙어있습니다. 이건 행렬에서 transpose라는 개념입니다만 지금 설명하면 길어지니 바로 오른쪽 그림처럼 이것이 지난 포스팅에서 배운 선형 방정식과 동일하다는 것만 확인하죠. 이렇게 식을 만들면 결국 z 값에 따라서 분류 기준이 결정되고 z는 곧 우리가 세운 선형 방정식이 됩니다. 우리는 다시 가장 그럴듯한 쎄타 값만 찾아주면 되겠네요.


선형회귀에서 쎄타 값을 찾기 위해 어떻게 했는지 기억 나시나요? 네 맞습니다. 비용 함수를 만들어 내고 여기에 Gradient Descent라는 기법을 적용해 여러번 반복해 가장 우수한 쎄타 값을 찾아 낼 수 있었습니다. 여기도 방식은 똑같습니다. Logistic 회귀에 해당하는 비용 함수를 만들고 이 함수에 Gradient Descent를 여러번 반복해 쎄타값을 찾아낼 수 있습니다. Logistic 회귀의 비용함수는 다음과 같아요



수학자들이 이렇게 하면 가장 좋다는 것을 이미 밝혀 냈으니! 증명하는 것은 생략합시다. 위 수식으로 우리가 설정한 쎄타값이 얼마나 학습 값을 잘 맞추고 있는지 확인 해 볼 수 있어요. 그리고 위 수식에 Gradient Descent를 적용해서 가장 우수한 쎄타값을 구해 낼 수 있답니다. 이것도 증명은 생략하고 수식만 보고 갈까요? 



선형회귀에서 했던 것처럼 Logistic 회귀에서도 위의 수식을 반복 적용해서 훌륭한 쎄타 값을 구해 줄 수 있습니다. 너무 위 식에 신경쓸 필요 없습니다. 어차피 Tensorflow가 모두 계산해 줄 거니까요 하하하. 그냥 느낌만 가져갑시다!


그런데 여기서 의문이 하나 생기네요. 지금껏 우리는 두가지 경우로 분류하는 것만 생각했습니다. 하지만 실제로는 아래 그림처럼 여러가지 경우로 분류하게 되는 경우가 생깁니다. 이런 경우는 어떻게 해야할까요?



방법은 생각보다 간단합니다. 분류 작업을 각각의 군집 마다 세번 실행하면 됩니다. '이것 아니면 나머지는 다 무효로 처리해!' 방법을 쓰는거지요. 아마 그림을 보시면 바로 감이 올겁니다.



첫번째 그림에서 파란색 군집과 나머지를 모두 검은색으로 생각했습니다. 그러면 파란색에 대해서는 확실히 구해 줄 수 있겠지요? 시계방향으로 이어지는 노란색과 초록색도 각각의 색깔에 대해서 만큼은 분류를 확실히 해줄 수 있었습니다. 각각의 분류 기준을 모두 합하니 각 기준에 대해서 분류를 해줄 수 있게 되었네요.


결국 여러 경우에 대해서 분류를 하는 것은 한 군집 집단에 대해서만 Logistic 회귀를 해주면 되는 것으로 귀결됩니다. 정말 유용한 함수죠? 다음 포스팅에선 지금까지 배운 것들을 이용해 손글씨를 판별하는 Tensorflow Tutorial 코드분석을 해보도록 하겠습니다!


* 참고한 자료


Coursera Machine Learning Andrew Ng 강의자료 - https://www.coursera.org/learn/machine-learning/

728x90

'기술 > 인공지능' 카테고리의 다른 글

뉴럴네트워크(Neural Network)  (0) 2017.08.18
오버피팅(Overfitting)  (0) 2017.08.10
숫자인식 코드 분석해보기  (2) 2017.08.03
Softmax와 Cross entropy  (0) 2017.08.03
Logistic Regression (로지스틱 회귀)  (0) 2017.07.31
Linear Regression (선형회귀)  (0) 2017.07.28

Linear Regression (선형회귀)

기술/인공지능 2017. 7. 28. 20:50 Posted by 아는 개발자

앞선 포스팅에서 우리는 인공지능이란 '대량의 데이터를 학습해 가장 그럴듯한 미래를 예측한다'고 정의했습니다. 그리고 여기서 우리 개발자가 할 일은 대량의 데이터를 훌륭하게 학습시키는 알고리즘을 만드는 일이라고 말씀드렸습니다. 이제 학습 알고리즘에 대해서 공부해봐야겠죠? 마음은 벌써 알파고에 쓰인 딥러닝을 짜보고 싶지만, 지금 당장은 해볼 순 없으니 가장 기초적인 학습법인 선형회귀(Linear Regression)부터 천천히 공부해봅시다.


우리에게 아래 표처럼 나이에 따른 연봉 데이터가 있다고 가정해봅시다


실제데이터는 아닙니다. 제가 가공한거에요 헤헤


언듯 데이터를 분석해보니 나이가 많을수록 연봉을 더 많이 받는것 같습니다. 한번 차트로 볼까요?


그래프 상으로 표현해보니 실제로도 그러하네요. 위 자료를 통해 나이에 따라서 연봉을 대강 추측 해볼 수 있을 것 같습니다. 나이가 32살이라면 대강 4천 3백만원에서 5천만원 사이인 4천 6백만원 정도 받을거라고 생각 해볼 수 있겠네요. 하지만 이런 식으로 하면 좀 야매인 느낌이죠? 위 데이터를 간단히 선형 방정식으로 표현한다면 좀더 일관성 있고 훌륭하게 예측 할 수 있을 것 같네요.


엑셀에 있는 추세선 기능을 활용해 선형 방정식을 추가해봤습니다. 나이 값에 따라 연봉을 추출 할 수 있으니 더 신뢰성이 가는 데이터입니다. 좀더 전문적인 느낌이죠? 정말 별거 아닌것 같지만 이것은 인공지능의 가장 기본적인 연산작업입니다. 이렇게 짜잘한 일련의 작업들이 쌓여서 더 복합한 것들을 예측하는 작업이지요.


자 그러면 이제 가장 그럴듯한 일차 방정식을 구해봅시다. 아마 위에서 우리는 차트로 그려보고 대강 짐작했을 뿐이지 실제로는 어떻게 구해야 할지 감이 안올겁니다. 따로 이걸 구할 수 있는 방정식이 있는 것도 아니구요. 그래도 괜찮습니다. 좀 번거롭지만 그래도 컴퓨터를 이용하면 훌륭하게 구할 수 있습니다.


기본적인 선형 방정식의 수식은 이렇습니다.



여기서 두 θ(쎄타라고 부르겠습니다) 값이 어떻냐에 따라서 그래프의 모형이 결정됩니다. 우리는 학습 데이터에 가장 근접한 두 쎄타 값을 구하는것이 목표입니다. 가장 손쉽게 해볼 수 있는 방법은 랜덤 값을 막 넣어보고 가장 그럴듯한 모형을 찾는 겁니다만 이렇게 한다면 시간이 엄청 오래 걸리겠지요? 컴퓨터도 이정도 연산을 커버할 만큼 빠르지 못합니다.


마구잡이로 대입해가며 선형 방정식을 만들기 전에 한 번 생각해봅시다 우리가 구한 선형 방정식이 학습 데이터와 얼마나 근접한지를 식으로 어떻게 표현 할 수 있을까요? 가장 간단한 방법은 우리가 만든 선형 방정식이 추출한 값과 실제 데이터 값의 차이를 확인해보는 방법일 겁니다. 여기서 값의 차이의 제곱의 합을 식으로 표현해봅시다.


m: 데이터의 개수, x: 나이값, y: 연봉, h: 선형방정식 입니다


대충 이렇게 만들면 우리가 만든 선형 방정식의 효율을 측정 해 볼 수 있을것 같습니다. 위의 식은 인공지능에 비용함수(cost function)이라 부릅니다. 아마 가장 좋은 선형방정식은 비용함수의 값이 가장 낮은 방정식일 겁니다. 


위의 그래프는 쎄타 값의 변화에 대한 비용함수 그래프입니다. 볼록볼록한 산능선 같은것이 둘쭉날쭉합니다. 지도상의 십자가 포인터는 현재 우리가 있는 지점이고 우리가 가야할 지점, 비용 함수가 가장 낮은 지점은 z 값이 가장 낮은 지점 즉 별표로 표시된 부분입니다. 이때의 쎄타값들을 구해 줄 수 있다면 가장 훌륭한 선형 방정식을 만들어 낼 수 있을것 같습니다.


이건 'Gradient Descent'라는 방법을 이용해서 가능합니다. 먼저 수식을 보여드릴게요!


여기서 α는 Learning Rate라고 합니다


위의 수식은 두개의 쎄타 값이 수렴 할 때 까지 반복하라고 되어 있습니다. 반복하는 작업은 비용함수의 편미분값 곱하기 알파 값을 기존 쎄타 값에서 빼주는 것을 계속 반복하는 방식입니다. 이렇게만 설명하면 감이 잘 오지 않죠? 다시 그래프로 돌아가봅시다


비용함수에서 쎄타0 값을 일반 상수로 놓고 Θ과 비용에 대한 그래프를 그려봅시다. 그러면 아래와 같이 볼록한 이차원 그래프가 만들어질겁니다(위 비용함수 그래프에서 특정 쎄타0를 축으로 자른다고 생각해보세요!)


여기서 노란 십자가는 현재 저희가 있는 지점입니다. 빨간 십자가 지점에 가까워 질수록 비용 값은 작아지겠죠? Gradient Descent 방법은 한번에 최적의 지점에 도달하진 못하지만 반복작업을 통해 최적의 지점에 도달 할 수 있게 해줍니다. 위의 수식을 1회 적용한 그래프는 아래와 같습니다.



비용함수를 쎄타에 대해서 미분한 값은 그 지점의 접선 기울기와 같습니다(고등학교때 다 배웠습니다) Gradient Descent 방법대로 기울기에 α값을 곱한 값을 기존 쎄타값에 빼주면 그래프는 최적점과 조금 더 가까워지게 됩니다. 이 방법을 여러번 반복하면 점점더 그래프가 최적점에 가까워지겠죠?


       


쎄타 0와 쎄타 1 각각에 대해서 위 작업을 반복하면 최적의 선형 방정식을 구해 낼 수 있게됩니다. 단 여기서 주의해야 할 점이 있습니다. Learning Rate라 말씀드린 α값을 어떻게 정하느냐에 따라서 학습 속도 및 성공의 유무가 결정됩니다. 만약 이 값이 너무 크거나 혹은 너무 작으면 어떻게 될까요?



왼쪽 그림은 α값이 너무 큰 경우에 해당합니다. 이때는 기존 쎄타 값이 너무 급격하게 변해 오히려 비용함수의 값이 더 증가하는 방향으로 움직이고 있네요. 이런 경우는 차라리 안하느니만 못합니다.


오른쪽그림은 α값이 너무 작은 경우입니다. 이때는 비용함수의 값이 감소하는 방향으로 이동하는데 이동하는 크기가 너무 느립니다. 한 두번이면 되는 학습 횟수를 몇배 더 해야하니 학습에 소요되는 시간이 오래 걸리게 됩니다. 그래도 비용함수가 증가하는 방향은 아니니 시간이 많다면 충분히 최적점에 도달 할 수 있겠네요


적당한 Learning Rate 값을 세팅하고 두 쎄타값을 구해내는 과정이 Linear Regression입니다. 생각보다 복잡한 수학 지식이 적용되진 않았지요? 고등학교 수학 + 편미분 만 알고 있으면 충분히 따라 갈 수 있을 정도입니다. 앞으로 배울 Logistic Regression도 이와 큰 차이는 없습니다! 다음 포스팅에선 이거랑 비슷한 Logistic Regression에 대해서 배워보도록 합시다

728x90

'기술 > 인공지능' 카테고리의 다른 글

뉴럴네트워크(Neural Network)  (0) 2017.08.18
오버피팅(Overfitting)  (0) 2017.08.10
숫자인식 코드 분석해보기  (2) 2017.08.03
Softmax와 Cross entropy  (0) 2017.08.03
Logistic Regression (로지스틱 회귀)  (0) 2017.07.31
Linear Regression (선형회귀)  (0) 2017.07.28

tensorflow 설치하면서 인공지능을 정의해보자

2017. 7. 20. 21:55 Posted by 아는 개발자

인공지능이 정확히 무엇인지 생각해보기에 앞서서 Ubuntu 터미널에 tensorflow 설치 명령어 하나만 실행해둡시다.

(혹시 GPU를 사용하는 버전으로 설치하고 싶으신 분은 tensorflow 사이트에서 확인해보시고 설치하셔야 합니다!)


sudo pip install --upgrade tensorflow


인터넷 사양에 따라서 20-30분 정도 걸립니다. 딱 인공지능에 대해서 정의 해보기 좋은 시간이네요. 그럼 시작해볼까요?


지금 우리에게 가장 익숙한 인공지능은 알파고입니다. 알파고는 엄청난 양의 기보를 학습한 후에 어떤 위치에 수를 놓으면 유리한지를 알게 됐습니다. 자 그렇다면 인공지능은 결국 대량의 데이터를 학습한 결과물 이라고 손쉽게 정의해볼 수 있을 것 같습니다.


그런데 대량의 데이터라면 어떤 단어가 떠오르지 않으세요? 2-3년전만해도 이 단어가 뉴스를 휩쓸고 다녔습니다. 바로 "빅데이터"입니다. 대량의 데이터를 축적한 기업 또는 연구소가 이 데이터를 분석해 사용자에게 유용한 정보를 제공하기 위해 여러가지 연구를 진행하곤 했었죠. 이런 데이터 분석 덕분에 쇼핑몰에서는 특정 나이대의 소비자들이 어떤 상품을 선호하는지, 정부 연구소에서는 사회 취약층의 소득 구조를 좀더 잘 파악하게 됐습니다. 이것 역시 대량의 데이터 덕분입니다.


인공지능은 대량의 데이터를 학습했고 빅데이터는 분석했습니다. 학습과 분석. 사실 이렇게 개념으로 표현해서 달라 보이는 것이지 데이터를 이용했다는 사실 만큼은 둘다 동일합니다. 데이터를 사용한 사실 하나만 놓고 보면 둘 간의 경계가 매우 모호한것 같습니다.


 

(인공지능과 빅데이터 모두 대량의 데이터를 사용한다는 점은 다를 것이 없어 보입니다)


하지만 둘 사이에 예측이란 단어를 놓으면 차이는 확연히 구분됩니다. 빅 데이터는 (물론 분야마다 다르긴 합니다만)예측을 하지 않습니다. 빅데이터는 어질러진(unstructured) 데이터들을 분석하기 좋게 정리정돈을 잘 한 후 여기서 유용한 정보(useful information)를 추출하는 것이 목표입니다. 이와 달리 인공지능은 잘 정돈된 데이터(structured)를 여러 알고리즘을 통해 학습 한 후 가장 그럴듯한 미래를 예측합니다. 알파고로 예를 들면 기보는 학습에 사용된 잘 정돈된 데이터 였고 알파고가 이세돌을 상대로 놓았던 수들은 승리하기 가장 유리한 예측 들이었습니다. 


자 그러면 이제 인공지능을 정의 해볼 수 있겠습니다. 인공지능은 '대량의 데이터를 학습해 가장 그럴듯한 미래를 예측한다'. 엄청 거창한 것이 나올 줄 알았는데 의외로 간단하지요? 정의를 아래의 그림처럼 표현해 볼 수 있습니다


(잘 정돈된 데이터를 학습한 모델은 미래를 예측 할 수 있습니다)


여기서 모델은 잘 정돈된 데이터를 학습한 결과물입니다. Test Data를 모델에 입력하면 가장 그럴 듯한 예측을 하는 것을 말하지요. 머신러닝의 성능은 얼마나 예측을 잘하는 모델을 잘 만드느냐에 달렸고 이 모델은 얼마나 풍부한 데이터를 갖고 있느냐 그리고 얼마나 우수한 학습 알고리즘을 사용하고 있느냐에 달려있습니다. 많은 인공지능 연구자들이 좋은 학습 알고리즘을 만드는데 시간을 많이 투자했고 가장 간단한 선형 회귀(Linear Regression) 부터 알파고에 적용된 딥러닝(Deep Learning)까지 만들어진 상태입니다.


이제 우리가 공부할 부분도 명확해졌습니다. 우리는 인공지능에서 학습 알고리즘을 공부해볼 것입니다. 가장 기초적인 선형회귀에서부터 뉴럴네트워크까지 차근차근 밟아볼까요?

728x90

개발자는 이제 인공지능을 공부해야 합니다.

2017. 7. 17. 23:13 Posted by 아는 개발자

2016년 3월 이세돌과 알파고의 대결이 시작되기 전까지만해도 알파고의 승리를 점치는 사람은 거의 없었습니다. 경우의 수가 제한된 장기나 체스에 비해 바둑은 수가 거의 무한에 가까워 현재 컴퓨팅 능력으로는 기계가 이 모든 경우의 수를 계산하기는 무리라는 것이 대부분 바둑계 전문가들의 생각이었지요. 하지만 모두의 예상을 뒤엎고 알파고는 10년간 세계를 평정했던 이세돌을 4:1로 제압합니다. 그리고 올해 한층 더 진화된 알파고는 현 인간계 바둑랭킹 1위 커제를 상대로 단 한판도 내주지 않고 우승합니다. 알파고의 기세를 보니 당분간(어쩌면 평생) 이 기계를 이길 수 있는 인간은 나오지 않을 것 같습니다.



(알파고를 상대로 거둔 이세돌의 1승은 인류의 마지막 승리가 될 것이라고 합니다)


바둑 경기가 시작되기 전만 해도 우리는 인공지능이 아주 먼 미래라 생각했었습니다. 사실 컴퓨터 전공생들이라면 배우는 컴퓨터 역사에서도 인공지능 기술은 잠깐 번쩍하고 말던 기술이었습니다. 매번 역사속에 등장 할 때마다 이론은 출중하나 하드웨어 성능 문제로 빛을 보지 못했었죠. 그런데 이번에 다시 등장한 인공지능의 기세는 심상치 않습니다. 초기엔 단순한 노무작업 정도만 할 것이라 예상했는데 요즘의 인공지능은 간단한 스포츠 기사도 쓰고 스스로 광고도 만들기 시작했습니다. 그뿐만이 아닙니다. 창의성의 영역이라 여겨지는 예술에도 침투하기 시작했습니다. 다량의 예술 작품을 학습한 인공지능은 직접 그림을 그리기도 하고 단편 소설을 쓰기도 합니다. 인공지능이 만든 작품 중 몇몇은 저같은 일반인들은 전혀 구분 하지 못할 정도로 고퀄로 보이는 작품들도 있습니다.


(유명 예술가의 화풍을 학습한 인공지능의 작품입니다. 놀랍지 않나요?)


일반인들도 놀라우시겠지만 저처럼 인공지능 관련 일에 종사하고 있지 않는 개발자들은 더더욱 놀랍습니다. 중앙처리장치와 메모리의 성능이 좋아지면서 컴퓨터 산업 전반의 발전이 있을 것이라고 예상은 했습니다만 이정도일 줄은 몰랐습니다. 그리고 놀람과 동시에 두려움도 엄습합니다. 인공지능 시장이 확장하면 할 수록 들러리였던 영역은 더더욱 들러리가 될 확률이 매우 높아졌습니다. 대학시절 저를 감동시켰던 운영체제는 역사가 오래된 만큼 관련 연구도 완료된 상태이고 관련 문제들의 솔루션도 모두 만들어 졌습니다. 앞으로는 더 좋은 페이지 할당 알고리즘이나 더 훌륭한 스케줄링을 만든다고 해도 좋은 논문거리는 되지 못할 것입니다. 이 영역 종사자들이 아마 앞으로 해야할 일은 아마 과거에 이뤄왔던 연구들을 잘 조합하는 것에 불과 할지도 모릅니다. 원가 절감이라는 획기적인 아이디어를 들고 나온 가상화 기술도 Docker가 등장하면서 클라우드 컴퓨팅에서 한물 간 기술이 되버렸습니다. 더이상 발전할 요소가 없다, 한물 간 기술이 되었다는 말은 곧 그 기술을 파고 들었던 개발자의 밥줄이 끊긴다는 말과 같습니다.


(운영체제는 연구가 오래 됐고 Docker가 등장하며 가상화 기술은 한물간 기술이 됐습니다)


우리 모두 역사의 흐름을 무시한 나머지 굶주림을 면치 못했던 조선 후기 양반들과 같은 실수를 하고 싶어하진 않을 것입니다. 남들이 알아주지 않는 분야를 파고든 예술가는 역사책에 이름을 남기지만 남들이 알아주지 않는 기술을 파고든 개발자는 이름 남길 곳이 깃로그 밖에 없습니다. 한물간 기술을 부여잡고 수요가 생기길 기다리는 것은 본인 뿐만 아니라 가족까지 굶이기 딱 좋은 행동입니다. 그렇게 되지 않으려면 개발자들은 시대의 기술 트렌드를 읽고 팔로우 해야합니다. 거의 3-4년을 주기로 트렌드 기술이 있었고 이번에는 인공지능 기술이 트렌드가 됐지요. 그런데 지금까지 나왔던 기술들과 다르게 인공지능의 진입장벽이 높아보입니다. 수식이 난무한 이론도 따라기 벅찰 것 같은데 저걸 다 일일히 코딩하자니 언제쯤 구현을 해볼 수 있을지 가늠이 되지 않습니다.


(분명 공학수학을 수료한 것 같은데 뭔소린지 통 모르겠네요, 하하하)


이런 고민을 하는 개발자를 위해 구글은 올해 4월에 인공지능용 라이브러리 tensorflow 를 공개했습니다. 이건 python으로 짠 인공지능 개발 툴입니다. 학습에 쓸 데이터와 기본적인 인공지능 이론을 갖춘 개발자는 tensorflow에서 제공하는 함수를 이용해 (손쉽게)AI를 만들수 있게 됐습니다. 휴 한시름 덜었습니다. 저런걸 일일히 다 코딩하자니 앞이 깜깜했는데 이미 툴은 다 마련되어 있었다니 천만 다행입니다.





그래도 여전히 첩첩 산중인것 같습니다. 결론적으로는 tensorflow가 있어도 이론을 공부해야하고 위의 수식도 언젠간 배워야 한다는 말이지 않습니까? 얼른 알파고 같은걸 만들어내야 할 것 같은데 마음만 초조하고 뭐부터 시작해야 할지는 모르겠습니다. 대학시절에 인공지능 강의를 하나도 수강하지 않았던 것이 후회되기 시작합니다. 야간대학 속성 강좌라도 등록해야 할 것 같기도 합니다. 하지만 굳이 대학까지 갈 필요는 없습니다. 이미 코세라에 인공지능 개발시 필요한 기초적인 머신러닝 강의가 마련되어 있고 tensorflow 튜토리얼에서도 논문까지 첨부해주면서 설명해줍니다. 다행히 공부할 자료는 인터넷에 널려 있습니다. 우린 이걸 잘 활용해야 합니다.


물론 강의 내용이 쉽지 않고 논문이 술술 읽히지는 않습니다. 하지만 현재 무모하게 트랙을 밟아보고 있는 사람으로서 해볼만 하다고는 생각합니다. 100%까진 아니어도 '이것을 어떻게 쓰면 좋겠다'고 감 정도는 잡아본것 같습니다. 앞으로 저의 매거진에선 인공지능 지식이 전무한 개발자가 코세라 강의와 tensorflow로 공부한 내용을 공유해보려고 합니다. 영어와 수식이 난무한 인공지능에서 엑기스만 뽑아내 저와 같은 개발자들이 쉽게 인공지능을 접할 수 있는 글을 쓰도록 하겠습니다.



728x90

예상보다 힘들었던 디자인 작업도 끝나고 이제 드디어 런칭까지 했다! 하아 그런데 생각보다 런칭이 이렇게 오래 걸릴 줄이야... 


https://play.google.com/store/apps/details?id=com.cholab.kwony.jochongmu



애플리케이션을 출시하는 일은 가게 차리하는 것과 비슷했다. 개발하는 일 뿐만 아니라 가게를 꽃단장 하는 것처럼 사용자들이 스토어에 애플리케이션을 다운받으러 들어 올 때 홍보할 그래픽 이미지 및 문구도 필요했고 음식점이 식약청에 검사 받는 것처럼 내 애플리케이션도 유해한 요소가 있는지 없는지 간단한 설문 조사를 통해 검사받았다(3세 이상 이용 가능한 애플리케이션이 됐다)


막상 출시하고 친구들에게 공유를 했는데... 아 생각보다 허접한 느낌이다. 마치 많이 공부 한 것 같은데 시험장에 들어오면 머리가 빈 것 같은 느낌이랄까. 남들이 과연 이걸 쓰고 편안 할 지 모르겠고... 최대한 심플하게 만들겠다고 했는데 반드시 필요한 기능까지 뺀 것은 아닌가 생각했다.


성실하게 피드백 해준 친구 한명 덕에 고칠 부분들을 많이 찾아냈다. 모임을 관리하는 사람의 입장에선 참여한 멤버를 관리 할 수 있는 기능이 반드시 필요하다고 했다. 쉽게 모임 멤버수만 갖고 계산하면 참여한 멤버수를 기억하기가 쉽지 않다고 한다. 다른 애플리케이션도 결제 목록별로 참여자 정보를 모두 관리하고 있었다. 맞는 말인 것 같아서 바로 수용했다. 대리결제자 탭을 참여 멤버 탭으로 변경하고 각 결제 내역 별로 참여한 멤버들을 관리 하도록 했다. 물론 친구의 표현처럼 건물을 3센치 옮기는 일이긴 하지만. 건물이 더 커지기 전에 빨리 옮겨야겠다.


그리고 반올림 기능보단 올림 기능이 좋다고 했다. 반올림하면 손해보는 경우가 생기니 염치 없으려면 제대로 하는게 좋다고(ㅋㅋ) 적극 수용했다.


결제 내역별 참여자 정보는 Relation table을 만들어서 결제내역별로 참여자 정보를 관리 할 수 있도록 했다. 테이블 구성은 아래와 같다.


먼저 party ID로 한 번 거르고 그 다음에는 결제 내역 X 참여자 정보로 관리하는 방식이다. DB숙제에서도 안 썼던 Relation Table을 여기서 쓸 줄이야. Relation Table은 하나의 객체가 다른 여러 객체와 연결이 필요한 경우 데이터 수를 최소화 하면서 관리 할 수 있는 방법이다. 매번 table을 찾아다녀야 하는 cost가 있지만 그래도 공간을 많이 차지하지 않고 관리가 용이하다는 점에서 좋은 방법이다.

728x90

총무앱 - 디자인

사이드 프로젝트/이기적인 총무 2017. 7. 2. 16:43 Posted by 아는 개발자

솔직히 말해 대학생 시절에는 디자인을 무시했었다. 그때의 난 디자이너들이 공학자들이 힘들게 만들어 둔 기술위에 숟가락을 올리는 일을 한다고 생각했다. 핸드폰이나 티비의 디자인 정도는 나같은 일반인들도 손쉽게 할 수 있을 것 같은데 디자이너들은 쓸데없이 굴리는 영어 발음과 패션 스타일로 폼 잡는다고 생각했다. 공대생들에게 가야할 노고가 디자이너들에게 뺏긴 것 같아 못마땅한 기분도 들었다.


하지만 이 편견은 회사에 들어와서 일차적으로 깨졌다. 내가 만든 피피티는 디자이너들의 손을 하루만 거치면 놀랍게 변해 있었다. 내가 대강 만들어둔 그림을 아름답게 바꿀 뿐만 아니라 발표에서 내가 강조하려고 했던 단어와 문장들을 정확히 캐치해 보는 가장 강렬하게 인상을 줄 수 있는 곳에 배치해뒀다. 피피티뿐만 아니라 제품을 보는 관점도 달랐다. 내가 만든 기능들을 디자이너들은 사용자가 어려움을 겪지 않고 사용 할 수 있도록 변형해주었다. 이 과정은 전혀 숟가락을 올리는 일이 아니었다. 오히려 디자이너가 없었으면 공들여 만든 기술이 모두 허공이 될 판이었다.


그리고 이 앱을 직접 만들면서 이제 뼈저리게 알게됐다. 아마 디자인 하는 작업이 개발하는 작업보다 1.5배는 더 오래 소요됐을것이다. 사용자가 편하게 앱을 사용 할 수 있도록 디자인 하는 것은 생각보다 매우매우 어려운 일이었다. 개발은 궁금한 점을 검색해보면 모두 나오는데 디자인은 인터넷에 '어떻게 편리하게 사용 할 수 있을까'라고 물어볼 수도 없고 아무런 기초 지식이 없는 상태라 그런지 참 답답했다. 거의 모든 순간들이 고민의 연속이었다. 어떤 아이콘을 사용해야 할지 그리 어떻게 배치해야할지, 어떤 텍스트가 손쉽게 사용자가 이해 할 수 있을지 그리고 어떤 레이아웃이 안정감이 있을지를 놓고 한참을 고민했다. 아마 회사에서 짧은 짬이 날때는 모두 볼펜으로 낙서해가며 작업을 했던 것 같다. 뿐만아니라 다른 애플리케이션은 어떻게 디자인 되어 있는지 참고도 해보고 주변 사람들에게 피드백도 받았다. 만족스럽지는 않지만 그래도 봐줄 만한 정도는 된 것 같다.


전체적인 디자인 플로우는 이렇다.


1. 앱 실행시 짧게 보여주는 스크린. 카카오톡이나 네이버 실행할 때 애플리케이션 마크가 나오는걸 생각하면 된다. 있으면 좀더 fancy해보이지 않을까 해서 만들어 넣어봤다. 가운데 있는 아이콘은(조커 얼굴에 계산기랑 돈다발이 있는) iconflows라는 에디터를 이용해서 만든거다. 시중에 무료로 배포된 아이콘을 사용해서 만들려고 했는데 아이콘에 윤기가 없는 느낌이라 직접 5000원 결제하고 에디터를 이용해서 만들었다. 나름 귀여운 아이콘인것 같다 ㅎㅎ



2. 모임 목록 부분. 총무인 사람이 자신이 관리하고 있는 모임들의 목록을 관리하는 인터페이스다. 오른쪽 아래 버튼을 이용해 간단히 추가 할 수 있다. 만들고 나니 카카오톡 친구 목록 인터페이스랑 비슷하다. 사용자들에게 가장 친숙한 인터페이스인것 같다.


      



3. 파티별 결제내역/대리결제자/정산하기 디자인. 이부분은 이 애플리케이션의 가장 핵심인 부분이라 사용자가 별다른 어려움을 겪지 않고 사용할 수 있도록 많은 고심을 들였던 부분이다. 특히 여기서 정산하기 탭을 만들 때 어려움이 많았다. 하나의 화면에 결제 내역 반올림 하는 인터페이스와 정산 기능 그리고 은행 계좌를 설정하는 것을 모두 표현해야 했는데, 사용자에게 정보를 많이 주려고하니 텍스트가 많아져 애플리케이션이 아마추어 같아졌고 또 정보를 전혀 안주자니 사용하기 불편해지는 것 같았다. 아래 그림은 가장 초기버전이다.



크게 결제내역 조정 레이아웃, 정산내역 레이아웃, 입금계좌 레이아웃 세개로 나눠져 있는데 경계선이 없으니 안정감이 없었고 텍스트가 지나치게 많아서 지저분한 느낌이었다. 그리고 위의 화살표는 반올림 조정 버튼이었는데 따로 설명이 없다면 전혀 이미지가 반올림을 표현하지 못하고 있었다. 일단 각 레리아웃별로 경계선을 넣었다. 텍스트를 최대한 없애기위해 정산해보기와 계좌의 텍스트는 없애버리고 Dialog로 보여주는 것으로 바꿨다. 그리고 반올림 버튼은 0의자리, 1의자리, 10의자리별 이미지를 다르게해서 각자리별 반올림을 표현했다. 이것도 만족스럽진 않지만 그래도 이전보다는 직관적인 느낌이다.


    


정산 결과와 입금계좌 설정하는 다이얼로그. 텍스트를 다이얼로그로 빼버렸다. 다이얼로그까지 이쁘게 만들고 싶은데 이건 어떻게 해야할지 감이 안온다. 일단 먼저 앱스토어에 출시하고 업데이트를 해볼 생각이다.

728x90

'사이드 프로젝트 > 이기적인 총무' 카테고리의 다른 글

이기적인 총무 - 리팩토링 계획  (1) 2018.01.07
총무앱 - 이기적인 총무 런칭 그리고 업데이트  (0) 2017.07.15
총무앱 - 디자인  (0) 2017.07.02
총무앱 - 개발+a  (0) 2017.06.06
총무앱 - 개발  (0) 2017.05.14
총무앱 - 기획  (0) 2017.05.05

총무앱 - 개발+a

사이드 프로젝트/이기적인 총무 2017. 6. 6. 17:47 Posted by 아는 개발자

사실 애초에 개발은 지난번 포스트에 올린 요구사항들만 만드는 것으로 끝내려고 했는데 막상 만들고 나니 그냥 일반 계산기와 다를바 없는 것 같았다. 어디가서 내 이름 걸고 만들었다고 하기 창피할 것 같아 몇가지 기능을 더 넣었다. 총무로 살아온 기간이 길어서 그런지 있으면 좋을 법한 기능들은 금방 떠올랐고 현재는 모두 구현해둔 상태다


1. 대리결제 기능


모든 계산을 총무가 처리하지 않고 다른 사람이 결제하는 경우도 있다. 예를들어 여행중에 총무가 아닌 다른 사람이 대신 여행 물건을 사러 다녀오는 경우나 총무 카드가 한도초과돼서 다른 사람이 대신 결제하는 경우들이 그렇다. 대리 결제가 한두건이면 별 문제가 되지 않는데 결제 수가 많거나 여러 사람이 대리 결제를 한 경우에는 총무 입장에선 난감하다. 대리 결제한 당사자들은 나중에 정산 할 때 자신이 낸 결제내역을 참고해서 정산 해달라고 하는데 이 사람들에게 얼마를 덜 받아야하는지 그리고 혹시 내가 지금 손해보고 있는 것은 아닌지 머리가 아프다. 


이렇게 골치아픈 경우들을 애플리케이션 자체에서 모두 처리 할 수 있도록 만들었다. 결제 내역을 추가 할 때 대리 결제자를 등록 할 수 있게 하고 각 대리 결제자별로 결제 총액을 덜 내도 되는 금액으로 공유 메시지에 넣었다. 총무 입장에선 결제 내역을 추가할 때 대리 결제자 이름만 잘 등록하면 된다. 


(대리 결제자 목록이 따로 존재한다. 오른쪽 아래 버튼으로 대리 결제자를 추가 할 수 있다)



2. 똑똑한 정산 기능


총 결제 금액을 참석자 수로 나누면 0원으로 정확히 떨어지는 경우가 많이 없다. 결제 건수가 많아지면 더 그렇다. 이런 경우 내림을 하거나 반올림을 하게 되는데 비록 적은 금액이지만 총무 입장에선 손해가 되는 경우도 있고 이득을 볼 수 있는 경우도 있다. 소소한 금액이 이득으로 돌아오면 소위 개이득이 되지만 손해로 오면 번거로운 직책을 맡고 있는데 손해까지 보는 경우가 되어 기분이 좋지 않다.


그래서 각각의 금액으로 정산 할 경우 얼마를 손해볼 지 애플리케이션에서 모두 계산해주도록 했다. 각 결제 내역별로 특정 자리에서 반올림 할 수 있도록 만들고 조정된 금액으로 정산 할 경우 얼마나 이득/손해를 볼지 자동으로 계산해줄 수 있도록 만들었다. 


(각 결제 내역 별로 반올림 조정을 할 수 있다. 왼쪽 하단의 이미지를 누르면 조정된 내역에 대한 정산 값이 나온다)


이 기능을 구현하면서 이기적인 총무라는 애플리케이션 이름도 생각해냈다. 앱스토어에 등록된 다른 총무앱들에 비해서 컨셉이 독특하고 사람들이 한 번쯤 관심을 가질만한 이름인 것 같다. 

728x90

'사이드 프로젝트 > 이기적인 총무' 카테고리의 다른 글

이기적인 총무 - 리팩토링 계획  (1) 2018.01.07
총무앱 - 이기적인 총무 런칭 그리고 업데이트  (0) 2017.07.15
총무앱 - 디자인  (0) 2017.07.02
총무앱 - 개발+a  (0) 2017.06.06
총무앱 - 개발  (0) 2017.05.14
총무앱 - 기획  (0) 2017.05.05