스토리 홈

인터뷰

피드

뉴스

조회수 1148

해커 준비: 좋은 코드 만들기

출처 : 구글 이미지 검색Just Hacks지난 몇 주간 저는 I/O의 devops문화 기반을 다지는 작업을 해왔습니다. 여전히 부족한 점이 많지만 그동안 일어난 변화를 지켜보면 첫 걸음은 비교적 잘 뗀듯 합니다. 지금부터는 이 devops문화가 제대로 자리잡는 일이 중요한 단계입니다. 다시말해, devops문화가 튼튼하게 뿌리내릴 수 있게 Hacking하는 것이 저의 당분간의 과제입니다.최근 devops를 연구하고 도입하는데 적잖은 시간과 노력을 쏟았기 때문에 실패할 경우 매몰비용이 만만치 않습니다. 꼭 성공시켜야하는만큼 실증적으로 엔진을 검증하기로했습니다. 그래서 지난 주부터는 저도 devops문화에 소속된 벡엔드 엔지니어로서의 일을 시작했습니다. 당분간 직접 코드를 만들어내야겠지요.설계에 그치지 않고 스프린트를 직접 참여해야만 현재 devops문화가 지닌 문제점이 무엇인지 제대로 볼수 있고 훌륭한 기술조직으로 거듭날 수 있다고 저는 믿습니다. 다시 개발자의 자세로 돌아가기 위해 가장먼저 좋은 코드를 작성하는 공부를 시작하였습니다.좋은 코드 만들기컴퓨터가 인식 가능한 코드는 바보라도 작성할 수 있지만, 인간이 이해할 수 있는 코드는 실력 있는 프로그래머만 작성할 수 있다. -마틴 파울러-SW엔지니어가 되기로한 이상, 제겐 감동까지는 아니지만 코드리뷰를 하는 짝꿍이 쉽게 이해할 수 있는 좋은 코드를 짜야할 의무는 있습니다. 그래서 지금까지 감명 깊게 읽은 고전 책들을 복습하기 시작했습니다. 그 첫 번째 책이 켄트백의 구현패턴입니다. 이 책은 설계나 디자인 패턴과 같은 추상적인 내용보다 키보드로 코드를 짜내는 순간에 고민해야하는 부분에서 교훈을 줍니다. 저는 이 책을 통해 코드를 바라보는 제 관점이 다음과 같이 바뀐듯 합니다.필드(현업)에서 생산된 코드는 코드를 작성하는데 드는 시간보다 읽는 시간이 압도적으로 많기 때문에 이를 감안해 봤을 때 읽기 “좋은 코드”를 짜는 노력이 가장 중요하다.돌이켜보면 학생 시절에는 왜 좋은 코드를 짜야하는지 당연히 모를 수 밖에 없었던 것 같습니다. 프로젝트성격의 코드만 짰기 때문에 종강하고나면 제가짠 코드를 다시는 들여다 볼일이 거의 없었거든요. 만약 대학교가 학생들의 취업경쟁력을 높이기 위해 CS 지식 뿐만아니라 Hacker 소양도 가르치고 싶다면 1학년부터 졸업할 때까지 서서히 발전되는 프로그램 하나를 만드는 4년짜리 과제를 두면 효과적일 것 같습니다.말씀드린 것처럼 필드에서 생성된 코드는 작성 시간보다 유지보수를 위해 읽혀지는 시간이 더 많은 편입니다. 특히 린스타트업을 충실하게 따르는 스타트업이라면 런칭기간이 극단적으로 짧기 때문에 제품(SW) 의 생애주기 중 99%의 시간이 유지보수 단계에 있을 것입니다. 이런 관점에 비춰보면 독자를 고려한 좋은 코드를 짜야한다는 사실은 더욱 중요해집니다.새로운 원칙지금까지 제가 견지하고 있는 좋은 코드를 만드는 원칙은 단순화와 중복제거였습니다. 이번 기회에 이 책을 다시 읽고 제 프로그래밍관에 새로운 원칙을 한 가지 더 추가하였습니다. 일관된 추상화인데요.좋은 코드는 일관된 추상화를 보여줍니다. 아래 예시 코드로 바로 확인하실 수 있습니다.void compute() { input(); flag |= 0x0080; // 나쁜 추상화 output(); }이 간단한 compute라는 함수는 제목처럼 입력(input)을 처리하고 이를 16진수 연산을 거친뒤에 출력(output)과정을 거치면서 마무리 됩니다. 그런데, 함 수 2번째 줄에 드러난 flag변수의 16진수 연산은 조금 쌩뚱 맞습니다. 암호처럼 느껴지네요. comput의 절차를 보여주는 input, output 사이에서 세부 구현사항을 친설하게 알려주려는 작성자의 배려는 되려 독자에게 혼란을 주기만 합니다. 이 혼란스러운 코드를 캡슐화를 통해서 일관된 추상화 수준으로 아래 코드처럼 리팩토링 할 수 있습니다.void compute() { input(); updateFlag(color.Brown); // 좋은 추상화 output(); }16진수 연산대신 의도가 드러나는 함수명과 인자전달을 통해 우리는 input을 처리하고 ouput을 갈색 텍스트로 출력시킨다는 사실을 자연스럽게 받아들일 수 있게 됩니다. 보시는 예제처럼 일관된 추상화는 문제해결 능력, 알고리즘 실력보다 코드를 작성하는 센스에 가깝습니다. 항상 독자를 배려하는 마음을 갖고 상대방에 입장에서 서서 코드를 작성하는 습관을 가져야 겠습니다. 이제 코드를 짜고 리뷰도 받으면서 구린내나는 코드를 신나게 리팩토링 할 일만 남았네요 :-)#스위쳐 #Switcher #DevOPS #데브옵스 #개발 #개발자 #DevOPS도입 #인사이트 #성장
조회수 2545

JANDI 검색엔진 도입기

이번 포스트에서는 JANDI가 검색엔진을 도입하게 된 배경과 어떤 작업을 했는지 공유하려고 합니다검색엔진 도입 배경JANDI는 사용자가 입력한 메시지를 검색하고 사용자가 올린 파일의 파일명/파일 타입을 검색하는 메시지/파일 검색 기능을 제공하고 있습니다. 데이터 저장소로 MongoDB를 사용하고 있는데 검색되는 필드에 인덱스를 걸고 정규 표현식을 이용하여 DB Like 검색(“DB는 검색을 좋아한다”아니에요;;)을 하고 있습니다.초기에는 데이터가 아담했는데, 서비스가 커감에 따라 사용자 증가하면서 생성되는 데이터도 많아졌습니다. 올 초에 데이터가 많아지면서 검색이 DB에 부하를 주고, JANDI 서비스에도 영향을 주게 되었습니다. 그래서 JANDI 서비스용 MongoDB와 검색 전용 MongoDB를 분리했는데 이는 임시방편이었고 언젠가는 꼭 검색엔진을 도입하자며 마무리를 지었습니다.시간은 흘러 흘러 4월이 되었습니다. 당시 메시지 증가량을 봤을 때 올해 안에 검색엔진을 사용하지 않으면 서비스에 문제가 될 거라고 판단이 되어 도입을 진행하게 되었습니다.검색엔진 도입의 목표는 다음과 같았습니다.현재 DB Like 검색과 비슷한 검색 품질이어도 좋다. (일정때문에)검색엔진 도입을 통해 검색이 JANDI 서비스에 영향을 주지 않도록 한다.색인을 위해서 주기적으로 JANDI의 MongoDB 데이터를 가져 와야 했지만, 이 작업이 JANDI 서비스에 큰 부하를 주지 않을 거라고 생각했습니다.검색엔진 후보로는 Solr, ElasticSearch, CloudSearch, ElasticSearch Service 가 있었는데 Solr를 선택했습니다.왜냐하면제가 경험한 검색엔진이 Solr 였습니다. 더군다나 2010년 초에 접했던 Solr 비해 많이 발전한 것 같아 개발자로서의 열정과 도전 욕구가 샘솟았습니다. SolrCloud pdf, WhyNoWarAWS에서 제공하는 검색 서비스는 많은 부분을 관리해준다는 면에서 솔깃했지만, Custom Analyzer는 적용할 수 없어서 선택하지 않았습니다.ElasticSearch에 크게 흔들렸지만 경험이없다 보니 공부하면서 프로젝트를 진행한다는 부담감이 커서 다음을 기약했습니다.작업 내용1. MongoImporter, Sharding. MongoImporter 수정현재 JANDI는 MongoDB를 데이터 저장소로 사용하고 있습니다. MongoDB의 데이터를 색인하기 위해 데이터를 검색엔진으로 가져와야 하는데 Solr에서는 DataImportHandler 기능을 제공하고 있습니다. 기본 DataImportHandler로 RDB 데이터는 가져올 수 있지만 이 외 MongoDB나 Cassandra 같은 NoSQL의 데이터를 가져오기 위해서는 따로 구현이 필요합니다. 구글신에게 물어봐서 SolrMongoImporter 프로젝트를 찾았는데 문제가 있었습니다. mongo-java-driver 버전이 낮아서(2.11.1) 현재 JANDI에서 서비스 되고 있는 MongoDB(3.0.x)의 데이터를 가져올 수 없었습니다.url: Reference compatibility MongoDB Java2.11.1에서 3.2.2로 버전을 올리고 변경된 api를 적용하는 작업, 빌드 툴을 ant에서 maven으로 변경하는 작업을 하였습니다. 마음의 여유가 된다면 P/R을 할 계획입니다.여담으로 DataImportHandler 작업과 함께 검색 schema 정하는 작업을 했는데 sub-document 형식이 필요하게 되었습니다. Solr 5.3부터 nested object를 지원한다는 article을 보았는데, nested object 지원 얘기를 보니 Solr도 text search 뿐 아니라 log analysis 기능에 관심을 가지는건 아닐까 조심스레 생각해봤습니다. (역시나… 이미 banana, silk 같은 프로젝트가 있습니다. Large Scale Log Analytics with Solr 에 관련된 이야기를 합니다.). Sharding. 그리고 Document Routing대량의 데이터를 처리하기 위해 한 개 이상의 node로 구성된 데이터 베이스에 문서를 나누어 저장하는 것을 sharding이라고 합니다. SolrCloud는 shard 생성/삭제/분리할 수 있는 API가 있고, 문서를 어떻게 나눌지 정할 수 있습니다. 어떻게 나눌지는 shard 생성 시 router.name queryString에 개발한 router 이름을 적어주면 됩니다. 그렇지않으면 Solr에서 murmur Hash 기반으로 문서를 나누는 compositeId router를 사용합니다. JANDI의 검색 기능은 Team 단위로 이루어지기 때문에 TeamId를 기준으로 문서를 나누기로 하고, compositeId Router를 사용했습니다. 실제 서비스의 문서 데이터를 색인 돌려서 각 node에 저장되는 문서 개수나 메모리/디스크 사용량을 확인했는데 다행히도 큰 차이가 나지 않았습니다.하나의 문서는 TeamId와 MessageId를 조합한 “TeamId + ! + MessageId” 값을 특정 field에 저장하고 해당 필드를 uniqueKey 지정했습니다. 간단한 수정으로 문서 분배가 되는점이 좋았고, 더 좋았던건 검색시 _route_ 를 이용해서 실제 문서가 존재하는 node에서만 검색을 한다는 점이 었습니다. 4년 전 제가 마지막으로 Solr를 사용했을 때는 사용자가 직접 shards queryString에 검색할 node를 넣어주어야 했습니다..../select?q=\*:\*&shards=localhost:8983/solr/core1,localhost:8984/solr/core1SolrCloud RoutingSolrCloud Routing2Multilevel CompositeId2. analyzer, queryParser. analyzerSolr에 기본으로 있는 text_cjk analyzer를 사용하였습니다. <!-- normalize width before bigram, as e.g. half-width dakuten combine --> <!-- for any non-CJK --> text_cjk는 영어/숫자는 공백/특수기호 단위로 분리해주고 cjk는 bigram으로 분리해주는 analyzer 입니다. analyzer는 이슈 없이 완성될 거라 생각했지만 오산이었습니다. 텍스트가 들어오면 token을 만들어주는 StandardTokenizerFactory 에서 cjk와 영어/숫자가 붙어있을 때는 분리하지 못해 원하는 결과가 나오지 않았습니다. 또한 특수기호중에 ‘.’(dot), ‘_‘(underscore)가 있을 때에도 분리하지 못했습니다.nametextInputTopic검색개선_AB1021_AB제시CD.pdfStandardTokenizerFactoryTopic검색개선_AB1021_AB제시CD.pdfCJKWidthFilterFactoryTopic검색개선_AB1021_AB제시CD.pdfLowerCaseFilterFactorytopic검색개선_ab1021_ab제시cd.pdfCJKBigramFilterFactorytopic검색개선_ab1021_ab제시cd.pdf원하는 결과topic 검색개선 ab 1021 ab 제시 cd pdf그래서 색인/검색 전에 붙어있는 cjk와 영어/숫자사이에 공백을 넣어주고 ‘.’와 ‘_‘를 공백으로 치환해주는 작업을 하였습니다. 색인은 Transform에서 처리하고 검색은 다음에 알아볼 QParserPlugin에서 처리했습니다.nametextInputTopic검색개선_AB1021_AB제시CD.pdfTransform 단계Topic 검색개선 AB 1021 AB 제시 CD pdfStandardTokenizerFactoryTopic 검색개선 AB 1021 AB 제시 CD pdfCJKWidthFilterFactoryTopic 검색개선 AB 1021 AB 제시 CD pdfLowerCaseFilterFactorytopic 검색개선 ab 1021 ab 제시 cd pdfCJKBigramFilterFactorytopic 검색개선 ab 1021 ab 제시 cd pdf※ 추가 : 검색 결과를 보여줄때 어떤 키워드가 매칭되었는지 Highlight 해야했는데, 색인하기 전에 원본을 수정을 해서 Solr에서 제공하는 Highlight를 사용하지 못하게 됐습니다. 눈 앞의 문제만 바라보고 해결하기 급급했던 저를 다시금 반성하게 되었습니다.. queryParser앞에서도 언급하였지만, 색인뿐만 아니라 검색할 때도 검색어가 입력되면 검색하기 전에 붙어있는 cjk와 영어/숫자를 분리하고 ‘.’, ‘_‘를 공백으로 치환해주는 작업이 필요합니다. Solr에서 기본으로 사용하는 LuceneQueryParserPlugin 을 수정하였습니다.@Override public Query parse() throws SyntaxError { // 수정한 코드 String qstr = splitType(getString()); if (qstr == null || qstr.length() == 0) return null; String defaultField = getParam(CommonParams.DF); if (defaultField == null) { defaultField = getReq().getSchema().getDefaultSearchFieldName(); } lparser = new SolrQueryParser(this, defaultField); lparser.setDefaultOperator (QueryParsing.getQueryParserDefaultOperator(getReq().getSchema(), getParam(QueryParsing.OP))); return lparser.parse(qstr); } QParserPlugin3. DataImportHandler manageMongoImporter에서도 얘기했지만 Solr에서는 DB 데이터를 가져오는 DataImportHandler 기능을 제공 하고 있습니다. DataImportHandler Commands를 보면 총 5개의 명령을 제공하고 있는데, 그중 색인을 실행하는 명령은 full-import와 delta-import입니다. full-import 명령은 DB의 모든 데이터를 색인 하는 것을 말합니다. 색인 시작할 때의 시간을 conf/dataimport.properties에 저장하고 이때 저장한 시간은 delta-import 할때 사용됩니다. 전체 색인한다고 말합니다. delta-import 명령은 특정 시간 이후로 생성/삭제된 데이터를 색인 하는 것을 말합니다. 특정 시간이란 full-import 시작한 시간, delta-import가 최근 종료한 시간을 말합니다. full-import와는 다르게 delta-import가 종료된 시간을 conf/dataimport.properties에 저장합니다. 증분 색인 혹은 동적 색인이라고 하는데 여기서는 증분 색인이라고 얘기하겠습니다. 두 명령을 이용하여 JANDI의 메시지/파일을 색인 하기 위한 삽질 경험을 적었습니다.. 첫 번째 삽질full-import는 현재 active인 데이터를 가져올 수 있도록 query attribute에 mongo query를 작성하고, delta-import 는 특정 시간 이후에 생성된 데이터를 가져올 수 있도록 deltaQuery attribute에 mongo query를 작성합니다. 또한 deltaQuery로 가져온 id의 문서를 가져올 수 있도록 deltaImportQuery attribute에 mongo query를 작성하고, 특정 시간 이후에 삭제된 데이터를 가져올 수 있도록 deletedPkQuery 에도 mongo query를 작성합니다.<!-- data-config.xml --> <?xml version="1.0" encoding="UTF-8" ?> 정상적으로 동작은 했지만, 색인 속도가 실제 서비스에 적용하기 힘들 정도였습니다. 실행되는 mongo query를 확인했는데 다음과 같이 동작하였습니다.특정 시간 이후에 생성된 데이터를 색인하기 위해 약 (새로 생성된 문서개수 + 1) 번의 mongo query가 실행되었습니다. (batch size와 문서 갯수에 따라 늘어날 수도 있습니다.) 메신저 서비스 특성상 각각의 문서 크기는 작지만 증가량이 빠르므로 위 방식으로는 운영 할 수 없었습니다. 그래서 delta-import using full-import 를 참고해서 두 번째 삽질을 시작 하였습니다.. 두 번째 삽질full-imoprt 명령을 실행할 때 clean=false queryString을 추가하고 data-config.xml query attribute를 수정하는 방법으로 증분 색인 하도록 수정했습니다. 특정 시간 이후 생성된 문서를 가져오는 attribute인 deltaQuery와 deltaImportQuery 는 필요가 없어 지웠습니다.<!-- data-config.xml --> <?xml version="1.0" encoding="UTF-8" ?> <!-- if query="" then it imports everything --> 전체 색인은 /dataimport?command=full-import&clean=true 로 실행하고, 증분 색인은 /dataimport?command=full-import&clean=false(생성된 문서)와 …/dataimport?command=delta-import&commit=true(삭제된 문서)로 실행하도록 했습니다.정상적인 것 같았지만, 문제가 있었습니다.full-import, delta-import 명령을 실행하면 conf/dataimport.properties 파일에 전체 색인이 실행한 시작 시각 혹은 증분 색인이 최근 종료한 시간이 “last_index_time” key로 저장됩니다. 첫 번째 삽질에서 증분 색인시 delta-import 명령 한 번으로 생성된 문서와 삭제된 문서를 처리했지만, full-import와 delta-import 두개의 명령으로 증분 색인이 동작하면서 생성된 문서를 처리할 때도 last_index_time이 갱신되고 삭제된 문서를 처리할 때도 last_index_time이 갱신되었습니다.예를 들면증분색인 동작이 1분마다 삭제된 문서를 처리하고, 5분마다 생성된 문서를 처리 한다고 가정해보겠습니다. 3시 13분 14초에 delta-import가 완료되어 last_index_time에 저장되고, 다음 delta-import가 실행되기 전 3시 13분 50초에 full-import가 완료되어 last_index_time이 갱신되었다면, 3시 13분 14초부터 3시 13분 50초 사이에 삭제된 문서는 처리를 못 하는 경우가 발생합니다.Solr에서 dataimport.properties에 기록하는 부분을 수정하는 방법과 전체/증분 색인을 동작시키는 Solr 외부에서 특정 색인 시간을 관리하는 방법이 있었는데 Solr를 수정하는 건 생각보다 큰 작업이라 판단되어 외부에서 관리하는 방법으로 세 번째 삽질을 시작하였습니다.. 세 번째 삽질전체/증분 색인을 주기적으로 동작 시키는 곳에서 full-import&clean=false(생성된 문서) 처리할 때 필요한 마지막으로 색인 된 문서 id와 delta-import(삭제된 문서) 처리할 때 필요한 마지막으로 색인 된 시간을 관리하도록 개발하였습니다. 증분 색인 시 full-import&clean=false를 실행하기 전에 현재 색인 된 마지막 id 조회 후 해당 id보다 큰 데이터를 처리하도록 하였고, delta-import를 마지막으로 마친 시간을 따로 저장하다가 delta-import 실행 시 해당 시간을 전달하는 방법으로 수정하였습니다.<!-- data-config.xml --> <?xml version="1.0" encoding="UTF-8" ?> 마치며튜닝의 끝은 순정이라는 말이 있는데 IT 기술은 예외인 것 같습니다. 현재는 Solr의 기본 기능만으로 구성했지만, 고객에게 더 나은 서비스를 제공할 수 있는 시작점으로 생각하고, JANDI 서비스에 맞게 끊임없이 발전해나가겠습니다.감사합니다.참고Getting Started with SolrApache Solr 5.5.0 Reference Guide PDFApache Solr 6.1 - Analyzers, Tokenizers and FiltersRebalance API for SolrCloud issueYonik Blog#토스랩 #잔디 #JANDI #개발자 #개발팀 #개발후기 #인사이트
조회수 1980

나는 이쁜 데일리룩을 보고 싶은걸? \w pose estimation

안녕하세요. 스타일쉐어 백엔드 개발자 김동현입니다.2018년의 스타일쉐어에서는 뷰티, 중고 그리고 데일리룩이라는 피드가 추가로 등장했는데요, 그중 제가 작업했던 데일리룩 피드를 만들게 된 배경과 개발 방향에 대해 공유드리고자 합니다.스타일쉐어 데일리룩#데일리룩 #ootd / 타자 치는 것은 귀찮아데일리룩에 관련된 스타일들만 뽑아내는 방법 중에 가장 간단한 방법은 텍스트로 분리해내는 방법이었을 것입니다.하지만 #데일리룩 #ootd는 사진이나 내용이 관계가 없더라도 들어가 있는 경우가 많았습니다.또한 위의 피드처럼 정성스러운 글을 써주는 유저도 많긴 했지만 자신의 데일리 로그를 남기면서 글을 작성하지 않는 경우도 더러 있었습니다.즉, 단순히 텍스트로만 구별해내기에는 이미지에 대한 질을 확신할 수 없었고, 텍스트가 주된 서비스가 아니다 보니 설명 없는 좋은 이미지들이 많았는데요.우리는 이 이미지들을 놓치고 싶지 않았습니다.그래서 결과적으로 텍스트 대신 이미지를 사용하는 방향을 선택하게 되었습니다.이미지로 어떻게 구별해낼까?다행히도 R-CNN의 높은 인식률과 Pre-Trained 된 모델의 label 중 person이 이미 학습되어있던 터라 별도의 Transfer Learning 없이 이미지 내에서 body parts가 있는지 없는지 찾아내는 것은 아주 어렵지 않았습니다.다만 문제가 있다면 body parts에 들어가는 모든 부분을 person이라고 예측하던 부분이었죠.예를 들자면 아래와 같습니다.다음과 같이 제가 사용한 모델에서는 body parts를 person이라는 라벨로 처리하고 있었습니다.단순히 R-CNN의 person 라벨만을 믿기에는 의도했던 데일리룩 외에도 너무나도 많은 것들이 데일리룩이라는 이름으로 필터링될 것 같았습니다.그래서 또 다른 필터가 하나 더 필요하다는 생각이 들었습니다.Pose EstimationBody Parts 중 우리가 원하는 부분이 사진에 있으면 좋겠다!라는 생각을 곰곰이 하다 보니 우연히 머릿속에 스쳐 지나가는 하나의 장면이 있었습니다.Source: http://graphics.berkeley.edu/papers/Kirk-SPE-2005-06/바로 3D 모델링 중에서 Motion Tracker 에 관련된 장면이었는데요. 이것을 Tracker가 아니라 이미지에서 stick figure를 뽑아낼 수 있으면 되지 않을까?라는 생각이 들었습니다.놀라운 딥러닝의 세계에는 이미 여러 명의 Stick Figure를 뽑아낼 수 있는 경지에 도달해 있었습니다.Source: https://github.com/ZheC/Realtime_Multi-Person_Pose_EstimationPose Estimation 딥러닝 모델을 사용하여 아래와 같은 결과물을 얻어낼 수 있었는데요.이미지 내의 Body Parts의 존재 여부를 알게 되었으니 우리가 원하는 Body Parts가 이미지 내에 있는지 검사할 수 있게 되었습니다.하지만 해당 모델이 마냥 가볍지는 않았기에 사용자의 업로드가 많은 순간에는 예측 Task가 밀리기 시작했습니다.그래서 아주 단순하지만, 효과적인 아이디어들을 적용하였는데요.pose estimation을 하기 전에 R-CNN을 돌린 후 person으로 예측된 bounding box가 있다면 pose estimation 모델을 돌리도록 했습니다.하지만 위의 필터를 통했음에도 원하는 결과물이 안 나오는 경우가 종종 있었는데요.바로 다음과 같은 경우입니다.생각보다 작은 사람의 stick figure도 잘 추출 내어서 해수욕장으로 떠나 찍은 사진 속의 저 멀리 있는 휴양객을 데일리룩으로 잡는 일이 종종 발생했거든요.그래서 위의 조건에 더불어서 person이라고 예측된 bounding box size가 전체 이미지 크기 대비 n % 이상의 크기 일 경우 Pose Estimation을 진행하자는 것이었죠.적당한 크기 이상의 데일리룩들을 뽑아내고 싶었고 사람이 너무 작아서 안 보이는 경우도 피할 수 있었습니다.빠른 분류 속도는 덤이었고요.덕분에 유저들이 올린 콘텐츠 중 데일리룩이라는 범주에 속하는 콘텐츠를 잘 뽑아낼 수 있었습니다.아래는 위의 과정을 거쳐서 Pose Estimation까지 처리되어 데일리룩 사진이라고 판별된 이미지입니다.이다음으론 무엇을 더 해볼 수 있을까요?사진 속의 자세를 알 수 있게 되었으니 좀 더 재밌는 것을 할 수 있을 것 같은데요.예를 들면 K-Means를 적용하면 비슷한 모습의 데일리룩들만 모아볼 수도 있고 스타일쉐어 유저들이 자주 찍는 자세 라던가 유저 별 자세 선호도 등등 재밌는 것들을 할 수 있을 것 같습니다.날 따라 해 봐요 같은 것도 해볼 수 있겠네요 :)같이 해보지 않을래요?아직도 재밌는 것들이 많이 남은 스타일쉐어 에서는 더 많은 것을 하기 위해 개발자분들을 모시고 있습니다 :)백엔드 개발자라고 해서 백엔드 개발에만 국한되지 않고 하고 싶은 것들을 해도 된다, 할 수 있다고 이야기해 주는 회사라고 생각합니다.스타일쉐어를 좀 더 알고 싶으시다면 여기를 눌러 주세요 :)#스타일쉐어 #개발팀 #개발자 #백엔드개발 #개발인사이트 #경험공유 #후기
조회수 3284

Attention is all you need paper 뽀개기

이번 포스팅에서는 포자랩스에서 핵심적으로 쓰고 있는 모델인 transformer의 논문을 요약하면서 추가적인 기법들도 설명드리겠습니다.Why?Long-term dependency problemsequence data를 처리하기 위해 이전까지 많이 쓰이던 model은 recurrent model이었습니다. recurrent model은 t번째에 대한 output을 만들기 위해, t번째 input과 t-1번째 hidden state를 이용했습니다. 이렇게 한다면 자연스럽게 문장의 순차적인 특성이 유지됩니다. 문장을 쓸 때 뒤의 단어부터 쓰지 않고 처음부터 차례차례 쓰는 것과 마찬가지인것입니다.하지만 recurrent model의 경우 많은 개선점이 있었음에도 long-term dependency에 취약하다는 단점이 있었습니다. 예를 들어, “저는 언어학을 좋아하고, 인공지능중에서도 딥러닝을 배우고 있고 자연어 처리에 관심이 많습니다.”라는 문장을 만드는 게 model의 task라고 해봅시다. 이때 ‘자연어’라는 단어를 만드는데 ‘언어학’이라는 단어는 중요한 단서입니다.그러나, 두 단어 사이의 거리가 가깝지 않으므로 model은 앞의 ‘언어학’이라는 단어를 이용해 자연어’라는 단어를 만들지 못하고, 언어학 보다 가까운 단어인 ‘딥러닝’을 보고 ‘이미지’를 만들 수도 있는 거죠. 이처럼, 어떤 정보와 다른 정보 사이의 거리가 멀 때 해당 정보를 이용하지 못하는 것이 long-term dependency problem입니다.recurrent model은 순차적인 특성이 유지되는 뛰어난 장점이 있었음에도, long-term dependency problem이라는 단점을 가지고 있었습니다.이와 달리 transformer는 recurrence를 사용하지 않고 대신 attention mechanism만을 사용해 input과 output의 dependency를 포착해냈습니다.Parallelizationrecurrent model은 학습 시, t번째 hidden state를 얻기 위해서 t-1번째 hidden state가 필요했습니다. 즉, 순서대로 계산될 필요가 있었습니다. 그래서 병렬 처리를 할 수 없었고 계산 속도가 느렸습니다.하지만 transformer에서는 학습 시 encoder에서는 각각의 position에 대해, 즉 각각의 단어에 대해 attention을 해주기만 하고, decoder에서는 masking 기법을 이용해 병렬 처리가 가능하게 됩니다. (masking이 어떤 것인지는 이후에 설명해 드리겠습니다)Model ArchitectureEncoder and Decoder structureencoder는 input sequence (x1,...,xn)<math>(x1,...,xn)</math>에 대해 다른 representation인 z=(z1,...,zn)<math>z=(z1,...,zn)</math>으로 바꿔줍니다.decoder는 z를 받아, output sequence (y1,...,yn)<math>(y1,...,yn)</math>를 하나씩 만들어냅니다.각각의 step에서 다음 symbol을 만들 때 이전에 만들어진 output(symbol)을 이용합니다. 예를 들어, “저는 사람입니다.”라는 문장에서 ‘사람입니다’를 만들 때, ‘저는’이라는 symbol을 이용하는 거죠. 이런 특성을 auto-regressive 하다고 합니다.Encoder and Decoder stacksEncoderN개의 동일한 layer로 구성돼 있습니다. input $x$가 첫 번째 layer에 들어가게 되고, layer(x)<math>layer(x)</math>가 다시 layer에 들어가는 식입니다.그리고 각각의 layer는 두 개의 sub-layer, multi-head self-attention mechanism과 position-wise fully connected feed-forward network를 가지고 있습니다.이때 두 개의 sub-layer에 residual connection을 이용합니다. residual connection은 input을 output으로 그대로 전달하는 것을 말합니다. 이때 sub-layer의 output dimension을 embedding dimension과 맞춰줍니다. x+Sublayer(x)<math>x+Sublayer(x)</math>를 하기 위해서, 즉 residual connection을 하기 위해서는 두 값의 차원을 맞춰줄 필요가 있습니다. 그 후에 layer normalization을 적용합니다.Decoder역시 N개의 동일한 layer로 이루어져 있습니다.encoder와 달리 encoder의 결과에 multi-head attention을 수행할 sub-layer를 추가합니다.마찬가지로 sub-layer에 residual connection을 사용한 뒤, layer normalization을 해줍니다.decoder에서는 encoder와 달리 순차적으로 결과를 만들어내야 하기 때문에, self-attention을 변형합니다. 바로 masking을 해주는 것이죠. masking을 통해, position i<math>i</math> 보다 이후에 있는 position에 attention을 주지 못하게 합니다. 즉, position i<math>i</math>에 대한 예측은 미리 알고 있는 output들에만 의존을 하는 것입니다.위의 예시를 보면, a를 예측할 때는 a이후에 있는 b,c에는 attention이 주어지지 않는 것입니다. 그리고 b를 예측할 때는 b이전에 있는 a만 attention이 주어질 수 있고 이후에 있는 c는 attention이 주어지지 않는 것이죠.Embeddings and Softmaxembedding 값을 고정시키지 않고, 학습을 하면서 embedding값이 변경되는 learned embedding을 사용했습니다. 이때 input과 output은 같은 embedding layer를 사용합니다.또한 decoder output을 다음 token의 확률로 바꾸기 위해 learned linear transformation과 softmax function을 사용했습니다. learned linear transformation을 사용했다는 것은 decoder output에 weight matrix W<math>W</math>를 곱해주는데, 이때 W<math>W</math>가 학습된다는 것입니다.Attentionattention은 단어의 의미처럼 특정 정보에 좀 더 주의를 기울이는 것입니다.예를 들어 model이 수행해야 하는 task가 번역이라고 해봅시다. source는 영어이고 target은 한국어입니다. “Hi, my name is poza.”라는 문장과 대응되는 “안녕, 내 이름은 포자야.”라는 문장이 있습니다. model이 이름은이라는 token을 decode할 때, source에서 가장 중요한 것은 name입니다.그렇다면, source의 모든 token이 비슷한 중요도를 갖기 보다는 name이 더 큰 중요도를 가지면 되겠죠. 이때, 더 큰 중요도를 갖게 만드는 방법이 바로 attention입니다.Scaled Dot-Product Attention해당 논문의 attention을 Scaled Dot-Product Attention이라고 부릅니다. 수식을 살펴보면 이렇게 부르는 이유를 알 수 있습니다.Attention(Q,K,V)=softmax(QKT√dk)V<math>Attention(Q,K,V)=softmax(QKTdk)V</math>먼저 input은 dk<math>dk</math> dimension의 query와 key들, dv<math>dv</math> dimension의 value들로 이루어져 있습니다.이때 모든 query와 key에 대한 dot-product를 계산하고 각각을 √dk<math>dk</math>로 나누어줍니다. dot-product를 하고 √dk<math>dk</math>로 scaling을 해주기 때문에 Scaled Dot-Product Attention인 것입니다. 그리고 여기에 softmax를 적용해 value들에 대한 weights를 얻어냅니다.key와 value는 attention이 이루어지는 위치에 상관없이 같은 값을 갖게 됩니다. 이때 query와 key에 대한 dot-product를 계산하면 각각의 query와 key 사이의 유사도를 구할 수 있게 됩니다. 흔히 들어본 cosine similarity는 dot-product에서 vector의 magnitude로 나눈 것입니다. √dk<math>dk</math>로 scaling을 해주는 이유는 dot-products의 값이 커질수록 softmax 함수에서 기울기의 변화가 거의 없는 부분으로 가기 때문입니다.softmax를 거친 값을 value에 곱해준다면, query와 유사한 value일수록, 즉 중요한 value일수록 더 높은 값을 가지게 됩니다. 중요한 정보에 더 관심을 둔다는 attention의 원리에 알맞은 것입니다.Multi-Head Attention위의 그림을 수식으로 나타내면 다음과 같습니다.MultiHead(Q,K,V)=Concat(head1,...,headh)WO<math>MultiHead(Q,K,V)=Concat(head1,...,headh)WO</math>where headi=Attention(QWQi,KWKi,VWVi)dmodel<math>dmodel</math> dimension의 key, value, query들로 하나의 attention을 수행하는 대신 key, value, query들에 각각 다른 학습된 linear projection을 h번 수행하는 게 더 좋다고 합니다. 즉, 동일한 Q,K,V<math>Q,K,V</math>에 각각 다른 weight matrix W<math>W</math>를 곱해주는 것이죠. 이때 parameter matrix는 WQi∈Rdmodelxdk,WKi∈Rdmodelxdk,WVi∈Rdmodelxdv,WOi∈Rhdvxdmodel<math>WiQ∈Rdmodelxdk,WiK∈Rdmodelxdk,WiV∈Rdmodelxdv,WiO∈Rhdvxdmodel</math>입니다.순서대로 query, key, value, output에 대한 parameter matrix입니다. projection이라고 하는 이유는 각각의 값들이 parameter matrix와 곱해졌을 때 dk,dv,dmodel<math>dk,dv,dmodel</math>차원으로 project되기 때문입니다. 논문에서는 dk=dv=dmodel/h<math>dk=dv=dmodel/h</math>를 사용했는데 꼭 dk<math>dk</math>와 dv<math>dv</math>가 같을 필요는 없습니다.이렇게 project된 key, value, query들은 병렬적으로 attention function을 거쳐 dv<math>dv</math>dimension output 값으로 나오게 됩니다.그 다음 여러 개의 head<math>head</math>를 concatenate하고 다시 projection을 수행합니다. 그래서 최종적인 dmodel<math>dmodel</math> dimension output 값이 나오게 되는거죠.각각의 과정에서 dimension을 표현하면 아래와 같습니다.*dQ,dK,dV<math>dQ,dK,dV</math>는 각각 query, key, value 개수Self-Attentionencoder self-attention layerkey, value, query들은 모두 encoder의 이전 layer의 output에서 옵니다. 따라서 이전 layer의 모든 position에 attention을 줄 수 있습니다. 만약 첫번째 layer라면 positional encoding이 더해진 input embedding이 됩니다.decoder self-attention layerencoder와 비슷하게 decoder에서도 self-attention을 줄 수 있습니다. 하지만 i<math>i</math>번째 output을 다시 i+1<math>i+1</math>번째 input으로 사용하는 auto-regressive한 특성을 유지하기 위해 , masking out된 scaled dot-product attention을 적용했습니다.masking out이 됐다는 것은 i<math>i</math>번째 position에 대한 attention을 얻을 때, i<math>i</math>번째 이후에 있는 모든 position은 Attention(Q,K,V)=softmax(QKT√dk)V<math>Attention(Q,K,V)=softmax(QKTdk)V</math>에서 softmax의 input 값을 −∞<math>−∞</math>로 설정한 것입니다. 이렇게 한다면, i<math>i</math>번째 이후에 있는 position에 attention을 주는 경우가 없겠죠.Encoder-Decoder Attention Layerquery들은 이전 decoder layer에서 오고 key와 value들은 encoder의 output에서 오게 됩니다. 그래서 decoder의 모든 position에서 input sequence 즉, encoder output의 모든 position에 attention을 줄 수 있게 됩니다.query가 decoder layer의 output인 이유는 query라는 것이 조건에 해당하기 때문입니다. 좀 더 풀어서 설명하면, ‘지금 decoder에서 이런 값이 나왔는데 무엇이 output이 돼야 할까?’가 query인 것이죠.이때 query는 이미 이전 layer에서 masking out됐으므로, i번째 position까지만 attention을 얻게 됩니다.이 같은 과정은 sequence-to-sequence의 전형적인 encoder-decoder mechanisms를 따라한 것입니다.*모든 position에서 attention을 줄 수 있다는 게 이해가 안되면 링크를 참고하시기 바랍니다.Position-wise Feed-Forward Networksencoder와 decoder의 각각의 layer는 아래와 같은 fully connected feed-forward network를 포함하고 있습니다.position 마다, 즉 개별 단어마다 적용되기 때문에 position-wise입니다. network는 두 번의 linear transformation과 activation function ReLU로 이루어져 있습니다.FFN(x)=max(0,xW1+b1)W2+b2x<math>x</math>에 linear transformation을 적용한 뒤, ReLU(max(0,z))<math>ReLU(max(0,z))</math>를 거쳐 다시 한번 linear transformation을 적용합니다.이때 각각의 position마다 같은 parameter W,b<math>W,b</math>를 사용하지만, layer가 달라지면 다른 parameter를 사용합니다.kernel size가 1이고 channel이 layer인 convolution을 두 번 수행한 것으로도 위 과정을 이해할 수 있습니다.Positional Encodingtransfomer는 recurrence도 아니고 convolution도 아니기 때문에, 단어의sequence를 이용하기 위해서는 단어의 position에 대한 정보를 추가해줄 필요가 있었습니다.그래서 encoder와 decoder의 input embedding에 positional encoding을 더해줬습니다.positional encoding은 dmodel<math>dmodel</math>(embedding 차원)과 같은 차원을 갖기 때문에 positional encoding vector와 embedding vector는 더해질 수 있습니다.논문에서는 다른 *frequency를 가지는 sine과 cosine 함수를 이용했습니다.*주어진 구간내에서 완료되는 cycle의 개수PE(pos,2i)=sin(pos/100002i/dmodel)<math>PE(pos,2i)=sin(pos/100002i/dmodel)</math>PE(pos,2i+1)=cos(pos/100002i/dmodel)<math>PE(pos,2i+1)=cos(pos/100002i/dmodel)</math>pos<math>pos</math>는 position ,i<math>i</math>는 dimension 이고 주기가 100002i/dmodel⋅2π<math>100002i/dmodel⋅2π</math>인 삼각 함수입니다. 즉, pos<math>pos</math>는 sequence에서 단어의 위치이고 해당 단어는 i<math>i</math>에 0부터 dmodel2<math>dmodel2</math>까지를 대입해 dmodel<math>dmodel</math>차원의 positional encoding vector를 얻게 됩니다. k=2i+1<math>k=2i+1</math>일 때는 cosine 함수를, k=2i<math>k=2i</math>일 때는 sine 함수를 이용합니다. 이렇게 positional encoding vector를 pos<math>pos</math>마다 구한다면 비록 같은 column이라고 할지라도 pos<math>pos</math>가 다르다면 다른 값을 가지게 됩니다. 즉, pos<math>pos</math>마다 다른 pos<math>pos</math>와 구분되는 positional encoding 값을 얻게 되는 것입니다.PEpos=[cos(pos/1),sin(pos/100002/dmodel),cos(pos/10000)2/dmodel,...,sin(pos/10000)]<math>PEpos=[cos(pos/1),sin(pos/100002/dmodel),cos(pos/10000)2/dmodel,...,sin(pos/10000)]</math>이때 PEpos+k<math>PEpos+k</math>는 PEpos<math>PEpos</math>의 linear function으로 나타낼 수 있습니다. 표기를 간단히 하기 위해 c=100002idmodel<math>c=100002idmodel</math>라고 해봅시다. sin(a+b)=sin(a)cos(b)+cos(a)sin(b)<math>sin(a+b)=sin(a)cos(b)+cos(a)sin(b)</math>이고 cos(a+b)=cos(a)cos(b)−sin(a)sin(b)<math>cos(a+b)=cos(a)cos(b)−sin(a)sin(b)</math> 이므로 다음이 성립합니다.PE(pos,2i)=sin(posc)<math>PE(pos,2i)=sin(posc)</math>PE(pos,2i+1)=cos(posc)<math>PE(pos,2i+1)=cos(posc)</math>PE(pos+k,2i)=sin(pos+kc)=sin(posc)cos(kc)+cos(posc)sin(kc)=PE(pos,2i)cos(kc)+cos(posc)sin(kc)<math>PE(pos+k,2i)=sin(pos+kc)=sin(posc)cos(kc)+cos(posc)sin(kc)=PE(pos,2i)cos(kc)+cos(posc)sin(kc)</math>PE(pos+k,2i+1)=cos(pos+kc)=cos(posc)cos(kc)−sin(posc)sin(kc)=PE(pos,2i+1)cos(kc)−sin(posc)sin(kc)<math>PE(pos+k,2i+1)=cos(pos+kc)=cos(posc)cos(kc)−sin(posc)sin(kc)=PE(pos,2i+1)cos(kc)−sin(posc)sin(kc)</math>이런 성질 때문에 model이 relative position에 의해 attention하는 것을 더 쉽게 배울 수 있습니다.논문에서는 학습된 positional embedding 대신 sinusoidal version을 선택했습니다. 만약 학습된 positional embedding을 사용할 경우 training보다 더 긴 sequence가 inference시에 입력으로 들어온다면 문제가 되지만 sinusoidal의 경우 constant하기 때문에 문제가 되지 않습니다. 그냥 좀 더 많은 값을 계산하기만 하면 되는거죠.Trainingtraining에 사용된 기법들을 알아보겠습니다.Optimizer많이 쓰이는 Adam optimizer를 사용했습니다.특이한 점은 learning rate를 training동안 고정시키지 않고 다음 식에 따라 변화시켰다는 것입니다.lrate=d−0.5model⋅min(step_num−0.5,step_num⋅warmup_steps−1.5)warmup_step<math>warmup_step</math>까지는 linear하게 learning rate를 증가시키다가, warmup_step<math>warmup_step</math> 이후에는 step_num<math>step_num</math>의 inverse square root에 비례하도록 감소시킵니다.이렇게 하는 이유는 처음에는 학습이 잘 되지 않은 상태이므로 learning rate를 빠르게 증가시켜 변화를 크게 주다가, 학습이 꽤 됐을 시점에 learning rate를 천천히 감소시켜 변화를 작게 주기 위해서입니다.RegularizationResidual ConnectionIdentity Mappings in Deep Residual Networks라는 논문에서 제시된 방법이고, 아래의 수식이 residual connection을 나타낸 것입니다.yl=h(xl)+F(xl,Wl)<math>yl=h(xl)+F(xl,Wl)</math>xl+1=f(yl)<math>xl+1=f(yl)</math>이때 h(xl)=xl<math>h(xl)=xl</math>입니다. 논문 제목에서 나온 것처럼 identity mapping을 해주는 것이죠.특정한 위치에서의 xL<math>xL</math>을 다음과 같이 xl<math>xl</math>과 residual 함수의 합으로 표시할 수 있습니다.x2=x1+F(x1,W1)<math>x2=x1+F(x1,W1)</math>x3=x2+F(x2,W2)=x1+F(x1,W1)+F(x2,W2)<math>x3=x2+F(x2,W2)=x1+F(x1,W1)+F(x2,W2)</math>xL=xl+L−1∑i=1F(xi,Wi)<math>xL=xl+∑i=1L−1F(xi,Wi)</math>그리고 미분을 한다면 다음과 같이 됩니다.σϵσxl=σϵσxLσxLσxl=σϵσxL(1+σσxlL−1∑i=1F(xi,Wi))<math>σϵσxl=σϵσxLσxLσxl=σϵσxL(1+σσxl∑i=1L−1F(xi,Wi))</math>이때, σϵσxL<math>σϵσxL</math>은 상위 layer의 gradient 값이 변하지 않고 그대로 하위 layer에 전달되는 것을 보여줍니다. 즉, layer를 거칠수록 gradient가 사라지는 vanishing gradient 문제를 완화해주는 것입니다.또한 forward path나 backward path를 간단하게 표현할 수 있게 됩니다.Layer NormalizationLayer Normalization이라는 논문에서 제시된 방법입니다.μl=1HH∑i=1ali<math>μl=1H∑i=1Hail</math>σl= ⎷1HH∑i=1(ali−μl)2<math>σl=1H∑i=1H(ail−μl)2</math>같은 layer에 있는 모든 hidden unit은 동일한 μ<math>μ</math>와 σ<math>σ</math>를 공유합니다.그리고 현재 input xt<math>xt</math>, 이전의 hidden state ht−1<math>ht−1</math>, at=Whhht−1+Wxhxt<math>at=Whhht−1+Wxhxt</math>, parameter g,b<math>g,b</math>가 있을 때 다음과 같이 normalization을 해줍니다.ht=f[gσt⊙(at−μt)+b]<math>ht=f[gσt⊙(at−μt)+b]</math>이렇게 한다면, gradient가 exploding하거나 vanishing하는 문제를 완화시키고 gradient 값이 안정적인 값을 가짐로 더 빨리 학습을 시킬 수 있습니다.(논문에서 recurrent를 기준으로 설명했으므로 이에 따랐습니다.)DropoutDropout: a simple way to prevent neural networks from overfitting라는 논문에서 제시된 방법입니다.dropout이라는 용어는 neural network에서 unit들을 dropout하는 것을 가리킵니다. 즉, 해당 unit을 network에서 일시적으로 제거하는 것입니다. 그래서 다른 unit과의 모든 connection이 사라지게 됩니다. 어떤 unit을 dropout할지는 random하게 정합니다.dropout은 training data에 overfitting되는 문제를 어느정도 막아줍니다. dropout된 unit들은 training되지 않는 것이니 training data에 값이 조정되지 않기 때문입니다.Label SmoothingRethinking the inception architecture for computer vision라는 논문에서 제시된 방법입니다.training동안 실제 정답인 label의 logit은 다른 logit보다 훨씬 큰 값을 갖게 됩니다. 이렇게 해서 model이 주어진 input x<math>x</math>에 대한 label y<math>y</math>를 맞추는 것이죠.하지만 이렇게 된다면 문제가 발생합니다. overfitting될 수도 있고 가장 큰 logit을 가지는 것과 나머지 사이의 차이를 점점 크게 만들어버립니다. 결국 model이 다른 data에 적응하는 능력을 감소시킵니다.model이 덜 confident하게 만들기 위해, label distribution q(k∣x)=δk,y<math>q(k∣x)=δk,y</math>를 (k가 y일 경우 1, 나머지는 0) 다음과 같이 대체할 수 있습니다.q′(k|x)=(1−ϵ)δk,y+ϵu(k)<math>q′(k|x)=(1−ϵ)δk,y+ϵu(k)</math>각각 label에 대한 분포 u(k)<math>u(k)</math>, smooting parameter ϵ<math>ϵ</math>입니다. 위와 같다면, k=y인 경우에도 model은 p(y∣x)=1<math>p(y∣x)=1</math>이 아니라 p(y∣x)=(1−ϵ)<math>p(y∣x)=(1−ϵ)</math>이 되겠죠. 100%의 확신이 아닌 그보다 덜한 확신을 하게 되는 것입니다.Conclusiontransformer는 recurrence를 이용하지 않고도 빠르고 정확하게 sequential data를 처리할 수 있는 model로 제시되었습니다.여러가지 기법이 사용됐지만, 가장 핵심적인 것은 encoder와 decoder에서 attention을 통해 query와 가장 밀접한 연관성을 가지는 value를 강조할 수 있고 병렬화가 가능해진 것입니다.Referencehttp://www.whydsp.org/280http://mlexplained.com/2017/12/29/attention-is-all-you-need-explained/http://openresearch.ai/t/identity-mappings-in-deep-residual-networks/47https://m.blog.naver.com/PostView.nhn?blogId=laonple&logNo=220793640991&proxyReferer=https://www.google.co.kr/https://www.researchgate.net/figure/Sample-of-a-feed-forward-neural-network_fig1_234055177https://arxiv.org/abs/1603.05027https://arxiv.org/abs/1607.06450http://jmlr.org/papers/volume15/srivastava14a.old/srivastava14a.pdfhttps://arxiv.org/pdf/1512.00567.pdf
조회수 1812

Amazon SageMaker는 처음이지?

Overview브랜디 랩스를 사랑해주시는 여러분, 안녕하세요. 개발자 오-연주입니다. 지난 4월, Brandi Back-end 개발자 분들과 코엑스에서 열렸던 AWS Summit(04.18 - 04.19)에 다녀왔습니다!여러 세션을 듣는 와중에 우연히 AI machine learning 를 쉽게 도와주는 Cloud Machine learning Flatform인 Amazon SageMaker에 대해 들었습니다. 듣던 중 머닝러닝에서 학습을 시켜 그 데이터로 ‘Brandi 서비스와 연관지으면 어떨까’ 라는 생각을 했는데요. 그래서 오늘은 많은 분들의 관심사인 머신러닝 학습관련 Amazon Amazon SageMaker에 대한 글을 쓰려고 합니다.sage는 마법사, 현자라는 의미입니다.sageMaker를 create하자!“자, 퐈이팅 넘치게 신나게 sagemaker를 create해볼까요!” 했는데…Seoul Region이 없다!현재 지원되는 리전은 아직 네 군데입니다. 저는 제일 있어 보이는 미국 동부의 버지니아를 선택하겠습니다.1] EU (Iceland) 2] US West (Oregon) 3] USEast (N. Virginia) 4] US East (Ohio)SageMaker를 create하기 전에는 학습할 데이터와 학습 모델을 저장할 S3 Bucket이 필요합니다.1. Default 값으로 S3를 만드세요.중요한 점은, bucket 이름이 “sagemaker-” 로 시작되어야 한다는 것입니다. 그래야 나중에 notebook instance가 어느 곳에 데이터를 저장할지 알 수 있습니다.Next, Create bucket 버튼을 누르다 보니, S3 Bucket이 생성되었습니다.2. Create notebook instance 버튼을 눌러 SageMaker를 만들어 봅시다!원하는 이름을 지어줍니다. 저는 machineLearningTest 라고 지었어요. IAM role 선택하는 부분에서 None을 눌러 Default 값으로 sageMaker를 만듭니다.인고의 Pending 시간3. Pending이 끝나고 “open” action을 선택하면 Jupyter가 열립니다.Jupyter(Jupyter Notebook)는 오픈 소스로 라이브 코드, 등식, 코드에 대한 시각화를 위해 사용됩니다. 또한 description을 위한 텍스트 문서(마크다운 등)를 지원하는 웹 어플리케이션입니다. 이렇게 하면 코드에 대한 문서화가 가능합니다. 이 글에서는 Jupyter Notebook을 통해 데이터를 학습하고, 그 데이터를 테스트하겠습니다. 제가 진행한 전체 코드 스크립트(entire script)는 이 글의 마지막 부분에 기술있으니 참고해 주세요.자, 이제 드디어 머신러닝 학습을 시킬 차례입니다. 머신러닝 학습에 꼭 필요한 키워드 두 가지를 뽑아봤는데요. - Dataset: 정제된 데이터와 그 데이터에 대한 label을 정리해 놓은 데이터 모음      - Machine learning Algorithm: 기계학습 알고리즘 우리는 MNIST 데이터셋을 k-means 알고리즘으로 학습시킬 겁니다.1)MNIST Dataset기계학습 알고리즘을 사용할 때 가장 기본적으로 테스트하는 데이터셋으로 MNIST 데이터셋이 있습니다. 이것은 사람이 0부터 9까지 숫자 중 하나를 손글씨로 쓴 이미지 데이터와, 해당 이미지에 대한 레이블(0 - 9)이 6만 개 들어있는 학습 데이터셋입니다. 각 이미지는 가로와 세로가 각각 28 픽셀로서, 각 픽셀은 0부터 255 사이의 숫자가 있습니다. 다시 말해, 하나의 이미지는 28 x 28 = 784개의 숫자로 이루어진 데이터입니다. 하나의 이미지를 나타내는 데이터의 array > length가 784라고 표현할 수 있겠네요.MNIST dataset2)k-means지금 만든 SageMaker 학습 알고리즘은 AWS 튜토리얼에서 제시한 K-means를 사용할 예정입니다. k-means는 label 없이, 즉 정답을 모르는 상태로 학습을 하는 비지도 학습 (unsupervised learning) 알고리즘 중 가장 쉽고 많이 쓰입니다. 정답을 모르니, ‘비슷한 애들끼리 뭉쳐봐’ 라고 하고, 알고리즘은 비슷한 친구들끼리 뭉쳐 놓습니다. k-means에서 k는 ‘k개 덩어리로 뭉쳐주세요’라고 제시하는 숫자입니다. 우리는 0부터 9까지 비슷한 친구들끼리 모이게 하고 싶으니 k=10을 쓸 겁니다.지금부터 해야 할 TO DO!1. MNIST 데이터셋을 다운로드받고, 우리가 학습시키기 좋도록 정제하기(preprocessing)2. Amazon SageMaker를 통하여 데이터 학습시키기(training job)3. Amazon SageMaker를 통하여 학습된 데이터를 배포하기(Deploy the model)4. 배포된 모델에 요청을 보내 테스트 데이터에 대한 예측값을 받아오기(inference)4. Jupyter 노트북 인스턴스 생성하기Jupyter에 New Notebook(conda_python3)을 선택해 새로운 노트북을 생성합니다.5. 학습시키기 위한 기본 셋팅드디어 코딩 시작입니다! (의욕활활) 초기 설정해두었던 IAM role, S3 Bucket, MNIST 다운로드, 다운받은 데이터 등을 확인하세요. 글보다 코드로 주석을 보는 게 가독성이 더 좋습니다. 아래 노트북을 통해 마크다운, 주석처리를 통해 description을 해두었으니 참고 바랍니다.외부에서 MNIST 다운로드가 쉽도록 한 url로 MNIST를 다운받는데 성공했습니다. MNIST 데이터셋 내용물 중 하나를 jupyter notebook에 그려서 제대로 다운 받았는지 show_digit() 함수를 작성해 확인하겠습니다.서른 번째 데이터는 누군가 3을 손글씨로 쓴 이미지입니다.6. 머신러닝 학습하기이 세션에서는 기계학습 알고리즘 설정, 학습할 데이터 경로를 지정하겠습니다. 그 후 MNIST 학습 데이터를 S3 버킷에 옮겨 저장합니다.kmeans.fit() 함수를 호출해 직접 학습을 시켜볼까요? 학습 과정은 상당히 오래 걸린다고 했는데 다행히 4분 만에 학습이 끝났습니다.여기서 잠깐! 여기서 k = 10에 대해서 조금 더 알아보도록 할게요. cluster란 한 지점에 점을 찍고 데이터 분석을 한 뒤, 비슷한 데이터들의 군집을 만들어 주는 것입니다. k-means가 진행되면서 각 cluster의 중심이 서로가 잘 뭉치는 방향으로 이동합니다. 직접 그려봤어요(부끄).7. 학습된 모델을 배포하기학습을 시키면 테스트를 하거나 사용할 수 있어야겠죠? 학습된 모델을 배포해 주세요.8. 배포된 모델 테스트 진행하기배포된 모델에 valid_set 데이터로 검증 데이터를 진행합니다..predict() 함수를 호출하면 새로운 이미지가 어떤 cluster에 속했는지 예측 결과를 알려줍니다. 가장 가까운 cluster가 0번이라고 예측 결과를 반환했네요. 또한 cluster 중심과의 거리는 5.85라고 알려줍니다. 여기서 중요한 점은 cluster 번호와 실제 숫자는 일치하지 않는다는 겁니다. 알고리즘은 임의로 cluster 중심에 번호를 매기는데, 꼭 0번 클러스터가 숫자 ’0’을 뭉쳐놓은 건 아니에요!9. 데이터 예측해보기더 많은 데이터를 예측해볼까요? valid set에 있는 100개 데이터를 예측해봅시다! 각 cluster에 가까운 데이터들이 쭉 선정되었습니다. 정확하지는 않지만 비슷한 숫자 모양들이 서로 군집되어 나타납니다. 0과 2같은 숫자들은 잘 표현되지만, 알고리즘이 9랑 4를 헷갈리거나 5와 3을 헷갈리는 듯 하네요.FASHION MNIST로 SageMaker 머신러닝 학습 및 예측해보기자, 이제 몸도 풀었으니 제가 하고 싶었던 패션 관련 머신러닝 학습 및 예측을 진행해볼게요. 마침 옷 그림으로 MNIST와 매우 비슷한 데이터를 만들어 놓은 fashion-MNIST라는 데이터셋을 발견했어요!1. 패션 관련 MNIST 다운로드 받기패션 MNIST 데이터셋을 우선 다운받아 볼게요! 다운로드는 여기에서 받을 수 있습니다. 총 네 개의 파일을 다운로드 받으세요.- train-images-idx3-ubyte.gz : train set 이미지  - train-labels-idx1-ubyte.gz : train set 레이블  - t10k-images-idx3-ubyte.gz : test set 이미지  - t10k-labels-idx1-ubyte.gz : test set 레이블  다운로드 받은 패션 Mnist의 label은 아래와 같이 되어 있습니다. 숫자 0부터 9 대신에 각 이미지가 어떤 이미지인지 텍스트로 표현되어 있어요.LabelDescription0T-shirt/top1Trouser2Pullover3Dress4Coat5Sandal6Shirt7Sneaker8Bag9Ankle boot2. Fashion-MNIST 데이터셋을 이전에 사용했던 mnist.pkl.gz 와 같은 형태로 변환해주는 스크립트 작성해주기위에서 연습할 때는 mnist.pkl.gz 한 개 파일만 사용했는데요!?! 그래서 다운로드 받은 네 개의 파일을 똑같은 형식의 파일 하나로 만들어주는 파이썬 스크립트를 작성해 fashion-mnist.pkl.gz 파일로 만들었어요.import gzip import pickle import numpy as np # MNIST 데이터셋은 train, test 셋이 각각 image, label로 나누어 저장되어있는 4개의 파일로 구성 test_image_path = 't10k-images-idx3-ubyte.gz' test_label_path = 't10k-labels-idx1-ubyte.gz' train_label_path = 'train-labels-idx1-ubyte.gz' train_image_path = 'train-images-idx3-ubyte.gz' out_file_name = 'fashion-mnist.pkl.gz' # train label / images 추출 with gzip.open(train_label_path, 'rb') as train_label_f:     train_label = np.frombuffer(             train_label_f.read(), dtype=np.uint8, offset=8).astype(np.int64)   with gzip.open(train_image_path, 'rb') as train_image_f:     train_imgs = np.frombuffer(             train_image_f.read(), dtype=np.uint8, offset=16).reshape(-1, 784).astype(np.float32)   # test label / images 추출 with gzip.open(test_label_path, 'rb') as test_label_f:     test_label = np.frombuffer(test_label_f.read(), dtype=np.uint8, offset=8).astype(np.int64)   with gzip.open(test_image_path, 'rb') as test_image_f:     test_imgs = np.frombuffer(             test_image_f.read(), dtype=np.uint8, offset=16).reshape(-1, 784).astype(np.float32)   # 기존 60000개 training set에서 50000개는 train set으로 사용하고, 10000개는 valid set으로 활용 train_label, valid_label = train_label[:50000], train_label[50000:]  train_imgs, valid_imgs = train_imgs[:50000], train_imgs[50000:]   # train set, validati on set, test set을 튜플 자료형으로 저장 out_data = ((train_imgs, train_label),             (valid_imgs, valid_label),             (test_imgs, test_label))   # pickle file로 dataset 데이터 포맷 맞춰주기 with gzip.open(out_file_name, 'wb') as out_f:     pickle.dump(out_data, out_f) 이 과정을 통해 나온 결과물, fashion-mnist.pkl.gz 를 Jupyter Notebook이 있는 경로에 업로드합니다.fashion-mnist.pkl.gz가 업로드 되었습니다!3. 머신러닝 학습하기아까 사용했던 활용했던 숫자 MNIST 스크립트를 그대로 사용하겠습니다. show_digit()을 이름만 바꾼 show_fashion()으로 데이터를 살펴보니 드레스가 보입니다.조금 전에 했던 숫자 MNIST와 똑같은 과정을 SageMaker를 이용해, 학습 → 테스트 → 예측해보니 아래와 같은 예측 결과를 얻을 수 있었습니다. 신발은 신발끼리, 바지는 바지끼리, 가방은 가방끼리 분류된 게 너무나 신기합니다. (아까 진행한 숫자보다 더 학습이 잘 된 것 같은건 기분 탓일까요…?)머신러닝이라고 겁내지 않아도 됩니다! 유저들에게 더 좋은 서비스 제공할 수 있으니까요. 지금까지 브랜디 개발2팀의 단아한 개발자 오연ㅈ….참사를 막아주세요.앗, 잠시만요!! 중요한 것을 놓칠 뻔 했네요.저처럼 테스트를 하면 그냥 지나치지 마세요. 자동 결제로 출금되는 뼈 아픈 경험을 할 수도 있습니다. 반드시 이용했던 서비스들을 stop 하거나 terminate 해주세요. (Clean-up단계) 자세한 내용은 여기를 클릭하세요.지금까지 Brandi 개발 2팀, 단아한 개발자 오연주였습니다!# entire script (숫자 Mnist) # 오호 드디어 coding start! # 이제부터 Brandi의 단아한 개발자, 저를 따라오시면 됩니다 :) # 노트북 Block을 실행하는 방법은 Shift + Enter 입니다 from sagemaker import get_execution_role role = get_execution_role()  # 초기에 설정해 뒀던 IAM role 가져오기 bucket = 'sagemaker-julie-test' # 초기 단계에 만들었던 S3 Bucket 이름 적기 %%time import pickle, gzip, numpy, urllib.request, json   # 여기서 잠깐, 생소한 라이브러리 설명을 드릴게요! # pickle: python식 데이터 압축 포맷 # numpy: 수치 계산을 하기 위한 python package # Load the dataset urllib.request.urlretrieve("http://deeplearning.net/data/mnist/mnist.pkl.gz", "mnist.pkl.gz") with gzip.open('mnist.pkl.gz', 'rb') as f:     train_set, valid_set, test_set = pickle.load(f, encoding="latin1")     # matplotlib로 그리는 그림이 jupyter 노트북에 바로 보여줄 수 있도록 설정 %matplotlib inline import matplotlib.pyplot as plt # 도표나 그림을 그릴 수 있게 해주는 라이브러리 plt.rcParams["figure.figsize"] = (2, 10) # 그림의 크기 지정 def show_digit(img, caption='', subplot=None):     if subplot is None:         _,(subplot) = plt.subplots(1,1)         imgr = img.reshape((28, 28))     subplot.axis('off')     subplot.imshow(imgr, cmap='gray')     plt.title(caption)   # train_set의 그림과[0] 데이터 이름[1]을 예시로 보여준다 show_digit(train_set[0][30], 'This is a {}'.format(train_set[1][30]))   # 학습을 하기 위해 학습 알고리즘 및 데이터 경로 설정! from sagemaker import KMeans data_location = 's3://{}/kmeans_highlevel_example/data'.format(bucket) output_location = 's3://{}/kmeans_example/output'.format(bucket)   print('training data will be uploaded to: {}'.format(data_location)) print('training artifacts will be uploaded to: {}'.format(output_location))   kmeans = KMeans(role=role,                 train_instance_count=2,  # 장비 2대를 사용하여 학습하겠어요!                 train_instance_type='ml.c4.8xlarge',                 output_path=output_location,                 k=10,  # 아래 그림을 참고해 주세요!                 data_location=data_location) %%time   # 학습 시작! kmeans.fit(kmeans.record_set(train_set[0]))   %%time # 모델을 만든 후 사용하기 위하여 배포하기 kmeans_predictor = kmeans.deploy(initial_instance_count=1,                                 instance_type='ml.m4.xlarge')                                  # valid_set에 30번째 sample을 테스트 해보기 result = kmeans_predictor.predict(valid_set[0][30:31])  print(result)   %%time   # vaild_set에 있는 0번부터 99번까지의 데이터로 cluster를 예측 해보자 result = kmeans_predictor.predict(valid_set[0][0:100])   # 예측 결과에 대한 cluster 정보를 수집 clusters = [r.label['closest_cluster'].float32_tensor.values[0] for r in result]   # 각 cluster별 예측된 이미지 출력 for cluster in range(10):     print('\n\n\nCluster {}:'.format(int(cluster)))     digits = [ img for l, img in zip(clusters, valid_set[0]) if int(l) == cluster ]     height = ((len(digits)-1)//5)+1     width = 5     plt.rcParams["figure.figsize"] = (width,height)     _, subplots = plt.subplots(height, width)     subplots = numpy.ndarray.flatten(subplots)     for subplot, image in zip(subplots, digits):         show_digit(image, subplot=subplot)     for subplot in subplots[len(digits):]:         subplot.axis('off')     plt.show() 출처Getting Started - Amazon SageMaker CodeOnWeb - 머신러닝 초보를 위한 MNIST fashion-mnist 글오연주 사원 | R&D 개발2팀[email protected]브랜디, 오직 예쁜 옷만#브랜디 #개발문화 #개발팀 #업무환경 #인사이트 #경험공유
조회수 903

[Buzzvil People] Jin Yoon, Product Manager

 Buzzvil People에서는 다양한 배경과 성격 그리고 생각을 지닌 버즈빌리언들을 한 분 한 분 소개하는 시간을 갖습니다. 어떻게 버즈빌에 최고의 동료들이 모여 최고의 팀을 만들어가고 있는 지 궁금하시다면, 색색깔 다양한 버즈빌리언들 한분 한분의 이야기가 궁금하시다면, Buzzvil People을 주목해주세요.1. 간단한 자기 소개 부탁드립니다. 안녕하세요. 버즈빌의 여러 Product 중 하나인 버즈스크린(BuzzScreen)을 담당하고 있는 Product Manager, Jin 입니다. 요즘에는 사무실에서 알파카 or 라마를 닮았다는 흉흉한 소문이 퍼지면서 이름 대신 불리기도 합니다. 첫 사회생활은 Oil & Gas industry의 한국 대기업에서 시작했습니다. 쉽게 얘기하면 세계 곳곳 석유가 묻혀있는 곳에 그 석유를 캐내고 정제하는 공장을 지어주는일이죠. 몇억 불에 달하는 프로젝트 전반을 관리하는 Project Management가 저의 role이었습니다. 그 후에는 모바일광고, pet food ecommerce, 음식 배달 등 한국/미국의 작은 스타트업에서 일하다가 버즈빌에 조인하게 됐습니다.  2. 어떻게 버즈빌에 오시게 되셨나요? 가장 보수적인 industry의 가장 한국적인 대기업이었던 첫 회사를 그만두고 MBA를 하면서 크게 3가지에 초점을 맞춰 진로를 찾았습니다.  빠르게 변화하는 industry 나의 transferable skill을 사용할 수 있는 position 조금 더 자유로운 분위기에서 일할 수 있는 환경  찾다보니 그 industry는 IT였고, Project Management 에서 나름 배웠던 skillset을 사용할 수 있는 포지션은 여러 가지가 있었지만, Product Manager가 가장 가깝다고 생각했습니다. 자유로운 분위기는 미국에 있는 여러 tech giant 들, 그게 아니라면 스타트업이라는 생각이 확고했고요. 그렇게 들어간 곳이 LA에 있는 작은 스타트업이었습니다. 총 4명 정도의 작은 회사였기 때문에 1년여간 일하면서 마케팅, 기획 등 여러 가지 일들을 배울 수 있었고 개발적인 부분도 일부 배울 수 있었습니다. 하지만 tech 회사라고 하기에는 개발인력도 많이 부족했고, 조금 더 배울 수 있는 곳을 찾다 보니 버즈빌에도 지원하게 되었습니다. 버즈빌에 오기로 결정하게 된 가장 큰 이유는 버즈빌이 인터뷰를 진행하는 방식이였습니다. 3차례의 인터뷰를 보면서 굉장히 재미있었거든요.  PM면접은 1, 2차 두 번 다 과제가 있었고, 타이트한 데드라인에 맞춰 준비하면서 긴장도 많이 하고 엄청난 부담감을 갖고 인터뷰에 들어갔는데… 하지만 막상 인터뷰에서는 제가 해온 과제를 평가받는 게 아니라 “이 문제를 조금 더 잘 풀기 위해서 어떻게 할 수 있을까?”를 같이 머리를 맞대고 자유롭게 얘기하면서 고민하다가 시간이 가더라고요. CEO, CPO와 보는 인터뷰가 이런 거라면 “일할때도 내 생각을 자유롭게 얘기하면서 같이 일할 수 있겠구나” 라는 느낌을 강하게 받아서 조인하기로 결정했습니다. Interviewer로 참석했던 Jay 와 Young이 보여준 “만담” 도 한 몫했습니다.  3. 버즈빌에서 어떤 업무를 담당하고 계신가요? 버즈스크린이라는 Product의 Product Manager 역할을 하고 있습니다. 간단하게 얘기해서 supply side인 파트너사들과 유저의 니즈, 시장의 상황 등을 반영하여 로드맵을 짜고, 그 로드맵에 맞춰 프로덕트를 발전시키고 개선하는 역할이라고 할 수 있겠네요.  특히 버즈스크린은 SDK 상품이다 보니 파트너사와 interaction이 많은 편입니다. 파트너사와 정기적인 미팅을 통해 개선점을 발굴하고 필요한 기능들을 제품에 녹여내기도 합니다. 하지만 한국뿐만 아니라 외국의 여러 파트너사도 하나의 공통된 Product를 사용하기 때문에 너도, 나도 원하는걸 다 세세하게 전부 들어줄 수 없습니다. 그렇게 되면 결국 더는 관리 할 수 없는 Product이 될수 있기 때문이죠. 무엇이 정말 Product의 발전을 위해 필요한것인지, 어떻게 하면 Product의 sustainability를 해치지않고 유저와 파트너사들을 만족시킬 수 있는지 생각을 많이 해야 하는 포지션인 것 같습니다. 또 내부적으로는 Business의 호흡과 Development의 호흡을 조절하는 역할을 담당해야 합니다. 현재 상황을 놓고 생각해봤을 때 어느 한쪽이 너무 빠르거나 느리게 달려간다고 생각할때는 속도를 조절하고, 이에 맞춰 counterpart의 기대치를 조정하는 역할을 해야합니다. 이를 통해 개발자들이 쫓기지 않고 개발할 수 있는 환경을 마련해주어야 하고 사업 담당자들이 파트너사에 적절하게 대응할 수 있는 환경도 마련해주어야 하고요. 결국 각 분야에서 전문성을 가진 사람들이 자신들의 역량을 가장 잘 발휘할 수 있도록 그 일에만 집중할 수 있게 만드는 일을 하고 있다고 (혹은 해야 한다고..) 생각합니다. 4. 스타트업에서 혹은 광고업계에서 일하는 느낌이 어떠세요? 스타트업에서 일하는 건 정말 힘든일인 것 같아요. 하지만 힘든 만큼 나름 재미도 있고 보람도 느끼면서 일하고 있어요. “힘들다”는 사실이 큰 장점이 될 수도 있는 곳이 스타트업인것 같습니다. 대기업에서 일했던 경험과 비교해보면 스타트업은 확실히 프로세스가 덜 갖춰져 있습니다. 그러다 보니 프로세스에서 보완될 수 있는 부분들에까지 리소스가 들어간다는 점, 회사에서 이탈하는 한명 한명의 빈자리가 상대적으로 크다는점은 단점이라고 할 수 있을 것 같네요. 하지만 바꿔서 생각해보면, 정해진 프로세스가 없다 보니 자유도가 높고, 일의 진행속도도 빠릅니다. 부서 간에 scope of work를 놓고 논쟁하지 않고, 모두 달려들어 일을 끝낼 수 있는 가장 빠른 방법을 찾아 끝내고, 그 과정에서 내가 할 수 있는 일을 스스로 찾아서 할 수 있는 것도 굉장히 흥미롭습니다. 또한 회사 구조적으로도 이것저것 새로운 시도들을 하는 것도 재미있습니다. 대기업에 있을 때는… 이미 다 채색까지 완성된 그림이 있고 그 위에다가 계속해서 정해진 같은 색으로 조금씩 점을 찍고 있는 느낌이 들었다면, 스타트업에서는 그야말로 스케치만 되어있는 도화지에 그림을 그리는 느낌이 듭니다. (물론 이건 스타트업에서 일하는 느낌이 아니라 버즈빌에서 일하는 느낌일 수도…) 누가 그리느냐에 따라 초등학생의 낙서가 될 수도 있고, 유명한 화가의 명작이 될 수도 있겠지만요. 그 과정은 정말 정말 힘들지만, 회사의 성장에 기여한다는 보람도 느낄 수 있고, 나도 성장할 수 있는 환경이라고 할 수 있겠네요.  욕심 없이 편안하게 주어진 일만 하면서 살고 싶은 분들에게는 스타트업에서 일하는 게 정말 지옥 같고 힘든 일이 될 것 같네요. (지극히 개인적인 의견입니다.) 5. 이것만큼은 버즈빌이 참 좋다! 어떤 게 있으실까요? 버즈빌은 그야말로 인사가 만사다 라는 말에 딱 들어맞는 회사입니다. 이 사람들과는 어떤 일을 해도 성공할 수 있겠다는 생각을 하게 하는 분들만 모여있는 것 같아요. 제가 힘들 때마다 Steve가 항상 “지금은 공기처럼 당연해서 크게 느껴지지 않겠지만 지금처럼 좋은 사람들과 함께 일할 수 있는 환경은 드물다”라고 하시는 데 공감하지 않을 수 없습니다.  특히 제가 입사한 지 한 달이 채 안 되었을 때 외부적인 요인으로 회사가 힘든 상황에 놓인 적이 있었는데, 각자 할 수 있는 분야에서 최고의 능력을 발휘해서 위기를 넘기는 모습은 짧은 기간에 버즈빌리언들의 뛰어난 개개인의 역량을 느낄 수 있었던 좋은 기회였던 것 같습니다. 업무 외적으로도 좋은 사람들과 일하고 있다는 것을 실감하고 있습니다. 점심시간마다 (낮잠을 포기하고) 탁구를 치거나 게임을 할 때마다 제 부족한 탁구/게임 실력을 걱정해주기도 하고, 실력 향상을 위한 진심 어린!! 조언도 아끼지 않습니다. 6. 개인적인 목표나 꿈이 있으신가요? 있다면, 버즈빌에서의 경험이 어떻게 도움이 된다고 생각하시나요? 한마디로 얘기하자면 최고의 2인자가 되는게 꿈입니다. 다른 사람들 앞에 나서지도 않고 조명도 받지 않지만 “이 사람과 함께라면 어떤일도 다 성공할 수 있어” 라는 생각이 들게끔 만드는 사람이 되는 것..이라고나 할까요.. 어릴때는 막연하게 “다른 사람들을 돕는일을 하고 싶다” 라는 생각을 갖고 살았던것 같아요. 평범한 학창시절을 보내고, 대학에 가고, 취업을 하면서 마음 한켠으로 치워두게된.. 그냥 그정도의 생각이었죠. 처음 다니던 회사를 그만두고 나는 평생 어떤 일을 하면서 살아야할까 라는 원론적인 고민을 하게 되었고, 그때 이 생각을 다시 한번 바라보게 된것같아요. 그러다가 기회가 닿아 MBA에 가게 되고 지금까지 만나보지 못했던 사람들을 만나면서 한때는 막연했던 이 생각을 조금 더 구체화시킬 수 있었습니다.  최고의 2인자가 되는 첫번째 step으로.. 우선 주변에 아이디어만 있고 실행으로 옮기고싶은데 어떻게 할 수 있는지를 몰라서 헤매는 친구들에게 작게나마 도움이 되고 싶습니다. 엔젤 투자자나 인큐베이터보다 조금 더 깊게 사업에 참여하고 실질적인 업무를 도와주며 같이 일하고 문제를 해결하면서 그 친구들의 아이디어를 실현하는데 일조하고 싶어요. 지금 버즈빌에서 지금 하고 있는 일이 이와 크게 다른 것 같지 않습니다. PM으로써 하나의 프로덕트를 기획하고 만들고 운영하는 게 결국은 하나의 작은 사업을 시작하는것이라고 생각합니다. 프로덕트를 만드는 과정에서 필요한 일들을 챙기고 처리하고 또 그 과정에서 고통스러워하고 즐거워하다보면, 아이디어를 구체화 시키면서 필요한 일들을 직/간접적으로 경험할 수 있겠죠. 그렇게 저를 잘 단련시키다보면 결국 제가 이루고자 하는 꿈에 다가갈수 있지 않을까요. *버즈빌의 채용공고(전문연구요원 포함)를 확인하고 싶으면 아래 버튼을 눌러주세요!
조회수 3403

Next.js 튜토리얼 2편: 페이지 이동

* 이 글은 Next.js의 공식 튜토리얼을 번역한 글입니다.** 오역 및 오탈자가 있을 수 있습니다. 발견하시면 제보해주세요!목차1편: 시작하기2편: 페이지 이동  - 현재 글3편: 공유 컴포넌트4편: 동적 페이지5편: 라우트 마스킹6편: 서버 사이드7편: 데이터 가져오기8편: 컴포넌트 스타일링9편: 배포하기개요이제 간단한 Next.js 애플리케이션을 만들고 동작시키는 법을 알았습니다. 이 간단한 애플리케이션은 하나의 페이지를 가지고 있지만 원하는 만큼 페이지를 추가할 수 있습니다. 예를 들어 pages/about.js에 다음 내용을 추가하여 "About" 페이지를 만들 수 있습니다:그러면 http://localhost:3000/about를 통해 About 페이지에 접근할 수 있습니다.이제 이 페이지들을 연결시켜야 합니다. 이를 위해 HTML의 "a" 태그를 사용할 수 있습니다. 그러나 a 태그를 사용하면 클라이언트 사이드를 통해 이동하지 않습니다. 원하지 않게도 서버 사이드를 통해 페이지가 이동합니다.클라이언트 사이드 이동을 지원하기 위해 next/link를 통해 export된Next.js의 Link API를 사용해야 합니다.설치이번 장에서는 간단한 Next.js 애플리케이션이 필요합니다. 이전 편을 수행하거나 다음의 샘플 애플리케이션을 다운받아주세요:아래의 명령어로 실행시킬 수 있습니다:이제 http://localhost:3000로 이동하여 애플리케이션에 접근할 수 있습니다.Link 사용하기두 개의 페이지를 연결하기 위해 next/link를 사용할 예정입니다.pages/index.js에 다음과 같은 코드를 추가해주세요.next/link를 Link로 import하여 다음과 같이 사용하였습니다:http://localhost:3000에 방문해주세요.그런 다음 "About Page" 링크를 클릭하면 "About" 페이지로 이동합니다.이것은 클라이언트 사이드 이동입니다. 이 동작은 서버 요청없이 브라우저 안에서 수행됩니다.브라우저의 네트워크 상태 검사 툴에서 확인할 수 있습니다.자 지금 간단한 과제가 있습니다:- http://localhost:3000에 방문하세요.- 그런 다음 "About Page"를 클릭하세요- 브라우저의 뒤로가기 버튼을 클릭하세요.뒤로가기 버튼을 클릭했을 때 어떤 일이 일어나는지 가장 잘 설명한 것은 무엇인가요?- 뒤로가기 버튼이 동작하지 않았다.- 뒤로가기 버튼이 브라우저 콘솔에 에러를 발생시켰다.- 클라이언트 사이드를 통해 인덱스(home) 페이지로 이동했다.- "뒤로가기 버튼을 지원하기 위해 'next/back'를 import하세요"라는 알럿창이 띄워졌다클라이언트 사이드 히스토리 지원뒤로가기 버튼을 클릭하면 클라이언트를 통해 인덱스 페이지로 이동합니다. next/link는 모든  location.history를 처리합니다.클라이언트 사이드 라우팅에 대한 코드를 단 한 줄도 작성할 필요가 없습니다.간단하게 페이지들을 연결하세요. 그래도 잘 동작합니다!Link 스타일링하기대부분의 경우 링크에 스타일을 지정하고자 합니다. 스타일을 지정하는 방법입니다:위와 같은 코드를 추가하면 스타일이 올바르게 적용된 것을 볼 수 있습니다.위의 코드 대신 아래의 코드처럼 작성하는면 어떨까요?위의 코드처럼 변경했을 때 어떤 일이 일어났나요?- 원하던 스타일이 올바르게 적용되었다.- 링크에 어떤 스타일도 적용되지 않았다.- 전체 페이지가 다시 로딩된 후에 스타일이 적용되었다.- 스타일이 적용되었지만 콘솔에 에러가 나타났다.Link는 래퍼 컴포넌트입니다사실 next/link에 있는 스타일 prop는 아무런 효과가 없습니다. 왜냐하면 next/link는 단지 "href"와 다른 라우팅 관련 props만 받아들이는 래퍼 컴포넌트이기 때문입니다. 스타일을 적용해야 한다면 하위에 있는 컴포넌트에 지정해야 합니다.Button이 있는 Link링크의 앵커 대신에 "button"을 사용해봅시다. 다음과 같이 코드를 수정해야 합니다:인덱스 페이지의 버튼을 클릭하면 어떤 일이 일어날까요?- 아무 일도 일어나지 않는다- "링크 안에 버튼이 올 수 없습니다"라는 에러가 발생한다- 페이지가 다시 로딩된다- about 페이지로 이동한다Link는 어떤 것과도 동작합니다버튼과 같이 커스텀 React 컴포넌트나 div 등을 Link 안에 배치할 수 있습니다.Link 안에 있는 컴포넌트들의 유일한 요구 사항은 onClick prop를 받을 수 있어야 한다는 것입니다.Link는 간단하지만 강력합니다이번 편에서는 next/link의 기본적인 사용법을 살펴보았습니다. Link를 사용하기 위해  몇 가지 재밌는 방법들이 있습니다. 다음 편들에서 배울 예정입니다.그동안 Next.js Routing documentation를 살펴보세요. 유용합니다.#트레바리 #개발자 #안드로이드 #앱개발 #Next.js #백엔드 #인사이트 #경험공유
조회수 1157

개발자의 경력관리란?

경력이 아닌 업력이 되는 단계에 이르러야 가능한 것 아닌가 합니다.대부분의 경력은 '어느 회사의 누구'라는 표현에서 만들어진 것이 아닙니다.진정한 경력의 결과는 '자신의 이름'이 곧 브랜드화 되는 것입니다.매우 당연하게,하루 이틀, 한 두해 한다고 해서 얻어지는 것이 아닙니다."10년 경력!"10년 이상 한 분야나 하나의 도메인, 하나의 테크, 하나의 경력, 하나의 경험을 꾸준하게 파고들었을 때에 얻어지고, 그러는 경험속에서 인사이트, 통찰력이 생기게 됩니다.물론. 그래서, 20대에도 명성을 얻을 수 있는 '경력관리'가 가능하다고 이야기합니다.(실제 얻은 사람을 많이 봤습니다. 그들은 10대에 시작했죠. )회사의 테두리 내에서 얻을 수 있는 '경력'은 '경험'일뿐입니다.자신의 이름을 중심으로 기술할 수 있을 때에 '경력'이라고 이야기할 수 있습니다.개발자라면...글을 써서도 얻을 수 있고,강연을 해서도 얻을 수 있고,GitHub에 오픈소스를 공개하면서도 얻을 수 있습니다.현재 30대와 그 이전의 개발자라면...10대와 20대도 똑같습니다.40대, 50대 이후를 준비하세요.반복적인 일, 똑같은 일, 회사의 프로세스의 하나인 일만 하는 '사람'이라면...그냥, 그 회사의 톱니바퀴가 되는 것입니다.대부분 '경력관리'가 잘 안됩니다.앞으로 50대 이후에도 '브랜드'를 얻을 사람이 되려면...자신의 '경력'관리를 잘 해야 얻을 수 있습니다.나중에 닭 튀기거나 치킨 배달할 것이 아니라면...관리를 잘해야 합니다.경력관리가 가능하려면 어떤 회사를 찾아야 할까요.다음을 기억하세요.1. 구루급 개발자가 있는 회사를 찾으세요.2. 자신이 주도적으로 무언가를 만들 수 있는 권한과 책임을 줄 수 있는 회사를 찾으세요.3. 커뮤니티나 외부 강연, 외부 오픈소스 개발 행사에 적극 참여할 수 있는 기회를 주는 회사를 찾으세요.4. 반복적인 업무와 정체된 마켓에서만 반복적으로 서비스를 하는 회사는 회피하세요.5. 우리 도메인은 원래 이래, 이 일은 원래 이래... 이런 식으로 이야기하는 '상급자'가 있는 회사를 피하세요.6. 쉽게 설명할 수 있도록 준비하고, 리뷰를 할 수 있는 기회와 시간이 주어지는 회사를 찾으세요.그리고, 마지막으로...비전은 누가 주거나 만들어 주지 않습니다.결국, 자기 자신이 찾아야 하는데...이것도, 주변에 이야기가 통하는 '구루급 개발자'가 있어야 그나마 방향성을 찾기 좋습니다.혼자 고민하거나,주변에 비슷한 사람들끼리 고민해봐야 답이 안 나옵니다.꼭, 기억하세요!'구루급 개발자'와 상의하세요.그분들은 실패와 성공, 포기와 단념, 선택과 집중에 대해서 알고 있답니다.퇴근시간이라면..구루급 개발자에게 치맥 한잔 하자고 하세요!
조회수 2494

사운들리 백엔드 이야기

사운들리는 '귀에 들리지 않는 소리'를 이용해서 컨텐츠를 전달할 수 있는 SaaS 플랫폼을 서비스하고 있습니다.제품의 구성요소는,음파를 송신할 수 있는 송신단음파를 모바일에서 수신할 수 있는 Android, iOS SDK그리고 컨텐츠를 제공하고 데이터를 수집, 분석하는 백엔드로 구성되어 있습니다.오늘은 구성 요소중 백엔드에 대해서 이야기 해보도록 하겠습니다.<그림 1. 사운들리 솔루션 구성도>사운들리의 인프라는 모두가 잘 아시는 아마존 웹 서비스를 이용하고 있으며, 크게 컨텐츠를 제공하는 API서버 부분, 로그를 수집, 분석하는 부분, 그리고 컨텐츠를 관리하는 CMS 부분으로 이루어져 있습니다.소프트웨어 스택Java : 현재 사운들리의 일부 시스템을 제외하고는 전부 자바로 작성되어 있습니다. Node.js로 시작하여 PHP를 거쳐 지금의 자바 기반의 시스템으로 구성하게 되었습니다. 다양한 사람들이 개발을 해오면서 각자 가장 잘할 수 있고, 빠르게 구현할 수 있는 언어로 개발되어 가다 현재의 자바로 통일되어 구성되게 되었습니다.Spring : API서버는 HTTP 기반의 REST API를 이용해 컨텐츠를 전달하고 있으며 스프링 프레임워크를 이용해 개발되었습니다. 이외에도 일부 분석에 스프링 배치를 사용하고 스프링을 편리하게 사용할 수 있게해주는 스프링 부트도 이용하고 있습니다.gRPC : 분산되어있는 서버들끼리 이기종 언어간 통신을 하기 위해서 Protocol Buffers 기반의 gRPC를 이용하고 있으며 서버들의 모니터링하는 서버와 에이전트들 사이의 통신 목적으로 사용합니다.Flume : 분산된 서버들에서 로그를 수집하는 역할을 합니다. 수집된 로그는 파일로 저장하며 실시간으로 볼수 있도록 엘라스틱서치에 같이 저장하고 있습니다. SDK에서 전송되는 로그 또한 웹서버의 엑세스 로그를 플럼 에이전트가 수집하는 방식으로 비동기로 처리하고 있습니다.ElasticSearch : 수집된 로그들을 실시간으로 확인하기 위해서 사용되며 Kibana를 이용해 시각화하고 있습니다.Angular.js : CMS의 프론트엔드는 Angular.js + Bootstrap을 이용해 개발되었으며, Bower를 이용한 라이브러리 관리, Grunt를 이용한 빌드 관리를 하고 있습니다.소프트웨어 개발/운영GIT : 소스코드는 git로 관리하며 Git-Flow를 이용한 브랜치 정책을 수립하여 가져가고 있고 저장소로는 깃허브를 이용합니다.Quality Practice : QA단계에서 제품을 테스트하기 전 개발자들은 QA 프로세스에 맞게 다음 3가지 기준으로 소스 코드의 품질을 관리합니다.코딩 컨벤션 : 사운들리 내부 코딩 컨벤션에 맞게 개발되었는지 확인합니다. Checkstyle의 규칙을 정의 및 자동화합니다.테스트 코드 : 단위 테스트 코드를 작성하며 테스트 결과는 모두 통과되어야 합니다.테스트 커버리지 : 단위 테스트 코드가 작성된 커버리지를 계산하며 현재 60%를 목표로 진행하고 있습니다.젠킨스 : 소스코드 저장소에 변동이 일어나면 젠킨스가 소스코드를 빌드하고 위에서 언급한 세가지에 대한 리포트를 작성합니다.소나큐브 : 무료 오픈소스로 코드 정적 분석을 해주며 및 QA 리포트를 같이 볼 수 있습니다.슬랙 : 인력이 적은 저희 팀도 슬랙을 적극적으로 개발/운영에서 사용하고 있습니다.팀 커뮤니케이션 : 팀원들 간의 의사사통을 위한 주요 수단으로 모든 팀원이 함께 사용하고 있습니다.분석 리포트 : 젠킨스나 배치를 통해 분석된 데이터들은 분석이 끝난 지표들은 슬랙으로 결과를 전송하여 모든 팀원이 볼 수 있도록 공유하고 있습니다.서버 모니터링 : 서버들의 이상 징후 감지나 배치 오류등을 슬랙을 통해 담당자에게 전송하여 조치할 수 있도록 합니다.애플리케이션 및 서버 모니터링 : 애플리케이션의 모니터링은 Naver에서 오픈소스로 공개한 핀포인트를 사용하고 있고, 서버 상태 모니터링을 위해 자체 개발한 모니터링 시스템을 사용하고 있습니다. 모니터링 데이터 수집을 하는 에이전트와 전체 시스템의 데이터를 관장 하는 서버간에는 gRPC를 이용하여 상태 체크를 합니다. 서버의 상태에 문제가 있을 때에는 slack을 통해 담당자들에게 알람을 주도록 시스템 설계를 하였습니다.개발 문화개발자들은 각각 개발을 할때 정해진 정책에 맞춰 브랜치를 만들어 개발합니다.각각 개발된 소스들은 저장소인 깃허브에 푸시된 후 깃허브의 댓글 기능을 이용하거나 오프라인을 통해 코드 리뷰를 진행합니다.리뷰가 끝난 후 합쳐진 소스는 QP 활동을 통해 분석이 됩니다.빌드가 실패할 경우 커피를 사야합니다 ^^ (커피를 얻어 먹으려는 것이 아닌 소스코드를 푸시하기 전 잘 확인하자는 취지입니다) AWSEC2 : 사운들리의 대부분의 구성 요소인 API서버와 로그 수집, 분석 서버, 엘라스틱서치, 플럼, CMS등이 모두 EC2에 구축되어 있습니다.RDS : 컨텐츠의 주 저장소로 데이터베이스 관리의 용이성을 고려하여 RDS의 Multi-AZ에 배포하여 Active-Standby로 구성되어 있으며 이 데이터들은 레디스와 로컬 캐시를 이용하여 API서버에서 활용하고 있습니다.S3 : 컨텐츠에 포함된 각종 정적 데이터들이 저장되며 수집된 로그들도 저장하여 보관됩니다. EMR : 로그 수집서버를 통해 S3에 저장된 로그들은 EMR을 이용해서 분석됩니다.Beanstalk : 개발 서버의 배포에 사용됩니다. 최근 IntelliJ의 플러그인이 업데이트 되면서 IntelliJ 15버전을 지원하게 되므로써 로컬에서 개발하고 개발 서버에 배포까지 편리하게 하고 있습니다. VPC : 인터넷이 필요 없는 서버들은 VPC 내부 private-zone에 배포 및 ELB를 통해 외부에서 접근하도록 구성되어 있습니다.<그림 2. AWS 배포 구성도>이상으로 사운들리에서 사용하고 있는 백엔드 소프트웨어들을 소개해 보았습니다. 적은 인력으로 빠르게 사업을 진행하는 스타트업에서는 비즈니스에 집중할 수 있도록 도와주는 다양한 툴이나 오픈소스를 이용하여 많은 도움을 받을 수 있는 것 같습니다. 또한 코드를 잘 작성하여 에러를 줄이는 것도 필요하지만 여유가 많지 않으면 최소한 제품의 에러에 빠르게 대응할 수 있도록 하는 방법도 필요한 것 같습니다.#사운들리 #개발 #개발자 #문제해결 #프레임워크 #스킬스택 #스택 #인사이트
조회수 1031

Node.js 이해하기

Understanding node.js 글을 번역한 글입니다. 부족한 영어 실력이지만 공부를 위해 번역하여 틀린 내용이 있을 수 있습니다. 이런 부분이 있을 경우 댓글로 알려주시면 감사하겠습니다!! 글이 문답형으로 진행되니 감안하시고 읽어주세요!Node.js(이후 '노드'로 통칭)를 소개했을 때 사람들은 일반적으로 두 가지 반응을 보인다. 바로 알았다고 하는 반응 혹은 매우 혼란스러워 하는 반응이다.만약 너가 후자의 경우라면 노드를 설명하기 위한 내 시도가 있다.노드는 command line tool이다. 너는 파일을 다운로드하고 컴파일하고 소스를 설치한다.노드는 JavaScript(이후 '자바스크립트'로 통칭) 프로그램들을 터미널에 'node my_app.js'를 입력함으로써 실행하게 한다.자바스크립트는 V8 자바스크립트 엔진으로 실행된다. (구글 크롬을 빠르게 만드는 것이다.)노드는 네트워크와 파일 시스템에 접근하기 위한 자바스크립트 API를 제공한다.나는 내가 필요한 모든 것을 Ruby, Python, PHP, Java에서 구현할 수 있어!너의 말이 맞다! 미안하게도 노드는 너를 위해 오고 너의 일을 하는 별난 유니콘이 아니다. 이것은 단지 툴이고 적어도 지금은 너가 보통 사용하는 완벽한 툴들을 대체하지 않을 것이다.요점을 알려줘!ㅇㅋ. 기본적으로 노드는 같은 시간에 여러 가지의 일들을 해야할 때 매우 좋다. 코드를 작성하고 "나는 이것들이 동시에 작동했으면 좋겠어"라고 말해본 적 있니? 노드에서는 너의 코드를 제외한 모든 것들이 동시에 작동한다.엥??정말이다. 너의 코드를 제외한 모든 것들이 동시에 작동한다. 이것을 이해하기 위해 너의 코드는 왕이고 노드는 왕의 하인들이라고 상상해보자.한 하인이 왕을 깨워 왕이 필요한 것들이 있는지 물어보는 것으로 하루가 시작된다. 왕은 하인들에게 해야할 일 목록을 주고 다시 오랫동안 자러 간다. 하인은 이 할 일들을 동료들에게 나눠주고 그들은 일을 시작한다.하인이 일을 끝내면 그는 왕의 쿼터 밖으로 보고서를 나열한다. 왕은 한 하인씩 따로따로 들여보내고 그들의 보고서를 듣는다. 때때로 왕은 나가는 길에 하인에게 더 많은 일을 준다.인생은 좋다. 왕의 하인들이 동시에 왕의 모든 일들을 수행하는 동안 왕은 하나의 결과가 있는 보고서에만 따로따로 집중할 수 있다.짱이다! 하지만 그 어리석은 비유를 그만두고 컴퓨터적으로 말해줄 수 있니?ㅇㅋ. 간단한 노드 프로그램은 아래와 같을 것이다:너의 코드는 노드에게 파일을 읽고 쓰는 두가지 일을 주고 자러 간다. 노드가 일을 완료했을 때 이것을 위한 콜백이 실행된다. 하지만 그들은 동시에 실행되는 콜백이 될뿐이다. 콜백이 실행을 완료하는 동안까지 다른 모든 콜백들은 라인에서 멈춰있어야 한다. 게다가 그 콜백들이 실행될 것이라는 보장도 없다.그래서 나는 동시에 같은 데이터 구조에 접근하는 코드에 관해 걱정할 필요가 없지않아?맞다! 그것이 자바스크립트의 싱글 쓰레드와 이벤트 루프 디자인의 아름다움이다. 좋긴 하지만 내가 왜 노드를 써야해?한 가지 이유는 효율성이다. 웹 어플리케이션에서 너의 메인 응답 시간 비용은 대개 너의 모든 데이터베이스 쿼리들이 실행하는데 전력하는 시간들의 합이다. 노드에서는 제일 느린 쿼리를 실행하는 동안 응답시간을 줄이기 위해 너의 모든 쿼리를 즉시 실행한다.또 다른 이유는 자바스크립트다. 너는 노드를 브라우저와 백엔드 사이에서 코드를 공유하기 위해 사용할 수 잇다. 자바스크립트는 정말 다방면성의 언어다. 너가 과거에Python, Ruby, Java, PHP를 써왔다하더라도 아마도 어떤 자바스크립트를 선택해왔을 것이다.마지막 이유는 로우 스피드다. V8은 계속해서 행성에서 가장 빠른 동적 언어 인터프리터의 하나로 경계를 밀고 있다. 나는 자바스크립트만큼 적극적으로 속도를 위해 푸시되는 다른 언어를 생각할 수 없다. 게다가 노드의 I/O 설비는 정말 가볍고 너의 시스템의 가능한 많은 I/O 능력을 활용하게 다가가는 것이다.그러면 너는 내가 당장 내 모든 앱을 노드에서 구현하라고 말하는거야?그렇기도 하고 아니기도 하다. 너가 노드 망치를 휘두르기 시작하면 모든것들은 분명 손톱처럼 보이기 시작할 것이다. 하지만 만약 너가 데드라인이 있는 일을 한다면 너는 아래의 사항들을 기초하여 결정하고 싶을 수도 있다.- 적은 응답 시간과 높은 동시성이 중요한가? 노드는 이것에 정말 좋다.- 프로젝트가 얼마나 큰가? 작은 프로젝트는 괜찮다. 큰 프로젝트는 아마 신중하게 평가해야 한다. (이용가능한 라이브러리, 버그를 고치기 위한 리소스들, 투 업스트림 등)윈도우에서 노드가 실행되니?안된다. 만약 너가 윈도우라면 너는 리눅스와 함께 버츄얼 머신을 실행해야 한다. (VirtualBox를 추천한다.) 윈도우는 노드를 지원하는 계획이 있지만 그 포트와 함께 도와주기를 원하지 않는다면 앞으로 몇 달 동안 뜸들이지 마라.노드에서 DOM에 접근할 수 있니?좋은 질문이다! 접근할 수 없다. DOM는 물질적인 브라우저고 노드의 자바스크립트 엔진(V8)은 감사하게도 그 복잡한 모든것들과 분리했다. 그러나 사람들은 노드 모듈로써 DOM를 실행하여 일한다. 이것은 클라이언트 사이드 코드 유닛 테스트와 같은 매우 놀라온 가능성을 열어줄 것 같다. 이벤트 드리븐 프로그래밍은 어렵지 않니?그것은 너에게 달렸다. 만약 너가 juggle AJAX를 호출하는 방법과 브라우저에서 유저 이벤트들에 대해 이미 배웠다면 노드 사용 방법을 배우는게 큰 문제 아닐 것이다.그렇지 않다면 너가 유지 보수 디자인을 마련하는데 도움을 줄 수 있는 드리븐 개발을 테스트해라.노드는 누가 사용하고 있니?node wiki에 작고 불안정한 리스트가 있다. 야후는 YUI를 위해 노드를 경험중이고 Plurk는 거대한 comet을 위해 사용중고 Paul Bakaus(jQuery UI fame)은 노드 백엔드를 가지는 mind-blowing game engine을 빌드 중이다. Joyent는 노드 창시자인 Ryan Dahi를 고용하여 개발에 막대한 지원을 해주고 있다.아 그리고 Heroku는 실험적으로 hosting support for node.js를 발표했다.어디서 더 배울수 있니?Tim Caswell는 훌륭한 How To Node 블로그를 운영중이다. 트위터에서 #nodejs를 팔로우해라. 메일링 리스트를 구독해라. 그리고 IRC 채널 #node.js에서 시간을 보내라. 우리는 곧 200 lurker-mark에 도달해 간다. 또한 나는 계속 http://debuggable.com/에 글을 쓰고 있다. #트레바리 #개발자 #안드로이드 #앱개발 #Node.js #백엔드 #인사이트 #경험공유
조회수 796

챗봇과 인공지능 머신러닝 - Part 2/2

지난 시간에 이어 오늘은 챗봇에게 지능을 주는 방법에 대해 알아본다. 공부를 해보시면 아시지만 공부란 어느정도 양이 많아지면 가속이 붙는다는 것을 학창시절에 경험 하셨을 것이다. 즉, 공부를 잘하는 사람은 조금만 해도 더 잘한다. 아무것도 아는게 없는 상황이라면 무조건 머리에 넣는 것도 방법이다. 물론 그 후에는 외운 지식의 의미에 대해 깊은 사고가 필요하지만.  챗봇한테도 이런 사람에 통하는 방식이 그대로 적용된다.지도학습은 규칙이나 사례를 구조화된 형식으로 표현하고 이를 컴퓨터에 입력해 놓는 방식이다. 단점은 한 분야의 지능을 다른 분야에 재사용할 수 없기 때문에 분야별로 다시 개발해야 한다는 데 있다. 아! 주입식 교육의 한계.한편, 자율학습은 인간의 뇌처럼 컴퓨터도 동일하게 데이터간의 연결 상태와 강도로 지식을 보유하도록 하는 방식이다. 이 방식의 대표적인 예가 인공 신경망(Artificial Neural Network)으로 스스로 학습할 수 있다는 점이 가장 큰 장점이다. 대량의 데이터에서 스스로 특징을 추출한다. 최근에는 딥러닝(Deep Learning)이라는 방법을 이용하여 자연어 인식, 영상인식, 음성 인식 등에서 과거엔 손도 못 대던 일을 하고 있다.인공신경망 활용을 위한 두 가지 조건인공신경망의 장점을 살리기 위해선 두 가지 큰 장벽을 넘어야 한다. 첫째는 자율학습 알고리즘을 개발하는 것이다. 둘째는 필요한 양질의 데이터를 대규모로 확보하는 것이다. 인공신경망 개발툴은 구글이나 마이크로소프트 등이 무료로 공개하고 있으므로 데이터 공학자, 프로그래밍 전문가, 응용수학자, 기획자 등과 함께 팀을 구성하면 개발을 시작할 수 있다. 그러나 실제에 있어서 가장 큰 난관은 두 번째로 지적한 대규모 데이터의 확보에 있다. 데이터를 가진 자가 승자라는 말이 있을 정도로 데이터가 중요하지만 이를 확보하는 것은 쉽지 않다. 학습 알고리즘이 있어도 데이터의 질이 떨어지거나 데이터의 수량이 적다면 자율학습이 제대로 될 수 없기 때문이다. 아! 머리에 든게 충분히 있어야 딥러닝이 가능하다.기술력보다는 기획력이 중요한 챗봇챗봇은 텍스트 형식의 글자를 통해 사람과 기계가 소통하는 방법이므로 앞에서 언급한 머신러닝 기술 중 자연어 처리(NLP)와 자연어 인식(NLU)이 필요해진다. 아! 정말 알아야 할 게 많다. 간단히 설명하면 NLP에는 형태소분석, 구문분석이 포함되고 NLU는 여기에 사용자 의도 해석과 실제 상황처리가 필요한 문맥이해까지 포함된다. 누구나 알다시피 조사, 접사 등이 발달한 한국어는 텍스트 처리가 영어에 비해 쉽지 않다고 한다. 로봇한테 사람처럼 말귀를 알아듣게 하는 작업이란 이렇게 어려운 일이다.실무에서의 챗봇 서비스는 기술력도 중요하지만 어떤 컨텐츠를 가지고 어떻게 서비스 할지에 대해 더 고민해야 한다. 역시 대화란 사람에 대한 이해가 중요한 만큼 초기단계에서 좋은 데이터 축적을 위해 규칙기반의 룰을 잘 선정하고 이를 머신러닝 기법과 잘 융합하는 유연성이 필요하다. 또 데이터 크기가 작을 때에는 딥러닝 보다 SVM(Support Vector Machine)류의 머신러닝이 더 좋은 성능을 보인다. 또 오버피팅 문제로 인해 학습 시 많은 데이터 사용이 꼭 성능증가로 이어지지도 않는다. 오히려 도메인 지식과 기획력 및 간단한 세션관리로도 좋은 품질의 챗봇을 만들 수 있다고 본다. 아울러 초기기술을 계속적으로 축적하면서 차근차근 지속적으로 업그레이드 해 나간다면 누구나 그 컨텐츠 영역에서 훌륭한 챗봇 친구를 얻을 것이다.맺는말이상으로 간단하게 챗봇에 대해 지극히 개인적인 의견을 올려봤다. 깊이 들어가면 한이 없는 분야지만 제 4차 산업혁명을 맞이하여 필연적으로 우리와 함께 살아갈 수밖에 없는 스마트폰 안에 있는 로봇인 챗봇에 대해 모든 사람들이 더욱더 관심을 가졌으면 한다.

기업문화 엿볼 때, 더팀스

로그인

/