스토리 홈

인터뷰

피드

뉴스

조회수 3346

빅데이터 '분석가' '전문가'가 부족한 이유...

업계에서는 대기업이나 공공기관 등의 데이터 분석 수요가 커지면서 빅데이터를 다루거나 데이터 분석가들을 찾는 기업이 늘어난다고 하는 기사나 이야기들이 떠돌아다닌다.한국정보화진흥원에서 발간한 '2015년 빅데이터 시장 현황조사'보고서에 의하면 빅데이터 공급기업과 수요기업 모두 빅데이터 분석가가 필요하다고 내다보고, 많은 데이터 분석가가 필요하다고 이야기했다.분야도 금융을 비롯하여 통신, 커머스 등을 아우르고, IT 관련부서뿐만 아니라, 현업이라고 불리는 마케팅이나 영업도 포함된 관계에서의 데이터 활용을 위해서 빅데이터 '분석가'가 필요하다고 이야기를 한다.죄송하지만.. 한국형 환경에서는 '빅데이터 분석가'나 '전문가'는 그다지 필요 없을 것 같다.1. 변화하지 않는 기업어차피 정해져 있는 프로세서, 내부 R&R과 내부 혁신을 하기 위한 인사이트를 찾고, 데이터 변수를 찾는다고 하더라도 굳이 기업 내부의 변화를 일으키지 않을 것이기 때문에 '진정한 데이터 분석가'는 해당 기업에 무의미할 것이다.정말, 전문가라면 '내부 혁신'에 대한 키워드들을 뽑아줄 텐데... 이런 이야기는 '컨설팅'업체에서도 하지 않고, 내부에서도 '금기'시 해야 할 단어들이 대부분이다.만일, 대기업인 중요 키워드가 '오너'의 키가 문제라고 지적한다면... 아마도, 해당 부서나 관련자들은 움직이지도 못할 것이다.죄송하지만, '내부 혁신'이 불가능하고, '오너'중심의 대기업은 데이터 분석가가 필요하지 않다. 다만, '오너'의 생각을 읽고서 적당하게 마사지된 '데이터'를 보여줄 '외부 데이터 분석'서비스 업체만 필요할 뿐이다.그래서, 국내에서는 데이터 분석 서비스 업체 정도가 적당하다.2. 기업과 조직에 데이터가 없다.프로세스 하단에서 동작하는 수많은 로그들을 추적 감시, 감사하는 시스템이 가동되고 있어야 하며, 고객 서비스를 하는 서비스 집단에서도 하단에서 아이디어가 상단으로 올라가는 환경들이 이미 가동되고 있어야 한다. 데이터의 대부분은 그런 인사이트를 증명하는 근거가 되기 때문이다.이미, 중요한 움직임을 보이고 있을 때에만 '의미 있는 정보'를 추출할 데이터들이 축적되는데... 사실상, 의미 없이 마사지된 '보고서'들만 존재한다.원천적으로 의미 있는 데이터를 추출할 데이터가 있어야 하는데.. 대부분이 왜곡된 정보들이거나, 특정 힘에 의해서 데이터들이 왜곡돼 있다면, 해당 기업과 조직은 데이터가 없다고 봐야 한다.3. 오랜 경험을 축적한 실전 전문가들이 일찍 퇴직한다.빅데이터를 통해서 단지 현황만을 보여주는 것이 아니라, 기업의 미래나 새로운 먹거리를 유도할 수 있는 인사이트를 추출하기 위해서는 해당 도메인이나 해당 마켓에 익숙하고 경험이 풍부한 전문가들이 같이 있어야 한다. 실제, 데이터가 의미하는 방향성이나 수치, 지수가 어떤 것을 의미하는지 읽어 줄 수 있는 것은 데이터 전문가들이 하는 일이 아니다.해당 업무와 해당 도메인의 전문가가 그 '수치'를 읽어 줄 수 있는 것이다.대부분의 기업에서 '실전'이거나 '실제 업무'에 익숙한 전문가나 경험이 축적된 사람들은 하청업체이거나 이미 퇴직한 경험이 풍부한 사람들이다.해당 기업에서는 아무리 데이터가 분석되어도 어떤 의미인지 판독해줄 사람이 없다.4. IT기술 전문가가 필요한 것이 아니다.빅데이터나 머신러닝과 같은 지식화 인사이트는 절대 IT기술이나 주변의 소프트웨어 설루션으로 만들어지는 것이 아니다. 기업 내부에 축적된 '지식'을 기반으로 '사람'을 기준으로 데이터가 만들어진다. 데이터 분석 전문가는 단지, 그것의 가치를 '판정'해줄 수 있는 기준을 마련해줄 뿐이다.대부분의 '한국형'조직들은 데이터 거버넌스 조직도 없으며, 제대로 된 인사시스템이 가동되지 않고 있다. 슬프지만, 빅데이터 전문가들은 내부에서 영입하는 것이 아니라, 내부에서 자생적으로 생성되는 것이다.자생적으로 빅데이터 전문가가 생성되지 않는 조직은 이미, 지식화가 불가능한 형태이기 때문에, 너무 무리하지 말고, 현재 환경에서 연착륙하는 것을 고려하는 것이 최선일 것이다.역시, '한국형'에서는 굳이 '빅데이터 분석가'가 필요한 것이 아니라, '빅데이터 분석가 코스프레'를 하는 사람이 필요한 것 아닌가?오너가 이야기하는 'A'를 'A'처럼 써줄 수 있는 코스프레가 가능한 사람이면 충분한 것 아닌가 한다.
조회수 4420

RESTful API를 설계하기 위한 디자인 팁

올라왔었던 REST 아키텍처를 훌륭하게 적용하기 위한 몇 가지 디자인 팁의 글에서 언급되지 않았던 추가적인 내용에 대해서 좀 더 얘기해보고자 합니다. 혹시 이전 포스팅을 읽지 않으셨다면 이전 포스팅을 먼저 읽으신 후 이 포스팅을 읽어주시기 바랍니다.Document?컬렉션에 관해서는 앞서 소개한 이전 글에서 자세히 설명해놓았으니 읽어보시기 바랍니다. 지금 제가 언급할 것을 도큐먼트인데요. 도큐먼트는 컬렉션과는 달리 단수명사나 명사의 조합으로 표현되어 URI에 나타납니다.http://api.soccer.restapi.org/leagues/seattle/teams/trebuchet/players/claudio 위의 예제에서 leauges라는 컬렉션 리소스가 있는 것을 알 수 있습니다. 그 컬렉션의 자식 리소스 중 하나가 seattle이라는 리소스인데요, 바로 이 리소스가 도큐먼트입니다. 도큐먼트는 하위 계층으로 또 컬렉션을 가질 수 있습니다. 이 예제에서의 teams가 seattle의 자식 컬렉션 리소스가 되겠지요. 즉, 단수 리소스는 도큐먼트라 칭하고 복수 리소스는 컬렉션으로 칭한다고 알아두시면 됩니다.이 URI는 또한 문서의 계층 구조를 표현하고 있습니다. 즉 슬래시 기호(/) 다음으로 나타내는 명사가 그 앞에 나오는 명사의 자식 계층이 되는 것이지요. 이러한 도큐먼트의 응답으로써, 요청에서 명시된 Content-Type 헤더에 1:1대응하는 응답을 주는 것이 의미 있을 때가 있습니다. 가령,URI : dogs/1 1) Content-Type: application/json 2) Content-Type: application/xml 3) Content-Type: application/png 이와 같은 URI에 3개의 요청이 주어졌고, 각각 Content-Type이 다음과 같을 때 어떤 응답이 보내져야 할까요? 물론, 그것은 응답을 설계한 사람의 맘이지만 일반적인 기준을 적용해본다면 1번과 2번 요청에는 각각 json, xml 형식으로 구조화된 데이터가 그리고 3번 요청에 대해서는 해당 강아지의 사진이 담긴 png 파일을 보낼 수 있을 것입니다. 또한, Content-Type에 대해서 명시하여 원하는 리소스를 선택할 수 있으므로 URI 내에는 파일 확장자를 포함하지 않는 것이 좋습니다.dogs/1.xml 위와 같은 URI를 만드는 것보다, dogs/1 위의 URI에 Content-Type: application/xml헤더를 포함하여 요청을 보내는 것이 더 적절한 선택입니다. 어째서 파일에 확장자를 붙이지 않는 것이 더 나은 선택일까요? URI는 고유한 리소스를 나타내는 데 쓰여야 합니다. 그런데 URI에 확장자를 붙이는 순간 마치 다른 리소스인 것처럼 느껴집니다. 확장자를 달리하여 같은 리소스에 대한 다른 표현 양식을 주문하는 것이지 해당 리소스가 달라지는 것은 아닙니다. 또한, URI에 직접 확장자가 붙게 되면 해당 리소스 URI가 응답으로 지원하는 확장자만큼 새로운 URI들이 생기게 되겠지요. 결코, 이것은 좋은 디자인이 아닙니다.Controller?기본으로 GET, PUT, POST, DELETE 요청에 1:1매치 되는 개념인 CRUD가 있습니다. CRUD의 앞글자들을 풀어보면 Create, Read, Update, Delete가 될 텐데, 각각 POST, GET, PUT, DELETE에 대응되는 개념입니다. 그런데 사실 URI를 디자인 하다 보면 이러한 방식으로 나타내기 참 어려운 경우를 많이 만나게 됩니다. 그 중 가장 많은 경우가 어떤 특정한 행위를 요청하는 경우입니다. 많은 분이 이럴 때 동사를 쓰는데, 앞선 포스팅에서 밝혔듯이 동사를 써서 URI를 디자인하는 것은 대체로 옳지 않은 방식으로 여겨집니다.이럴 때 컨트롤러 리소스를 정의하여 이 문제를 해결할 수 있습니다. 컨트롤러 리소스는 URI 경로의 제일 마지막 부분에 동사의 형태로 표시되어 해당 URI를 통해 접근했을 때 일어날 행위를 생성합니다. (개념적으로는 이렇게 받아들이시면 됩니다.) 생성과 관련된 요청이 POST이기 때문에 컨트롤러 리소스에 접근하려면 POST 요청을 보내야 합니다. 예제를 살펴보시면 이해하기 빠르실 겁니다.http://api.college.restapi.org/students/morgan/register 리소스 morgan을 등록 http://api.ognom.restapi.org/dbs/reindex 리소스 dbs를 재색인 http://api.build.restapi.org/qa/nightly/runTestSuite 리소스 nightly에 테스트를 수행 그리고 마치 프로그램의 함수처럼 컨트롤러 리소스에는 입력값을 전달할 수 있습니다. 그것은 POST 요청의 엔티티 바디에 포함되어야 합니다. 그리고 역시 함수에서 반환값을 돌려주듯이 컨트롤러 리소스에서는 해당 입력 값에 대한 응답 값을 돌려주면 되겠습니다.URI 뒤에 붙는 쿼리의 용도흔히 GET 요청을 보낼 때 뒤에 추가로 쿼리 스트링(?,=,& 기호를 이용하여)을 전달하곤 합니다. 여기서는 그 쿼리 스트링을 어떻게 디자인 하는 게 좋은지에 대한 논의와 함께 실제 서비스에서 사용되는 사례를 살펴봅니다.가령 특정 컬렉션 리소스에 대하여 질의를 보낼 때 그 컬렉션의 집합이 너무 거대할 수 있으므로 필요한 정도의 정보만을 요구하기 위해서 페이징 값 혹은 구분 값을 쿼리 스트링에 포함할 수 있습니다. 예를 들어 보면/resources?pageSize=10&pageStartIndex=0 페이징을 위한 정보 전달 /dogs?color=red&state=running&location=park 구체적인 검색 제약사항 전달 이런 식으로 써서 페이징을 한다든가 혹은 다른 파라메터(color=red)따위를 던져서 검색 범위를 제한할 수 있습니다. 흔히 쿼리 스트링을 저런 용도로 많이 사용하기 때문에 아마 관찰력이 좋으신 분들은 저런 종류의 쿼리 파라메터를 네이버, 구글 같은 포털사이트의 검색 서비스를 이용하시면서 본 적이 있으실 것입니다.이와는 약간 다르게 실제 DB에서 사용하는 SQL의 select 문과 같은 결과를 낼 수 있도록 돕는 쿼리 스트링을 URI에 나타내려는 시도도 많은 편인데요. 물론 SQL에서 제공하는 구문의 모든 의미를 다 제공할 필요는 없겠지만, 기본적으로 서비스에서 필요한 정도의 인터페이스를 적절히 제공한다면 사용자가 선택할 수 있는 옵션이 많아진다는 측면에서 좋은 방법이겠죠. 이와 관련된 예제를 몇 개 소개하겠습니다. 이것은 실제 서비스에서 API로 제공되었던 URI들입니다. 구조나 의미가 SQL 문과 상당히 유사합니다.LinkedIn /people:(id,first-name,last-name,industry) 이 경우 people 리소스를 요청하되 마치 SQL 쿼리에서 가져올 필드를 제한하는 것처럼 필요한 필드에 대해서만 괄호로 묶어서 지정한 것을 볼 수 있습니다. Facebook /joe.smith/friends?fields=id,name,picture 이 경우 이름(혹은 계정이름)이 joe.smith인 사람의 정보를 가져오되 LinkedIn의 예와 같이 필드를 제한(id,name,picture)해서 가져오도록 한 예입니다. Google ?fields=title,media:group(media:thumbnail) 구글도 마찬가지네요. 이쯤 오면 대략 저 URI가 무엇을 의미하는지 알아채셨으리라 생각합니다. URI 설계시에 주의해야 할 점URI에는 소문자를 사용해야 합니다. 왜냐하면, RFC 3986은 URI 스키마와 호스트를 제외하고는 대소문자를 구별하도록 규정하기 때문이지요.http://api.example.restapi.org/my-folder/my-doc HTTP://API.EXAMPLE.RESTAPI.ORG/my-folder/my-doc 위의 두 URI는 같은 URI입니다. 호스트에서는 대소문자를 구별하지 않기 때문이지요. http://api.example.restapi.org/my-folder/my-doc http://api.example.restapi.org/My-Folder/my-doc 하지만 위의 두 URI는 다른 URI입니다. 뒤에 붙는 path가 대소문자로 구분되기 때문입니다. 물론 소문자가 아닌, 대소문자를 섞어 쓰거나 혹은 대문자만 쓰는 것도 가능하지 않으냐는 반론이 나올 수 있습니다. 하지만 대소문자를 섞어 쓰면 URI를 기억하기 어려울 뿐만 아니라 실제 사용 시 실수하기 쉽다는 단점이 있습니다. 만약 대문자만 쓴다면 상관은 없겠으나 일반적으로는 URI에 대문자를 잘 쓰지 않기 때문에 소문자로 쓰는 것을 권장합니다.HTTP HEADERHTTP 요청과 응답을 보낼 때 특정 헤더를 포함해 요청, 응답 그리고 리소스에 대한 메타 정보를 전달할 수 있습니다. 요청 헤더와 응답 헤더에 포함되면 좋을 만한 헤더 정보들에 대하여 알아보겠습니다.요청 헤더Accept응답으로 받고 싶은 미디어 타입을 명시하기 위하여 사용됩니다. 예제를 들어 설명하겠습니다.GET /magna-opus HTTP/1.1 Host: example.org Accept:text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8 이 요청은 mangna-opus 리소스에 대해서 기본적으로는 html이나 xhtml의 형식으로 응답을 받고 싶되, 만약 상황이 여의치 않으면 xml을 만약 그것도 여의치 않다면 모든 응답(*/*)을 받아들이겠다는 것을 말합니다. 옆에 붙은 q가 선호도를 나타내게 되지요. (q 생략 시 1값을 가짐) 만약 앞의 예에서 모든 응답에 대한 표시가 없다고 가정하고 서버에서 앞의 세 가지 미디어 타입을 모두 지원할 수 없는 상황이라면 응답으로 406 상태코드를 내보내야 합니다.Accept-Charset응답으로 받고 싶은 캐릭터셋에 대하여 명시하는 헤더입니다.Accept-Charset: iso-8859-5, unicode-1-1;q=0.8 가령 위의 예제는 일단 iso-8859-5를 선호하지만 unicode-1-1도 괜찮다는 메시지를 전달합니다.User-Agent현재 요청을 보낸 Agent의 정보를 표시하기 위해 사용됩니다.User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:12.0) Gecko/20100101 Firefox/21.0 파이어폭스 버전 21.0의 UA스트링, OS에 대한 정보도 담겨져있다. Referer해당 요청을 보내기 바로 직전에 참조하던 리소스 혹은 주소에 대한 정보를 나타내기 위해 사용합니다.Referer: http://en.wikipedia.org/wiki/Main_Page 응답 헤더Content-Length요청과 응답 메시지의 엔티티 바디가 얼마나 큰지에 대한 정보를 나타내기 위해 사용합니다. 단위는 바이트입니다.Content-Length: 348 Last-Modified해당 리소스가 마지막으로 갱신된 시간을 나타내기 위하여 사용됩니다. 캐싱 정책과 관련되어 중요한 헤더중 하나입니다.Last-Modified: Tue, 15 Nov 1994 12:45:26 GMT 캐시나 쿠키정책과 관련된 헤더 정보는 글의 분량을 고려하여 생략하였지만, 매우 중요한 헤더 중 하나이므로 다른 관련 문서들을 검색하여 일독을 권합니다.HTTP 상태 코드의미에 잘 맞는 URI를 설계하는 것도 중요한 일이지만 그 리소스에 대한 응답을 잘 내어주는 것 또한 중요한 일입니다. 그런데 혹시 HTTP의 상태코드 중 200이나 404코드 정도만 알고 계시지 않으신가요? 그 코드의 정확한 의미를 얘기하실 수 있으신가요? 사실 저도 흔하게 볼 수 있는 상태코드 몇 개 정도만 알고 있고 나머지 상태코드의 정확한 의미라든지 쓰임새에는 관심이 별로 없었던 것이 사실이었습니다. 하지만 전문적으로 웹 개발의 길을 걸어갈 사람이라면 그보다는 좀 더 자세히, 많이 알고 있을 필요가 있겠지요. 사실 우리가 생각하는 것보다 훨씬 많은 상태코드가 존재하고 각각 그 쓰임이 다 다릅니다. 그 중 몇 개를 살펴보겠습니다.200 : OK일반적인 요청 성공을 나타내는 데 사용합니다. 단, 주의해야 할 점이 있다면 200코드를 에러 응답에 사용하면 안 된다는 것입니다. 가령 코드는 200인데 에러메시지를 포함한다든가 하면 의미에 맞지 않은 응답코드를 보낸 것이겠지요. 이런 적절치 못한 상황을 처리하는 경우에는 4XX대 코드를 사용하여야 합니다.201 : Created리소스 생성 성공에 대한 응답 코드입니다. CRUD 요청에서 Create 요청에 대한(즉, 컬렉션에 도큐먼트 추가 같은) 응답으로 내보낼 수 있는 응답코드입니다. 응답 헤더의 Location 필드에 생성된 리소스에 접근할 수 있는 URI를 포함할 수 있다면 브라우저에서 그 값을 참조하여 적절히 대응할 수 있겠습니다.202 : Accepted대체로 처리 시간이 오래 걸리는 비동기 요청에 대한 응답으로 사용됩니다. 즉, 이 요청에 대한 응답이 결과를 포함하지 않을 수 있다는 것이죠. 하지만 최소한 응답 헤더나 응답데이터에 해당 처리를 모니터링할 수 있는 리소스 페이지를 안내하거나 혹은 해당 리소스가 처리되기까지의 예상 경과 시간 따위를 안내하는 것이 더 좋은 설계라고 할 수 있겠습니다.301 : Moved Permanently리소스가 이동되었을 경우의 응답코드입니다. 새로 리소스가 이동된 URI를 응답 Location 헤더에 명시해야 합니다. 이 응답을 받은 클라이언트는 새 URI로 이동하든지 아니면 URI를 갱신하고 캐싱을 한다든지 하는 행위를 해야 되겠지요.400 : Bad Request일반적인 요청실패에 사용합니다. 대체로 서버가 이해할 수 없는 형식의 요청이 왔을 때 응답하기 위해 사용됩니다. 무턱대고 400에러를 응답으로 주지 말고, 다른 4XX대의 코드가 더 의미를 잘 설명할 수 있는지에 대하여 고민해야 합니다.401 : Unauthorized말 그대로 리소스 접근 권한을 가지고 있지 않다는 것을 의미하기 위한 응답코드입니다. 리소스를 획득하기 위하여 요청자는 인증에 필요한 헤더(가령 Authorization 헤더 같은)나 데이터를 첨부해야 할 것입니다. 필요한 헤더나 데이터는 서버 쪽에서 요구하는 스펙을 충실히 따라야겠지요.403 : Forbidden감춰진 리소스에 접근하려 할 때의 응답코드입니다. 401과 달리 인증의 여부와 관계없이 리소스를 보여주지 않습니다. 기본적으로 클라이언트 쪽에 정보를 공개하고 싶지 않은 리소스임을 나타내기 위해 사용합니다.404 : Not Found해당 URI와 매치되는 리소스가 없다는 의미를 전달합니다. 어지간한 사람들은 다 한 번씩(?) 마주치게 되는 응답코드이지요.405 : Method Not Allowed지원하지 않는 요청(예를 들어 POST 요청을 받는 컨트롤러 리소스에 GET 요청을 보낸다든가)을 하였을 때 사용합니다. 가능하다면 응답 메시지에 Allow 헤더를 추가하고 그곳에 지원하는 메서드를 명시하여 클라이언트 측에서 정확한 요청을 보낼 수 있도록 유도합니다.Allow: GET, POST 406 : Not Acceptable해당 미디어 타입(MIME 타입)에 대해서 지원하지 않을 때 사용합니다. 요청 Accept 헤더에 명기된 타입(가령 Application/xml)에 대해서 지원이 불가능할 경우에 돌려주면 되는 코드입니다.409 : Conflict요청의 형식에는 문제가 없지만 리소스 상태에 의하여 해당 요청 자체를 수행할 수 없는 경우의 응답코드입니다. 즉, 이미 삭제된 리소스를 또 삭제한다든가 비어있는 리스트에서 무언가를 요청한다든가 하는 모순된 상황을 생각해보면 되겠습니다. 응답으로는 그 방법을 어떻게 해결할 수 있을지에(혹은 문제가 무엇인지) 대한 힌트가 포함되면 좋을 것입니다.500 : Internal Server Error일반적인 서버 에러에 대한 응답코드입니다. 4XX대의 에러코드가 클라이언트 측 에러를 나타내기 위해 사용된다면, 5XX대의 에러코드는 서버 측 에러를 나타내기 위해 사용됩니다.503 : Service Unavailable가장 두려운(?) 응답코드 중 하나일 503입니다. 현재 서버에 과부하가 걸려있거나 유지보수를 위하여 잠시 접근이 거부될 때 필요한 응답코드입니다.그냥 맨 앞의 숫자별로 퉁쳐서 상태코드를 내보내지 않고, 이렇게 디테일한 의미까지 따져가면서 상태코드를 내보내는 것에 대해서 그 효용성에 의문을 제기하시는 분들이 있을 것 같습니다. 하지만 브라우저에서 혹은 서버 단에서 특정 상태코드에 대해서 내부 구현을 달리하거나 최적화를 통해 더 쾌적한 환경을 제공할 가능성이 있으므로 되도록 의미에 걸맞은 상태코드를 사용하는 것을 생활화하는 것이 중요합니다. 또한, 이렇게 디테일한 상황을 가정하고 만든 URI들이 다음에 서비스를 확장할 때 큰 도움이 될 것임은 의심할 여지가 없겠지요.위에서 소개한 응답 코드 말고 또 다른 응답 코드들에 대해서도 전부 소개해 놓은 링크를 밑에 달아두었으니 참고하시기 바랍니다.정리지금까지 소개한 내용이 조금은 두서없게 느껴졌을 수도 있겠다는 생각이 들어 한 번 전체 내용 정리를 해보려 합니다.컨트롤러의 정확한 쓰임을 알고 적절한 컨트롤러 URI를 구현하자.URI에 추가로 붙게 되는 쿼리 스트링의 형식을 잘 디자인하여 사용자로 하여금 적재적소에 쓸 수 있도록 하자.가능하다면 이용 가능한 HTTP 헤더를 적절하게 첨가하자.HTTP 상태코드의 의미에 대해서 생각해보고 상황에 맞는 적절한 상태 코드를 응답으로 보내줄 수 있도록 하자.이 글을 쓰면서 한빛 미디어의 일관성 있는 웹 서비스 인터페이스 설계를 위한 REST API 디자인 규칙과 apigee사의 web API design eBook을 참고하였습니다. 둘 다 내용이 좋은 서적이고 이 글에서 다루지 않은 심층 내용을 다루니 기회가 되시면 읽어보세요.referencesUniform resource identifierapigee api design best practicesrestful uri designHTTP status codesList of HTTP status codesURI schemeMIME typesMIMEfun and unusual http response headers#스포카 #디자인 #디자이너 #디자인팀 #개발 #개발자 #개발팀 #협업 #코워킹 #Co-working #업무프로세스 #꿀팁 #인사이트
조회수 10492

Next.js 튜토리얼 7편: 데이터 가져오기

* 이 글은 Next.js의 공식 튜토리얼을 번역한 글입니다.** 오역 및 오탈자가 있을 수 있습니다. 발견하시면 제보해주세요!목차1편: 시작하기 2편: 페이지 이동 3편: 공유 컴포넌트4편: 동적 페이지 5편: 라우트 마스킹6편: 서버 사이드 7편: 데이터 가져오기 - 현재 글8편: 컴포넌트 스타일링9편: 배포하기개요꽤 그럴듯한 Next.js 애플리케이션을 만드는 방법과 Next.js 라우팅 API의 모든 장점을 배웠습니다.대부분의 경우 데이터 소스에서  원격으로 데이터를 가져와야 합니다. Next.js는 페이지에 데이터를 가져오기 위한 표준 API를 제공합니다. getInitialProps라 불리는 비동기 함수를 사용하여 구현할 것입니다.주어진 페이지에 원격 데이터 소스를 통해 데이터를 가져오고 원하는 페이지에 props을 통해 전달할 수 있습니다. 서버와 클라이언트 둘 다 동작하도록 getInitialProps를 작성할 수 있습니다. 그래서 Next.js는 클라이언트와 서버에서 모두 사용할 수 있습니다. 이번 편에서는 getInitialProps를 사용하여 공개된 TVmaze API에서 가져온 데이터로 배트맨 TV 쇼에 대한 정보를 보여주는 애플리케이션을 구현할 예정입니다.설치이번 장에서는 간단한 Next.js 애플리케이션이 필요합니다. 다음의 샘플 애플리케이션을 다운받아주세요:아래의 명령어로 실행시킬 수 있습니다:이제 http://localhost:3000로 이동하여 애플리케이션에 접근할 수 있습니다.배트맨 쇼 데이터 가져오기데모 애플리케이션 내의 home 페이지에 블로그 포스트 목록이 있습니다. 배트맨 TV 쇼 목록을 표시할 것입니다.쇼의 데이터들을 하드코딩하는 대신에 원격 서버에서 그 정보를 가져옵시다.여기서는 TV 쇼를 가져오기 위해 TVMaze API를 사용합니다.TV 쇼 정보를 검색하는 API 입니다.먼저 isomorphic-unfetch를 설치해야 합니다. 데이터를 가져올 때 사용할 라이브러리입니다. 브라우저 fetch API 구현을 간단히 할 수 있도록 만들어진 것이지만 클라이언트와 서버 환경에서 모두 동작합니다.npm install --save isomorphic-unfetchpages/index.js를 다음과 같이 변경해주세요:위의 페이지에 있는 모든 내용은 아래에 표시된 Index.getInitialProps를 제외하고는 익숙할 것입니다:애플리케이션의 어떤 페이지에든 추가할 수 있는 정적 비동기 함수입니다. 이것을 사용하여 데이터를 가져오고 가져온 데이터를 props를 통해 페이지로 보낼 수 있습니다.보다시피 배트맨 TV 쇼 데이터를 가져오고 'shows' props를 통해 페이지로 전달합니다.위에서 보았던 getInitialProps 함수에서 가져온 데이터 숫자를 콘솔에 출력합니다.이제 브라우저 콘솔과 서버 콘솔을 살펴봅시다. 그리고 페이지를 새로고침 해주세요.페이지를 새로고침 한 후 출력되는 메시지는 어디에서 보였나요?- 서버 콘솔- 브라우저 콘솔- 둘 다- 어떤 콘솔에도 출력되지 않았다서버에서만 출력됩니다이 경우 메시지는 서버에서만 출력됩니다.이는 서버에서 페이지가 랜더링되기 때문입니다.이미 데이터를 가지고 있어 클라이언트에서 다시 정보를 가져올 필요가 없습니다.post 페이지 구현하기TV 쇼에 대한 자세한 정보를 보여주는 "/post" 페이지를 구현해봅시다.먼저 server.js를 열고 /p/:id 라우트를 다음과 같이 바꿔주세요.위처럼 바꾼 코드를 적용하기 위해 애플리케이션을 재실행시켜주세요.이전에는 title 쿼리 파라미터를 페이지에 매핑했습니다. 이제 id로 이름을 바꿔야합니다.다음과 같은 내용으로 pages/post.js를 변경해주세요.페이지의 getInitialProps을 살펴봅시다:여기에서 함수의 첫 번째 파라미터는 context 객체입니다. 정보를 가져올 때 사용할 수 있는 쿼리 필드를 가지고 있습니다.예제에서 쿼리 파라미터로부터 보여지는 ID를 선택하고 TVMaze API로부터 데이터를 가져옵니다.이 getInitialProps 함수에서 표시할 제목을 출력하는 console.log를 추가했습니다. 이제 어디에서 출력되는지 볼 수 있습니다.서버와 클라이언트의 콘솔를 둘 다 열어주세요.그 다음 홈페이지 http://localhost:3000로 이동하여 배트맨 쇼 제목을 클릭하세요.위에서 애기했던 console.log 메시지가 보여지는 장소는 어디인가요?- 서버 콘솔- 브라우저 콘솔- 콘솔 둘 다- 아무 콘솔에서도 출력되지 않는다클라이언트 사이드에서 데이터 가져오기브라우저 콘솔에서 메시지를 볼 수 있습니다.클라이언트 사이드를 통해 포스트 페이지에 이동했기 때문입니다. 그런 다음 클라이언트 사이드로부터 데이터를 가져오는 것은 가장 좋은 방법입니다.예를 들어 http://localhost:3000/p/975에 직접 이동한다면 클라이언트가 아닌 서버에서 메시지가 출력되는 것을 볼 수 있습니다.마무리데이터를 가져오고 서버 사이드에서 렌더링하도록 만드는 Next.js의 가장 중요한 기능 중 하나를 배웠습니다.대부분의 유스 케이스에서 충분히 사용할 수 있는 getInitialProps의 기본을 배웠습니다. 더 많은 것을 배우고 싶다면 Next.js의 문서 중 data fetching 문서를 참고할 수 있습니다.#트레바리 #개발자 #안드로이드 #앱개발 #Next.js #백엔드 #인사이트 #경험공유
조회수 17496

Nodejs 기반의 개발 환경 클린하게 재 구성하기

다양한 언어 기반으로 개발 환경을 구축하여 만들다보면, 소프트웨어 버전관리 해야할 일이 흔히 생기곤 한다. 특히, 종종 대격변이 있는 버전의 판올림으로인해 충돌이 나거나 심볼릭 링크가 유실되는 경우들이 간혹 있는데 이번에도 그런 케이스였다.최근 node.js 기반으로 다양한 프로젝트 (vue.js, react.js등)를 진행하다가 이것저것 환경을 만지고 고치다보니 결국 node.js 를 완전히 클린하게 삭제해야 할 일이 생겼다.아마 이 환경에 결정타를 먹인 것이 OSX 환경에서 El Capitan에서 작업하던 Node.js를 그대로 high sierra로 OSX를 판올림 하면서 퍼미션 권한의 문제가 생긴건지, 노드 패키지 관리나 npm이 정상적으로 동작하지 않으면서 개발환경을 재 설정 할 수 밖에 없게 되었는데, 그 과정에 기름을 부어버리듯 당시에 brew로 설치한 노드가 brew로 삭제가 되지 않는 문제가 발생해버렸다.결국 환경을 처음부터 재 설치 해야하는 과정을 겪어야했는데 기존에 설치된 다양한 패키지 모듈의 찌꺼기들이 남아서 한방에 클린 설치를 할 수 있는 방법이 없을까 싶어 구글링을 해본 결과 앞서서 수많은 시행착오를 겪은 선배님들의 아주 좋은 작업 방식이 있어서 아래에 방법을 공유해본다.요세미티에서 nodejs 정리하는 법 [1]Uninstall nodejs from OSX Yosemite# 첫번째:lsbom -f -l -s -pf /var/db/receipts/org.nodejs.pkg.bom | while read f; do  sudo rm /usr/local/${f}; donesudo rm -rf /usr/local/lib/node /usr/local/lib/node_modules /var/db/receipts/org.nodejs.*# 완전히 nodejs + npm 을 날려버리는 방법 :# /usr/local/lib 경로로 가서 node 와 관련된 노드 모듈을 전부 삭제cd /usr/local/libsudo rm -rf node*# /usr/local/include 경로로 가서 node 와 관련된 노드 모듈 전부 삭제cd /usr/local/includesudo rm -rf node*# 만약 brew 로 인스톨을 했다면 아래와 같은 방법으로 삭제도 가능함. (저는 아래는 brew자체가 망가졌었는지 판올림으로 인한 권한 문제인지 brew로는 삭제 불가능했음.)brew uninstall node# home 디렉토리나 local, lib, include등의 폴더와 관련된 모든 파일은 아래의 경로에 있으니 찾아 들어가서 삭제cd /usr/local/binsudo rm -rf /usr/local/bin/npmsudo rm -rf /usr/local/bin/nodels -las# 아마 혹시 모르니까 클린하게 아래의 명령어도 한번 돌려주자sudo rm -rf /usr/local/share/man/man1/node.1sudo rm -rf /usr/local/lib/dtrace/node.dsudo rm -rf ~/.npmhomebrew를 사용하는 유저들 중에 npm이 제대로 동작하지 않으면 아래와 같은 방법으로도 처방이 가능하다. [2]rm -rf /usr/local/lib/node_modulesbrew uninstall nodebrew install node --without-npmecho prefix=~/.npm-packages >> ~/.npmrccurl -L https://www.npmjs.com/install.sh | sh클린하게 설치를 끝나고 react-native를 컴파일하는 과정에서 깃에 관련된 오류가 발생한다면 아래의 방법을 사용해보자. [3]오류메세지 :xcrun: error: invalid active developer path (/Library/Developer/CommandLineTools), missing xcrun at: /Library/Developer/CommandLineTools/usr/bin/xcrun솔루션 :xcode-select --install엘케피탄에서 하이시에라로 osx를 업데이트 하면서 homebrew의 링크가 깨졌다면 아래의 방법으로 다시 붙여준다. [4]sudo chown -R "$USER":admin /usr/localsudo chown -R "$USER":admin /Library/Caches/Homebrewbrew link libpng참고 출처 :[1] : https://gist.github.com/TonyMtz/d75101d9bdf764c890ef[2] : https://stackoverflow.com/questions/32893412/command-line-tools-not-working-os-x-el-capitan-macos-sierra-macos-high-sierra[3] : https://stackoverflow.com/questions/39778607/error-running-react-native-app-from-terminal-ios[4] : https://github.com/mikepurvis/ros-install-osx/issues/28 #더팀스 #THETEAMS #풀스택개발자 #Node.js #백엔드 #인사이트 #꿀팁
조회수 1466

비트윈이 사용자를 분석하는 방법

빅데이터분석이 최근 이슈가 되면서 관심이 많으실 것 같습니다. 비트윈팀도 데이터 분석 참 좋아하는데요, 저희도 한번 해보았습니다. 이번 포스팅에서는 비트윈팀의 데이터 분석 노하우를 아낌없이 공유해드립니다.왜 사용자의 데이터를 분석해야하는가요?비트윈같은 서비스는 초기 단계에는 앱을 기획하고 만들어낸 팀에 아이디어에 의해 계속해서 발전하고, 유지됩니다. 하지만 기능이 점점 다양해지고 사용자가 점점 많아지면서 사용자들의 앱 사용패턴을 점점 예측하기 어려워집니다. 게다가 비트윈은 해외 진출을 구상 중이었는데, 개인 혹은 팀의 아이디어만으로 해외에서의 사용패턴을 정확히 알기는 어려웠습니다.이런 시점에 필요한 것이 사용자 분석입니다.사용자들의 사용패턴을 분석해 보는 방법은 여러 가지가 있습니다. 초기에 해볼 수 있는 가장 직관적이고 쉬운 것은 비트윈을 사용하는 자기 자신의 사용 패턴을 돌아보고 분석해보는 것입니다. 또 친구들이나 익명 사용자들의 사용패턴을 물어보거나, 관찰하는 방법들이 있습니다. 이런 방법은 매우 효과적이고 많은 아이디어를 주지만 여러 가지 한계점이 있습니다. 지역적, 시간적인 한계 등이 그것입니다.그래서 택할 수 있는 방법이 실제로 사용자들의 행동을 컴퓨터로 수집해서 분석하는 것입니다. 말 그대로 '데이터 분석'을 하게 되는 것입니다.무엇을 분석할지 알아야 합니다데이터로 분석할 수 있는 것은 무궁무진합니다만, 먼저 데이터가 있어야합니다. 비트윈과 같이 서버와 통신하는 앱은 사용자들이 서버에 요청을 할 때마다 엑세스 로그를 남기게 됩니다. 이 엑세스 로그는 사용자들의 사용패턴을 고스란히 담고 있어, 소중한 데이터가 됩니다.엑세스 로그 분석은 전혀 어렵지 않습니다. 엑세스 로그에서 특정 행동에 해당하는 내용을 세는 것만으로도 여러 가지 유의미한 값을 얻어낼 수 있습니다. 하루 동안의 로그를 한줄씩 읽어서 메시지에 관련된 로그를 카운트하면 그날의 메시지 전송 건수를 얻을 수 있는 것입니다. (참 쉽죠?)엑세스로그에서 가입, 메시지, 사진, 메모 등 기본적인 내용에 해당하는 것들을 카운트하는 것만으로도 꽤 자세하게 앱 전체 사용자들의 전반적인 사용통계를 얻어낼 수 있습니다. 이제 해당 데이터를 엑셀에 넣어서 차트를 그려보면, 사용 통계에 대한 그럴싸한 차트가 그려집니다.엑세스 로그 분석에 성공했다면 좀 더 다양한 분석을 해볼 수 있을 텐데요, 사용자별 행동패턴 분석이나, 나라별, 혹은 아이폰, 안드로이드 디바이스별 분석 등 다양한 분석을 시도해볼 수 있습니다. 분석을 하기 전에 중요한 것은 무엇이 궁금한지, 어떻게 궁금한 데이터를 모을지 아이디어를 먼저 내는 것입니다. 여러 예제들을 찾아보며 공부해보면, 금방 좋은 아이디어를 얻으실 수 있을 겁니다.물론 여기서 중요한것은 개인정보나 사생활의 보호입니다. 로그가 유출되었을때의 보안 문제 뿐 아니라, 데이터 분석팀에게조차 개인정보가 노출된다면 곤란합니다. 이 문제에 저희가 어떻게 대처하고 있는지는 글 뒷부분에 자세히 알려드리겠습니다.특정 기술에 구애받지 말고 다양하게 구현해봅시다처음에는 로그 파일을 돌며 간단한 string을 검사하는 스크립트와 엑셀로도 충분했지만, 점점 복잡한 분석을 할수록 다양한 기술이 필요해집니다. 비트윈 사용자 분석도 점점 다양해지고 복잡해지면서 여러 가지 기술들을 사용하고 있습니다.비트윈 사용자 분석은 처음에는 6줄짜리 간단한 shell script에서 시작되었습니다.cat 2011-10-31.log | grep /messages | grep POST | wc -lcat 2011-10-31.log | grep /photos | grep POST | wc -lcat 2011-10-31.log | grep /memos | grep POST | wc -lcat 2011-10-31.log | grep /like | grep POST | wc -lcat 2011-10-31.log | grep SIGN | wc -lcat 2011-10-31.log | grep REL | grep POST | wc -l이런 스크립트를 만들어서 결과를 이메일로 공유하거나, 엑셀로 만들어 놓곤 했습니다.여기에 비트윈 분석은 조금 더 발전하여, 로그파일을 쿼리하여 Map Reduce 작업이 가능한 Hive를 사용하고, PHP로 통계 웹사이트를 만들어 차트를 그리기 시작했습니다. 이 방식은 처음에는 매우 편리했지만 차츰 쿼리만으로 원하는 결과를 얻기가 힘든 다소 복잡한 분석이 필요해지기 시작했습니다.현재는 모든 로그를 분산 데이터베이스인 HBase에 Date Key와 User Key로 넣고, 코드 생산성이 좋은 Scala로 직접 Map Reduce코드를 작성해서 데이터들을 분석하고 있습니다. 그래서 충분히 scalable하면서도 꽤 편리하게 이용할 수 있는 데이터베이스를 활용하고, Scala의 좋은 expression을 활용하여 짧고 유지보수나 확장이 쉬운 코드로 분석을 수행하면서도 Java와 호환되는 Scala의 특성을 이용하여 Map Reduce 코드 작성을 효과적으로 하고 있습니다. 이렇게 분석한 데이터는 MySQL에 넣어서 2차로 가공하고, Scala Web Framework인 Play Framework을 이용하여 분석 사이트를 구축하고 D3 Chart를 이용해서 Visualize하고 있습니다. 이렇게 함으로써 편리한 MySQL 쿼리 사용의 장점을 취하고 멋진 차트를 효과적으로 그려낼 수 있습니다.좋은 Visualization은 멋질 뿐만 아니라 손쉽게 아이디어를 공유할 수 있게 해줍니다.앞으로는 더 빠른 성능을 위해 Hive를 더 잘 사용해보거나, Elastic Search같은 index engine들을 사용해 볼 계획도 가지고 있습니다. 또한 End point들에서 직접 성능을 측정하여 중앙으로 모아서 분석해보려는 생각도 가지고 있습니다.기술을 선택함에 있어서 정답은 없는 거 같습니다. 널리쓰이는 MySQL같이 scalability가 좀 떨어지지만, 다양한 쿼리로 높은 생산성을 낼 수 있는 데이터베이스도 있고, HBase같이 scalability가 좋지만, 데이터를 저장하는 형태에 제한이 있어 생산성이 조금 떨어지는 데이터베이스도 있습니다. 저희는 앞서 소개드렸듯이 이 두 가지를 모두 혼용하여 사용하고 있습니다. 각자가 마주한 상황에 맞게, 또 각자가 익숙한 기술에 맞게 설계하고, 사용해보면 됩니다.개인정보 보호는 철저하게빅데이터 분석이 개인정보를 침해하는 빅 브라더가 될 수 있다는 우려들이 나오고 있습니다. 300만이 넘는 커플들의 비밀스러운 일기를 담고 있는 비트윈 서비스는 당연하게도 모든 업무를 진행하는 데 있어 보안과 개인정보를 최우선으로 하고 있습니다. 데이터 분석에서도 분석할 수 있는 내용을 상당히 제한받더라도, 예외 없이 그 원칙을 지키고 있습니다.비트윈의 API서버는 AWS클라우드에서 운영되고 있는데, 사용료가 상당히 비싸기 때문에 큰 컴퓨팅 파워를 사용해야 하는 데이터분석까지 AWS에서 하기엔 좀 부담이 되었습니다. 그래서 PC급 컴퓨터 여러 대를 구입하여 사무실 구석에 쌓아놓고 사용하고 있습니다.하지만 문제는 보안이었습니다. AWS의 비트윈 API서버는 다중으로 보안이 유지되고 있지만, 사무실에 있는 서버에 사용자들의 개인정보를 담아둘 수는 없는 일이었습니다. SECO*이 사무실을 지켜주고 있긴 하지만 보안회사에 고객들의 소중한 개인정보를 맡기고 안심할 수는 없으니까요. 그리고 설사 보안 문제가 잘 해결된다고 해도, 분석을 수행하는 비트윈 데이터분석팀원에 개인정보 혹은 사생활이 노출된다면 그 또한 문제라고 생각하였습니다.그래서 저희가 생각해낸 방법은 '익명화'입니다. Access Log들을 저장할 때 사용자의 아이디를 전부 단방향 salted-hash하여 누구인지 알 수 없게 만들었습니다. (물론 salt key는 데이터 분석팀은 알 수 없습니다.) 그리고 애초에 Access Log에는 '어떤 사람'이 '50글자짜리 메시지를 보냈다' 라던가, '사진을 올렸다' 정도만 기록이 되기 때문에, 이를 통계적으로 분석하는 것은 유의미하지만, 사적인 정보를 담고 있지는 않습니다.익명화되어 처리되고 있는 로그는 개인정보는 거의 담고 있지 않으면서도, 유익한 분석 결과를 만들어줍니다.이런식으로 운영을 한다면 데이터 분석팀에서도 사적인 정보(예: 메시지 내용)에 대해서는 접근할 수 없기 때문에, 회원들의 소중한 개인정보와 사생활을 지킬 수 있습니다. 어떤 분석을 수행할 때 언제나 비트윈팀은 언제나 보안과 사생활 보호의 원칙을 지킬 수 있는 범위에서만 진행하고 있습니다.아이디어의 공유, 그리고 액션아이템이 무엇보다도 중요합니다데이터 분석의 목표가 무엇인지, 왜 해야 하는지 생각해보면, 무엇을 해야 하는지 알 수 있습니다. 바로 분석으로부터 얻은 아이디어를 공유하고 액션아이템을 정하고 실천하는 것입니다.데이터를 visualization하는것이 중요한 이유가 여기에 있습니다. 보기 좋은 떡이 먹기도 좋다는 말이 있듯이, 데이터도 먹기 좋아야 합니다. 여러 사람이 쉽게 이해할 수 있어야 아이디어를 공유하고 의사결정을 내리기가 수월하기 때문입니다.민트&베리 사용량 분석. 연인들이 쓰는 앱이라 사랑표현이 인기가 많군요. 디자인팀이 이런 자료를 참고하여 이후 디자인 아이디어를 내는 데 도움이 되면 좋겠죠?비트윈팀은 매번 데이터 분석 미팅을 진행하고 나면 액션아이템을 정하고 실천합니다. 저희가 어떤 식으로 의사결정을 내리고 행동하는지에 대해서는 비트윈 팀블로그의 VCNC는 데이터분석에 기반해 어떤 결정을 내렸나 포스팅을 보시면 도움이 되실 것 같네요.맺으며이번 포스팅에서는 비트윈팀이 어떻게 무엇을 분석하는지 간단하게 다뤄봤습니다. 의견이나 참견 모두 환영이니 댓글 많이 남겨주세요! 다음번 포스팅엔 기술적인 부분에 대해 좀 더 자세하게 다뤄보도록 하겠습니다.저희는 언제나 타다 및 비트윈 서비스를 함께 만들며 기술적인 문제를 함께 풀어나갈 능력있는 개발자를 모시고 있습니다. 언제든 부담없이 [email protected]로 이메일을 주시기 바랍니다!
조회수 868

[Tech Blog] Keep Principles in Mind

원칙(Principle)은 중요합니다. “난 원칙대로 살지 않겠어!” 라고 외치고 싶더라도, 원칙이 있고 원칙을 충분히 이해하고 있지 않다면 그저 사춘기 소년/소녀의 이유 없는 반항 정도로 밖에 들리지 않을테니까요. 사실 대부분의 이런 경우 원칙 보다는 “규칙(Rule)대로 살지 않겠다”에 가깝지만, 여기에서는 그냥 넘어가도록 하죠. 소프트웨어 개발에도 다양한 원칙들이 존재합니다. 학부 수업에서 잠깐 들었거나 이런 저런 글들을 읽다가 접해 봤을 이런 원칙들은 실제 서비스를 만들면서 바쁘게 기능을 추가하고 버그를 수정 하느라 어느새 기억 속에서 잊혀지곤 하죠. 정신없이 기능을 구현하다가 문득 코드를 돌아봤을 때 ‘이게 왜 여기에 있지’ 라는 의문이 든다면 한 번쯤 원칙을 되새겨 보라는 신호가 아닐까요? 이 글에서는 Clean Architecture 와 Clean Code 등의 저자로 유명한 Uncle Bob(Robert C. Martin)이 얘기하는 S.O.L.I.D Principles 에 대해 얘기해 보려고 합니다. SOLID 원칙은 밥 아저씨가 2000년도 자신의 논문 Design Principle and Design Patterns 에서 OOD(Object-Oriented Design)를 위해서 제안한 5가지 원칙의 앞 글자만 떼서 붙여졌습니다. Object-Oriented Design 을 대상으로 제안된 원칙이지만 Agile 개발 등의 개발 방법론 핵심 철학에도 적용될 수 있는 개념들 입니다. S.O.L.I.D Principles Single Responsibility Principle Class 는 오직 한 가지의 책임이 주어져야 하고, 오직 한 가지 이유에서만 변경되어야 합니다. 보고서를 편집하고 출력하는 모듈에 대해서 생각해 볼까요. 해당 모듈은 두 가지의 이유로 변경될 가능성이 있습니다. 보고서의 내용이 바뀌었을 때도 변경되어야 하고, 보고서의 형식이 바뀌었을 때도 변경되어야 합니다. 편집 과정 때문에 모듈을 변경하다 보면 해당 변경 사항이 출력 부분에도 영향을 미칠 가능성이 상당히 높습니다. 이 경우 내용을 편집하는 모듈(i.e 내용을 담당하는 모듈)과 출력하는 모듈(i.e 형식을 담당하는 모듈) 두 가지로 나뉘어야 합니다. “할 수 있다고 해서 해야 한다는 뜻은 아닙니다.” Open / Closed Principle Class, Module, Function 등의 소프트웨어 구성 요소는 확장(extension)에 대해 열려 있어야 하며, 변경(modification)에 대해 닫혀 있어야 합니다. 어떤 모듈이 Data Structure 에 필드를 추가하거나 함수를 추가하는 등 확장이 가능하다면 그 모듈은 확장에 대해 열려 있다고 표현합니다. 반면에 어떤 모듈이 수정 없이 다른 모듈에 의해 사용될 수 있다면 그 모듈은 닫혀 있다고 표현합니다.  public class CreditCard {     private int cardType;       public int getCardType() { return cardType; }       public void setCardType(int cardType) { this.cardType = cardType; }          public double getDiscount(double monthlyCost){          if (cardType == 1) {              return monthlyCost * 0.02;          } else {              return monthlyCost * 0.01;          }     } }  위 CreditCard class 에 새로운 카드 타입을 추가하려고 하면 getDiscount 함수를 변경할 수 밖에 없습니다. 이 경우 Open/Closed Principle 을 위반된다고 볼 수 있습니다. “코트를 입기 위해서 개복 수술을 할 필요는 없으니까요.” Liskov Substitution Principle 프로그램 상의 Object 들은 프로그램의 정확성을 해치지 않으면서 하위 타입의 Instance 로 변경 가능해야 합니다. 하위 타입 함수 인자의 반공변성(Contravariance), 하위 타입 함수 반환 타입의 공변성(Covariance), 상위 타입의 예외를 상속하지 않는 추가적인 예외 발생 금지 등의 요구 사항이 있습니다. OOP 에서 상속 개념을 배울 때 이해를 돕기 위해 주어진 몇 가지 예시들이 있었을텐데, 우습게도 우리가 생각하기에 타당한 상속에 관한 예시들 중 의외로 원칙을 위배하는 경우가 많습니다. Liskov Substitution Principle 을 위반하는 대표적인 예시는 정사각형과 직사각형입니다. 정사각형은 직사각형의 일종이니 Square가 Rectangle을 상속받는 것이 충분이 타당한 것으로 보입니다. 정말 그럴까요? Rectangle 의 넓이를 구하는 함수의 테스트를 구성해 봅시다.  Rectangle rect = new Rectangle(); rect.setWidth(10); rect.setHeight(20); assertEquals(200, rect.getArea());  여기에 new Rectangle() 대신에 new Square()가 rect 에 할당되면 어떻게 될까요? 넓이는 400 을 반환하기 때문에 테스트는 실패하겠죠. 정사각형이 직사각형을 상속 받으면 Liskov Subsitution Principle 을 위반한다고 볼 수 있습니다. 상속은 문제를 해결하는데 있어서 상당히 유혹적인 방법입니다. 하지만 상당히 많은 경우에 상속을 오용할 가능성이 높습니다. “오리처럼 생기고 오리처럼 꽥꽥 거리더라도, 배터리가 필요하다면 오리가 아닙니다.” Interface Segregation Principle 많은 것을 아우르고 일반적으로 사용 가능한 하나의 interface 보다 특정 클라이언트를 위한 여러 개의 interface 가 낫습니다. Xerox는 Stapling(프린터기가!?), Fax 등의 다양한 기능이 포함된 신규 프린터 소프트웨어를 개발 도중, 더이상 개발이 불가능할 정도로 프로그램이 번잡 해졌다는 것을 인정하고 밥 아저씨에게 도움을 요청합니다. 문제는 Job Class 하나가 모든 기능을 다 구현하고 있다는데 있었습니다. 이 비대한 Class 는 Client 입장에서 사용되지도 않을 모든 함수를 알 수 있게 구성 되어 있었죠. 이 문제에 대해 밥 아저씨는 Interface Segregation Principle 을 적용하여 각 Client 입장에서 사용해야 하는 함수 만을 가지고 있는 각 interface 들을 따로 만들었습니다. 그리고는 다음에 나올 Principle 인 Dependency Inversion Principle 을 통해서 해당 기능을 구현하게 함으로써 문제를 해결했습니다. Dependency Inversion Principle “추상화에 의존해야지, 구체화에 의존하면 안됩니다.” 상위 계층의 모듈은 하위 계층의 구현이 아니라 추상화에 의존해야 합니다. 상위 계층이 하위 계층의 구현에 의존하던 전통적인 의존 관계를 역전 시킴으로써 상위 계층이 하위 계층의 구현으로부터 독립되게 할 수 있습니다. 예를 들어 Dependency Injection 은 이 원칙을 따르는 방법 중 하나 입니다. Conclusion 세상에 나쁜 프로그램은 있습니다. 당장 눈에 보이는 기능이 똑같다고 같은 프로그램인 것은 아닙니다. 생각보다 많은 코드들이 ‘그 곳에 넣을 수 있기 때문에’, ‘그 곳에 넣어도 돌아가기 때문에’ 깊은 고민 없이 그 곳에 정착합니다. 당장 좀 더 빠르게 기능을 추가해서 주변 사람들의 박수를 받을 수도 있습니다. 허나 이것들이 쌓이면 더이상 손댈 수 없는 코드가 되고, 문제를 느끼고는 Refactoring을 하자고 다짐하고, 모두 엎은 다음 또 다시 같은 코드를 만들게 되겠죠. 쉬운 코드가 가장 만들기 어려운 코드이고, 그런 좋은 코드는 좋은 원칙으로 부터 나옵니다. 변화에 적응할 수 있는 프로그램, 의도가 쉽게 읽히는 프로그램, 문제 발생 가능성이 적은 프로그램, 쉽게 확장할 수 있는 프로그램 등 좋은 프로그램을 만드는 것은 우리가 실제로 목표하는 것을 달성하기 위해서 정말 중요합니다. 이는 그저 경험이나, Tweak 만으로 이루어지지 않습니다. 다양한 신규 기술들과 Framework 들을 두루 섭렵하면서 활동 반경을 넓히고 경험을 쌓았다면, 가끔은 잠시 서서 원칙에 대해 되돌아 보는 것은 어떨까요?   *버즈빌에서 활기찬 개발자를 채용 중입니다. (전문연구요원 포함)작가소개 Whale, Chief Architect “Keep calm and dream on.”
조회수 1839

Swift 4.1에서 딥링크로 앱을 여는 경우 크래시되는 문제 해결하기

최근 Xcode 9.3 버전이 배포되었습니다. 이 버전에는 가장 최신의 Swift 4.1 버전이 포함되어 있습니다. Swift 4.1에는 여러 흥미로운 개선사항들이 많지만, 치명적인 버그도 존재합니다. 바로 딥링크를 통해 앱을 여는 경우 크래시가 발생하는 문제입니다. StyleShare에서는 QA 과정을 통해 문제를 발견할 수 있었습니다.만약 여러분의 애플리케이션이 아래 조건을 모두 충족할 경우 문제가 발생합니다:Swift 4.1 버전을 이용해서 빌드한 경우Deployment Target이 iOS 11.0 미만인 경우AppDelegate에서 application(_:open:sourceApplication:annotation:) 메서드를 구현한 경우문제를 재현하기에 가장 좋은 방법은 Safari 앱을 이용하는 것입니다.1. iOS 기기 또는 사뮬레이터에서 Safari 앱을 구동합니다.2. 주소 입력란에 앱이 지원하는 딥링크 URL을 입력한 뒤 이동합니다. (e.g. myapp://)3. 앱이 구동됨과 동시에 강제 종료됩니다.이 버그는 Swift 이슈 트래커에 SR-7240 티켓으로 이미 등록되어 있습니다. Resolved 상태로 표시되지만 이번 Xcode 9.3 버전에는 포함되지 않은 것으로 보입니다. 다행히 댓글에 한 개발자가 문제를 해결할 수 있는 workaround를 공유해두었는데요. 이 방법을 이용하면 당장의 문제는 해결할 수 있습니다. AppDelegate 메서드의 annotation 파라미터의 타입을 Any에서 Any?로 변경하는 것입니다.- func application(_ application: UIApplication, open url: URL, sourceApplication: String?, annotation: Any) -> Bool + func application(_ application: UIApplication, open url: URL, sourceApplication: String?, annotation: Any?) -> Bool<iframe width="700" height="250" data-src="/media/0ce1fe8c63fca7a6c953233b94406d02?postId=ed495077c36" data-media-id="0ce1fe8c63fca7a6c953233b94406d02" data-thumbnail="https://i.embed.ly/1/image?url=https://avatars2.githubusercontent.com/u/931655?s=400&v=4&key=a19fcc184b9711e1b4764040d3dc5c07" class="progressiveMedia-iframe js-progressiveMedia-iframe" allowfullscreen="" frameborder="0" src="https://medium.com/media/0ce1fe8c63fca7a6c953233b94406d02?postId=ed495077c36" style="display: block; position: absolute; margin: auto; max-width: 100%; box-sizing: border-box; transform: translateZ(0px); top: 0px; left: 0px; width: 700px; height: 100px;">UIApplicationDelegate에 정의된 메서드 시그니쳐와 다르기 때문에 컴파일러가 경고를 표시하지만 무시하셔도 됩니다.만약 새로운 버전의 앱을 릴리즈 할 계획을 가지고 계시다면 이 이슈를 꼭 확인하시길 바랍니다. 이 버그는 페이스북 로그인 등 다른 앱을 이용한 로그인이나, 카드 결제 후 주문서로 돌아오는 흐름에서 큰 문제를 일으킵니다. 이 글이 여러분들께 도움이 되길 바랍니다.Swift Korea 그룹에서 Xcode Release Notes에도 같은 내용이 있다는 것을 제보해주셨습니다. Swift Compiler 섹션의 Known Issues 4번째 항목입니다.#스타일쉐어 #개발팀 #개발자 #개발후기 #경험공유 #인사이트
조회수 2163

프로세스 마이닝과 AI를 통한 프로세스 혁신

지난해 이세돌과 알파고의 대결 이후에 인공 지능 (AI)과 기계 학습은 국내에서 많은 대중들의 관심을 얻어 중요한 추진력을 얻었으며, 모든 산업 분야의 기업들이 해당 기술을 빠른 속도로 계속 적용하여 사용하는 비중이 더욱 높아졌습니다. 실제로 Gartner는 2022년까지 스마트 머신과 로봇이 고학년 전문직 분야를 대체할 수 있을 것으로 내다봤으며, 심지어는 인공지능이 경영자 CEO도 대체 가능할 것인지에 대한 논의도 일어나고 있습니다. 이것은 사람이 과거 경험에 의해서 의사 결정을 내리 듯이 인공 지능도 확보한 데이터를 기반으로 의사 결정 모델을 만들 수 있다는 유사성에 기반합니다.  인공 지능에 의한 의사 결정은 사람한테 종종 있을 수 있는 감정이나 개인적 이해관계 및 관례에 의해 불합리한 판단에서 벗어나 데이터의 의한 객관적 판단을 할 수 있다는 장점이 있습니다.여기서 중요한 것은 인공지능이 학습하기 위한 “데이터”입니다.  지금까지 머신러닝이 막대한 이미지, 음성, 영상 데이터를 축적한 후 해당 데이터의 특징을 추출하여 패턴을 학습하여 자연어 처리 등을 통해 사람처럼 인식하여 분류하거나 상황을 판단하였듯이 기업 내 여러 가지 업무 활동에 머신 러닝을 적용하기 위해서는 이와 마찬가지로 관련 데이터가 필요합니다.제조 분야의 공정 관리, 공공 서비스, 물류 공급망 관리 등 전통적인 기업 내 업무 프로세스는 인공 지능에 의한 자동화과 효율화를 통해 혁신이 필요한 분야입니다. 기존에 외부 협력 업체로부터의 납기 예측, 소요되는 자재 인력 등 리소스 산정, 생산 스케줄, 장비 파라미터 입력값 등은 사람에 의해 수작업으로 진행 시 몇 주에서 수개월 소요되었지만, 인공 지능과 기계 학습 기반의 솔루션 도움으로 정확하게 지속적인 추세를 인식하고 인간의 개입 없이 데이터 중심의 결정이 가능해집니다.지금까지 기업 내 축적된 엄청난 양의 데이터를 활용하여 여러 산업 분야에서 숨겨진 패턴과 상관관계, 이상 징후 및 불량 탐지, 고객 수요 예측 등이 시도되었습니다. 하지만 이러한 시도들은 기업 내 문제 요인을 파악하여 우선적으로 어떤 부분에 초점을 맞추어 개선을 해야 하는지 알아야 하므로, 기업 경영 활동 전반에 걸쳐 돌아가는 판세를 읽는 노력이 필요합니다. 하지만, 기업 내에서 이뤄지고 있는 프로세스는 충분히 복잡하여, 개별 단위 작업의 전문가들은 존재하겠지만, 각 개별 부서, 구성원, 시스템 간에서 발생하는 다양한 상호작용과 이에 따른 예외 상황이 존재하여 이를 파악하기가 쉽지 않습니다.프로세스 마이닝은 데이터 기반의 프로세스 분석을 통해 문제 부분을 파악하여, 실제 인공 지능이나 머신 러닝을 적용하여 개선할 부분을 찾을 수 있도록 도와줍니다. 그리고, 프로세스 개선을 위해 머신러닝을 적용하기 위해서는 앞서 말한 것처럼 “데이터”가 학습될 수 있는 형태의 기반을 제공합니다.아래 그림과 같이 이벤트 로그를 기반으로 프로세스 모델을 생성하고, 수집된 패턴들과 각 분기 단계에서의 주요 성과 지표들을 디지털화하여 인공지능이 이해할 수 있는 형태로 축적합니다 이렇게 축적된 프로세스 패턴 데이터를 가지고 알파고가 최적화된 다음의 한 수를 예측하듯이 프로세스 마이닝은 인공 지능 기술과 결합하여 과거 프로세스에 대한 이해뿐만 아니라, 현재 시점에서 앞으로의 프로세스를 예측하여 합리적인 의사 결정을 도와줄 것입니다.#퍼즐데이터 #개발팀 #개발자 #개발후기 #인사이트
조회수 3617

워크로그 개발기

저는 야놀자 CX 서비스실의 API 파트에서 백엔드(90%)와 웹 프론트엔드(10%) 프로그래머로 일하는 송요창입니다.개정된 근로기준법에 따라 2018년 7월 1일부터 300인 이상 규모 기업인 경우주 40시간(최대 52시간) 근로합니다. 이에 따라 야놀자에서도 업무 집중도 향상과 함께 업무 시간을 명시하는 방안이 논의되었습니다. 이런 배경 속에서 만들어진워크로그개발 경험을 이야기하겠습니다.개인의 업무 시간 작성근로 시간이 기존 대비 단축되면서 각 개인의 업무 시간을 기록하고 기준 근로 시간을 초과하였을 때 이를 소진하도록 하는 방향이 결정되었지만 어떤 도구를 사용할지가 문제였습니다. Timing, TMetric, 출퇴근 기록기 알밤 등 다양한 도구를 사용해서 각자 기록을 시작했습니다.1차 시도 - Workflow + Alfred 활용그러던 중에 캘린더를 이용해서 출/퇴근 기록을 남기고 슬랙(Slack)으로 메시지를 발송하는 방법을 CX 서비스실 강미경 님이 공유합니다.캘린더와 - 유료인 경우 - 슬랙 모두에 기록이 남는 장점이 있습니다. 사용하기 쉽습니다.iOS 앱인 Workflow를 이용해서 캘린더에 이벤트를 등록하고 슬랙으로 메시지를 전송.데스크톱이나 노트북은 Alfred의 Workflows 기능으로 해결할 수 있었습니다.Workflow + Alfred로 워크로그를 기록하는 단점개인적으로 편리했지만 CX 서비스실 내부로 전파하여 사용하기에는 문제가 있었습니다.안드로이드 휴대전화를 사용하는 경우 Workflow를 사용할 수 없습니다.아이폰을 쓰더라도 유료로 판매되는 Workflow를 사지 않으면 쓸 수 없습니다.Alfred를 쓰더라도 Power Pack을 구매한 사용자만 Workflows를 적용할 수 있습니다.2차 시도 - 슬랙봇 활용위에서 언급된 문제를 해결하고 구성원 누구나 추가 앱 설치 없이 손쉽게 접근할 수 있는 슬랙봇에 주목합니다. 캘린더가 아니라 데이터베이스를 활용해서 개발하면 어떨지 논의했습니다.늦은 저녁(대략 23시부터 03시)에 Firebase 실시간 데이터베이스(Realtime Database)와 Firebase 클라우드 함수(Functions)를 활용해서 단순한 슬랙봇을 만들었습니다.슬랙을 실행한 뒤 슬래시 커맨드(slash command)로 /wl 출근을 입력하면 출근 로그가 추가되고 완료 메시지를 수신합니다.슬랙의 3초 이내 응답 요구단순한 기능이었지만 슬랙봇을 활용해서 워크로그를 작성하는 동료가 조금 늘었을 때 치명적인 문제가 발생했습니다.슬랙의 슬래시 커맨드는 3초 이내로 응답할 때 완료 메시지를 노출합니다. 3초를 초과하면 아래 메시지를 노출합니다.Firebase 클라우드 함수로 작성한 코드에 문제가 있었습니다. 단순한 로그 데이터와 사용자 요청에 대한 기록을 모두 완수한 후에 응답을 보내도록 했습니다. 이 부분에서 응답 지연이 발생합니다.기록은 된다고 변명해봤지만, 사용자가 기록 여부를 알 수 없으니 재시도하는 횟수가 늘어났습니다. 중복된 데이터를 삭제 요청하는 사용자가 늘었습니다. 이런 불편을 겪고 초기 사용자가 이탈했습니다.위 문제를 제외하고도 다수 사용자의 특정 기간 내 로그를 모두 살펴보기에 슬랙봇은 그다지 좋은 도구가 아니었습니다.제가 잘 못 쓴 것이지 슬랙봇에게는 죄가 없습니다.3차 시도 - 웹페이지 도입앞서 말한 문제가 대두하기 전 다수의 로그를 살펴보기 위해 웹페이지를 제작 중에 있었습니다. 프로그래밍에는 야놀자 앱 하이브리드에서 다뤄본 React.js 외에 최근 소개받은 razzle, After.js를 사용했습니다(이에 관한 회고는 아래서 짧게 다룹니다).Firebase 실시간데이터 베이스에 쌓인 로그를 Firebase 클라우드 함수로 제작된 API로 사용자별, 일자별로 불러서 표시하는 정도로 개발 착수.웹페이지로 조회 기능을 만든 시점과 맞물려 슬랙봇이 무용지물이 되었습니다. 로그인 기능을 제작하고 웹페이지에서 워크로그를 추가할 수 있도록 했습니다. 기록과 조회가 웹페이지로 대체 된 것입니다????????.Firebase 인증은 정말 편리합니다.대형 이벤트이렇게 만들었지만 떠나버린 사용자를 돌아오게 만드는 일은 불가능했습니다. 저를 제외하고 몇몇 분들만 사용하는 소소한 서비스로 사라질 예정이었습니다. 그런데 CX 서비스실 실장이신 하희진 님이 전격적으로 CX 서비스실 전 구성원이 워크로그를 통해 기록을 남겨달라고 요청하셨습니다. DAU가 10배는 급상승했습니다(1~2명에서 20명 이상으로). 많은 트래픽????이 들어오니 부족한 기능과 어설픈 기록 시스템 등이 문제가 되기 시작합니다.엎친 데 덮친 격으로 초과 근무 차감이란 주 기능 오픈에 대한 관리자(희진 님)와 사용자의 요구가 커졌습니다.할 일이 넘쳐난다.DAU 20의 공포요구사항을 분석하고 구현하면서 미비한 규칙을 관리자와 자주 논의했습니다. 논의 결과에 따라 메뉴가 생겼다가 사라졌다가를 반복해서 사용자의 혼란이 가중되었습니다. 아직 제작되지 않은 관리자 기능 때문에 데이터베이스를 직접 수정하는 일도 빈번했습니다.무엇보다 갑자기 새로운 도구를 사용하는 사용자의 질문이 쏟아졌습니다. 주 40시간을 어떻게 측정할지, 초과근무시간의 근거나 법정 휴식시간 발생 요건 등 대부분은 규칙에 관한 질문이었습니다. 30분 안에 같은 질문을 5번 듣고 동일하게 답변하는 헤프닝도 있었습니다.???? 어디서 많이 본 모습인데? 바로 IT산업 전체에서 자주 일어나는 일입니다.점진적 개선우선 비슷한 질문을 모아 FAQ 페이지를 개설했습니다(우리 PO가 자주 하는 업무라서 배운 풍월이 도움이 되었습니다). 지나치게 사용자 기능을 제한하여 CS가 늘어난 측면이 있어서 규칙이 확정된 부분만 사용자 기능 제한을 풀었습니다.금주 내의 로그는 언제든 추가 및 수정할 수 있도록 변경했습니다.누적된 초과시간은 금주 중 언제라도 사용할 수 있도록 변경했습니다.한 주가 끝나면 잘못된 로그가 있는지 검사한 뒤 로그 수정 후 초과시간 확정하는 일은 하고 있습니다.배포되는 버전마다 변경사항을 문서에 남기고 전체 사용자에게 공지했습니다.차감 기능은 자투리 시간과 CX 서비스실 구성원의 배려로 개발하였습니다.다행히 6월에 태어난 둘째가 새벽 4~5시면 한 번씩 울어서 알람 없이 기상할 수 있었습니다????.개인 회고워크로그를 제작하면서 크게 2가지를 느꼈습니다.미비한 요구사항 분석은 개발 비용을 상승시킨다하나의 요구사항은 여러 기능을 필요로 합니다. 자세한 분석 없이 뇌내 망상으로만 개발에 착수했더니 구조를 변경하느라 시간을 많이 소모했습니다.초과 시간을 예로 들면 우선 차감 메뉴를 만들고 있었습니다. 그런데 차감에 근거가 되는 누적 시간이 없습니다. 그럼 누적을 기록할 수 있는 모델을 제작합니다. 1일 8시간 기준으로 기록하도록 개발합니다. 주 40시간이 넘을 때 초과 시간이 발생하는 규칙이라서 1주일 단위로 마감하는 방식으로 변경합니다.이렇게 우왕좌왕하며 개발하니 밀고 나가는 힘이 약했습니다. 프로덕트 개발 시 PO가 이 부분을 많이 돌봐줘서 기본 없는 프로그래머가 되었습니다(????).개발은 50%. 운영이 나머지 50%다마이너 버전이라도 개발을 완료하고 배포할 때마다 한고비 넘었다고 생각했습니다. 그렇지만 진짜 서비스가 단단해지는 것은 사용자를 만날 때부터였습니다.사용자는 관리자보다 인내심이 없습니다. 개선 사항을 슬랙을 통해서 말해주고, 잘못된 기록이 있으면 수정을 요구했습니다. 이상한 규칙이 발견될 때마다 피드백이 왔습니다. 정당한 요구와 피드백이지만 1인 개발자가 감당하기는 벅찬 부분이 있었습니다.피드백을 정리해서 수정할 부분을 JIRA에 정리하고 작업하기를 반복했습니다. 이 과정을 통해 초기보다 더 다듬을 수 있었습니다.저는 근무시간 중에만 CS 대응을 했음에도 피곤했습니다. 이런 일을 매일 매시간 겪고 있는 야놀자 PO와 IT 업계 동료들은 정말 대단한 사람입니다. 이 자리를 빌려 다시 한번 존경합니다.개발 관련 회고(신약???? 임상 결과)토이 프로젝트이기 때문에 회사에서 사용하는 기술 외에 새로운 기술을 다뤄봤습니다. React.js와 함께 엄청나게 사랑받고 있는 vue.js가 아닌 이유는 개발 시간이 촉박해서 공부할 시간이 없었다고 핑계 대봅니다.razzle + After.js = ????React.js를 사용할 때 주로 Next.js를 사용해왔지만 이번에는 razzle과 After.js를 사용했습니다.razzle은 create-react-app처럼 React.js 애플리케이션을 제작할 수 있도록 초기 구성을 도와줍니다. React.js 외에도 Vue, Angular, Preact, Elm 등을 지원합니다.After.js는 Next.js처럼 서버사이드 렌더링을 지원합니다. Next.js와 다르게 React Route 4를 이용해서 라우팅을 지원합니다.사용해본 소감은 razzle이 아무런 설정도 하지 않도록 도와주고 있어서 편리했습니다. TypeScript 도입도 예시가 있어서 쉽게 적용할 수 있었습니다. 코드 수정 후 웹페이지를 다시 로딩하는 핫 리로드(hot reload)도 잘 작동합니다. After.js는 서버사이드 렌더링 시 getInitialProps 를 사용할 수 있어서 Next.js에 익숙한 저에게 편리했습니다. 무엇보다 Next.js처럼 route를 변경하기 위해서 next-route에 의존하지 않아서 편리했습니다(대신 React Route를 의존합니다).저처럼 프로젝트 셋업을 어려워하는 초심자에게 유용합니다(검색할 때 사례를 더 많이 찾으려면 Next.js가 더 유리합니다).배포는 초기에 Aws의 beanstalk을 활용하다가 Zeit가 운영하는 now로 변경했습니다. Node.js나 docker에 익숙하고 커맨드 라인 인터페이스(cli)를 사용하는 데 어려움이 없다면 사용할만 합니다. 리전이 모두 해외라서 응답속도가 빠르진 않습니다.Zeit는 Next.js 프레임워크를 제작한 회사입니다.도움 주신 분???? 아이디어와 기획에 도움을 주고 사용자가 돼주신 R&D CX 서비스실 강미경 님???? 제보에 적극적인 R&D CX 서비스실 노현석 님DAU를 비약적으로 높여주신 R&D CX 서비스실 하희진 님미약한 사용성과 구린 UI임에도 잘 사용해주고 계신 R&D CX 서비스실 모든 구성원!!공감의 ????????! 눈물 흘리는 역할로 열연해주신 R&D UX/UI팀 김하연 님이 글을 리뷰해주신 유관종 님, 노현석 님, 구본한 님무엇보다 이런 프로젝트가 가능하도록 도와준 R&D CX 서비스실 내 API파트 전원에게 ????‍ 감사합니다.참고한 자료https://medium.com/evenbit/building-a-slack-app-with-firebase-as-a-backend-151c1c98641dhttps://api.slack.com/slash-commandshttps://firebase.google.com/docs/database/web/start#야놀자 #개발자 #개발팀 #문제해결 #버그수정 #백엔드 #인사이트 #경험공유
조회수 1371

하나부터 열까지 모두 알려주겠다! Scatter 계정 만들기 (feat. HexBP 연동하기)

스캐터(Scatter)는 암호화폐 지갑 계정에 대한 신원인증을 대행해주는 일종의 신원인증 프로그램으로, 별도의 보팅포털에 접속해서 신원인증을 통해 로그인을 도와주는 크롬의 확장 프로그램입니다.스캐터를 사용하게 되면, 기존에 여러 지갑 및 사이트로 부터 EOS 프라이빗 키를 부여 받아야 했던 번거로움 없이, 한번만 등록해놓으면 다양한 사이트에서 스캐터 계정 하나로 자신의 EOS 계정을 증명할 수 있게 됩니다.이러한 스캐터를 사용하는 방법을 지금 부터 알아보겠습니다.Step 1. Scatter 설치 및 계정 생성Scatter에서 크롬 확장 프로그램을 다운로드하여 설치하셔야 합니다.설치 후 크롬 브라우저에 설치된 Scatter 아이콘을 누르시면 다음과 같은 화면이 나타납니다.새로운 비밀번호 (최소 8글자)를 입력하시면 됩니다.비밀번호 입력 후 Create New Scatter 버튼을 누르세요.그럼 아래와 같이 12단어가 표시된 화면이 나타납니다. 바로 단어들을 복사 혹은 화면 캡처를 하여 보관해야 합니다.( * 저장하지 않은 채 다른 창을 누르시게되면 해당 화면이 사라지게 되니 꼭 바로 저장하셔야 합니다.)이 단어들은 나중에 비밀번호를 잃어버렸을 때 필요합니다.다 복사를 하셨으면 [ I wrote it down]을 눌러주세요.그 다음 화면에서 백업을 하실 지, 그냥 넘기실 지 선택하셔야 합니다.선택하시면 다음화면으로 넘어가게 됩니다.이제 더 편리하게 사용할 수 있도록 한국어 설정으로 바꿔 볼 거에요!우측 상단의 톱니바퀴 모양을 누르신 후[Language]-한국어 선택 -[Change Language] 차근차근 클릭하여진행하시면 됩니다.짜잔! 이제 한국어 버전으로 사용할 수 있습니다.이제부터 Scatter를 통해 자신의 EOS 프라이빗 키를 등록하셔야 합니다.왼쪽 상단의 [ < ]뒤로가기 버튼을 누르시면 다음과 같은 화면이 나옵니다.여기서 두번째 줄에 보이는 키 쌍(Key pairs)을 선택합니다.우측 상단의 [신규 생성]버튼을 클릭 하셔서 계정을 생성 하셔야 합니다.버튼을 누르시면 아래와 같은 화면을 확인하실 수 있습니다.이는 ‘현재 등록이 되어 있지 않다’는 것을 의미합니다.프라이빗키 항목에 자신의 EOS 프라이빗키를 넣고이름은 영어나 숫자를 이용하여 자유롭게 이름을 정하시면 됩니다.*프라이빗 키를 입력하면 퍼블릭 키는 자동으로 입력됩니다.*반드시 키 쌍 생성 버튼이 아닌 저장 버튼을 누르셔야 합니다.정상적으로 등록이 완료되면 다음과 같은 화면을 확인 하실 수 있습니다.다들 잘 따라오셨나요?만약 이 절차를 진행하셨음에도 등록이 안되었다면 계정이 EOS에 등록이 되지 않은 경우입니다.Step2 : Scatter 설정하기이제 등록된 Scatter 계정을 통해 HEX BP 사이트의 투표 시스템과 연동하는 방법을 알아보겠습니다.Scatter의 첫 화면으로 돌아가서 [톱니바퀴]를 선택합니다.해당 버튼을 누르시면 다음과 같은 화면이 나타납니다.[네트워크]를 선택합니다.해당 버튼을 누르시면 아래와 비슷한 화면이 나타납니다.이제 다시 우측 상단의 [신규 생성] 버튼을 누릅니다.해당 버튼을 누르면 네트워크 정보를 입력해야 하는 화면이 나옵니다.* 이름 : eosnet.hexlant.Io* https 선택* 도메인 혹은 IP 주소 : 목록 중에 선택* 포트 : 80* 체인 ID : aca376f206b8fc25a6ed44dbdc66547c36c6c33e3a119ffbeaef943642f0e906복사하여 붙여넣기모두 정확하게 입력 하셨으면 저장 버튼을 눌러주시기 바랍니다.이제 등록한 Chain을 계정에 연결해야 합니다![신원인증 ID]를 눌러주세요그 다음 [신규 생성]을 클릭 합니다.EOS Mainnet을 설정 한 후에 자신의 계정을 선택합니다.모두 선택하셨으면 [가져오기]를 누릅니다.[가져오기] 버튼을 누르신 후 잠시 기다리시면다음과 같은 화면이 나타납니다.이때 acticve 권한을 클릭 후 [선택한 계정 사용] 버튼을누르시기 바랍니다.*아래 개인 정보 입력하는 부분은 옵션이기 때문에 굳이 입력하지 않으셔도괜찮습니다. 모두 입력 하셨으면 [저장] 버튼을 눌러주시기 바랍니다.이제 스캐터 새 계정이 성공적으로 만들어졌습니다. 짝짝짝Step 3 : 투표하기[Login] 눌러서 Scatter 로그인 하기2. [신원인증 ID 선택]-[수락] 클릭하기3. Log out으로 바뀐 화면을 확인하실 수 있습니다.4. 투표를 하기 위해선 [Vote] 버튼을 누르셔야 합니다.5. Vote 버튼을 누르시면 체크박스가 생성됩니다! 이제 21명의 BP가 되길 원하는 후보자를 선택하시면 됩니다.누르시면 아래부분에 선택한 BP 후보자들을 확인하실 수 있습니다.후보자를 다 선택하셨다면 [Done] 버튼을 눌러 투표를마무리 해주시면 됩니다!6. [Done] 을 누르시면 마지막으로 Scatter 화면이 뜹니다. 여기서 [Accept] 버튼을 누르시면 됩니다.자 이제 투표도 모두 완료되었습니다!#헥슬란트 #HEXLANT #블록체인 #개발자 #개발팀 #기술기업 #기술중심 #Scatter
조회수 1902

Tips for building fast portrait segmentation network with TensorFlow Lite

PrefaceDeep learning has led to a series of breakthroughs in many areas. However, successful deep learning models often require significant amounts of computational resources, memory and power. Deploying efficient deep learning models on mobile devices became the main technological challenge for many mobile tech companies.Hyperconnect developed a mobile app named Azar which has a large fan base all over the world. Recently, Machine Learning Team has been focusing on developing mobile deep learning technologies which can boost user experience in Azar app. Below, you can see a demo video of our image segmentation technology (HyperCut) running on Samsung Galaxy J7. Our benchmark target is a real-time (>= 30 fps) inference on Galaxy J7 (Exynos 7580 CPU, 1.5 GHz) using only a single core.Figure 1. Our network runs fast on mobile devices, achieving 6 ms of single inference on Pixel 1 and 28 ms on Samsung Galaxy J7. Full length video. There are several approaches to achieve fast inference speed on mobile device. 8-bit quantization is one of the popular approaches that meet our speed-accuracy requirement. We use TensorFlow Lite as our main framework for mobile inference. TensorFlow Lite supports SIMD optimized operations for 8-bit quantized weights and activations. However, TensorFlow Lite is still in pre-alpha (developer preview) stage and lacks many features. In order to achive our goal, we had to do the following:Understand details of TensorFlow and Tensorflow Lite implementation.Design our own neural network that can fully utilize optimized kernels of TensorFlow Lite. (Refer to 1, 2 and 3)Modify TOCO: TensorFlow Lite Optimizing Converter to correctly convert unsupported layers. (Refer to 4)Accelerate several quantized-layers with SIMD optimized code. (Refer to 5 and 6)We are willing to share our trials and errors in this post and we hope that readers will enjoy mobile deep learning world :)1) Why is depthwise separable layer fast in Tensorflow Lite ?Implementing low-level programs requires a bit different ideas and approaches than usual. We should be aware that especially on mobile devices using cache memory is important for fast inference.Figure 2. Memory access requires much more energy (640 pJ) than addition or multiplication.Accessing cache memory (8 pJ) is much cheaper than using the main memory (table courtesy of Ardavan Pedram) In order to get insight into how intrinsics instructions are implemented in Tensorflow Lite, we had to analyze some implementations including depthwise separable convolution with 3x3 kernelsBelow we describe some of the main optimization techniques that are used for building lightweight and faster programs.Loop UnrollingCan you spot the difference between the following two code fragments?for (int i = 0; i < 32; i++) { x[i] = 1; if (i%4 == 3) x[i] = 3; } for (int i = 0; i < 8; i++) { x[4*i ] = 1; x[4*i+1] = 1; x[4*i+2] = 1; x[4*i+3] = 3; } The former way is what we usually write, and the latter is loop-unrolled version of former one. Even though unrolling loops are discouraged from the perspective of software design and development due to severe redundancy, with low-level architecture this kind of unrolling has non-negligible benefits. In the example described above, the unrolled version avoids examining 24 conditional statements in for loop, along with neglecting 32 conditional statements of if.Furthermore, with careful implementation, these advantages can be magnified with the aid of SIMD architecture. Nowadays some compilers have options which automatically unroll some repetitive statements, yet they are unable to deal with complex loops.Separate implementation for each caseConvolution layer can take several parameters. For example, in depthwise separable layer, we can have many combinations with different parameters (depth_multiplier x stride x rate x kernel_size). Rather than writing single program available to deal with every case, in low-level architectures, writing number of case-specific implementations is preferred. The main rationale is that we need to fully utilize the special properties for each case. For convolution operation, naive implementation with several for loops can deal with arbitrary kernel size and strides, however this kind of implementation might be slow. Instead, one can concentrate on small set of actually used cases (e.g. 1x1 convolution with stride 1, 3x3 convolution with stride 2 and others) and fully consider the structure of every subproblem.For example, in TensorFlow Lite there is a kernel-optimized implementation of depthwise convolution, targeted at 3x3 kernel size:template <int kFixedOutputY, int kFixedOutputX, int kFixedStrideWidth, int kFixedStrideHeight> struct ConvKernel3x3FilterDepth8 {}; Tensorflow Lite further specifies following 16 cases with different strides, width and height of outputs for its internal implementation:template <> struct ConvKernel3x3FilterDepth8<8, 8, 1, 1> { ... } template <> struct ConvKernel3x3FilterDepth8<4, 4, 1, 1> { ... } template <> struct ConvKernel3x3FilterDepth8<4, 2, 1, 1> { ... } template <> struct ConvKernel3x3FilterDepth8<4, 1, 1, 1> { ... } template <> struct ConvKernel3x3FilterDepth8<2, 2, 1, 1> { ... } template <> struct ConvKernel3x3FilterDepth8<2, 4, 1, 1> { ... } template <> struct ConvKernel3x3FilterDepth8<1, 4, 1, 1> { ... } template <> struct ConvKernel3x3FilterDepth8<2, 1, 1, 1> { ... } template <> struct ConvKernel3x3FilterDepth8<4, 2, 2, 2> { ... } template <> struct ConvKernel3x3FilterDepth8<4, 4, 2, 2> { ... } template <> struct ConvKernel3x3FilterDepth8<4, 1, 2, 2> { ... } template <> struct ConvKernel3x3FilterDepth8<2, 2, 2, 2> { ... } template <> struct ConvKernel3x3FilterDepth8<2, 4, 2, 2> { ... } template <> struct ConvKernel3x3FilterDepth8<2, 1, 2, 2> { ... } template <> struct ConvKernel3x3FilterDepth8<1, 2, 2, 2> { ... } template <> struct ConvKernel3x3FilterDepth8<1, 4, 2, 2> { ... } Smart Memory Access PatternSince low-level programs are executed many times in repetitive fashion, minimizing duplicated memory access for both input and output is necessary. If we use SIMD architecture, we can load nearby elements together at once (Data Parallelism) and in order to reduce duplicated read memory access, we can traverse the input array by means of a snake-path.Figure 3. Memory access pattern for 8x8 output and unit stride, implemented in Tensorflow Lite's depthwise 3x3 convolution. The next example which is targeted to be used in much smaller 4x1 output block also demonstrates how to reuse preloaded variables efficiently. Note that the stored location does not change for variables which are loaded in previous stage (in the following figure, bold variables are reused):Figure 4. Memory access pattern for 4x1 output and stride 2, implemented in Tensorflow Lite's depthwise 3x3 convolution. 2) Be aware of using atrous depthwise convolutionAtrous (dilated) convolution is known to be useful to achieve better result for image segmentation[1]. We also decided to use atrous depthwise convolution in our network. One day, we tried to set stride for atrous depthwise convolution to make it accelerate computation, however we failed, because the layer usage in TensorFlow (≤ 1.8) is constrained.In Tensorflow documentation of tf.nn.depthwise_conv2d (slim.depthwise_conv2d also wraps this function), you can find this explanation of rate parameter.rate: 1-D of size 2. The dilation rate in which we sample input values across the height and width dimensions in atrous convolution. If it is greater than 1, then all values of strides must be 1.Even though TensorFlow doesn’t support strided atrous function, it doesn’t raise any error if you set rate > 1 and stride > 1. <!-- The output of layer doesn't seem to be wrong. -->>>> import tensorflow as tf >>> tf.enable_eager_execution() >>> input_tensor = tf.constant(list(range(64)), shape=[1, 8, 8, 1], dtype=tf.float32) >>> filter_tensor = tf.constant(list(range(1, 10)), shape=[3, 3, 1, 1], dtype=tf.float32) >>> print(tf.nn.depthwise_conv2d(input_tensor, filter_tensor, strides=[1, 2, 2, 1], padding="VALID", rate=[2, 2])) tf.Tensor( [[[[ 302.] [ 330.] [ 548.] [ 587.]] [[ 526.] [ 554.] [ 860.] [ 899.]] [[1284.] [1317.] [1920.] [1965.]] [[1548.] [1581.] [2280.] [2325.]]]], shape=(1, 4, 4, 1), dtype=float32) >>> 0 * 5 + 2 * 6 + 16 * 8 + 9 * 18 # The value in (0, 0) is correct 302 >>> 0 * 4 + 2 * 5 + 4 * 6 + 16 * 7 + 18 * 8 + 20 * 9 # But, the value in (0, 1) is wrong! 470 Let’s find the reason why this difference happened. If we just put tf.space_to_batch and tf.batch_to_space before and after convolution layer, then we can use convolution operation for atrous convolution (profit!). On the other hand, it isn’t straightforward how to handle stride and dilation together. In TensorFlow, we need to be aware of this problem in depthwise convolution.3) Architecture design principles for efficient segmentation networkUsually segmentation takes more time than classification since it has to upsample high spatial resolution map. Therefore, it is important to reduce inference time as much as possible to make the application run in real-time.It is important to focus on spatial resolution when designing real-time application. One of the easiest ways is to reduce the size of input images without losing accuracy. Assuming that the network is fully convolutional, you can accelerate the model about four times faster if the size of input is halved. In literature[2], it is known that small size of input images doesn’t hurt accuracy a lot.Another simple strategy to adopt is early downsampling when stacking convolution layers. Even with the same number of convolution layers, you can reduce the time with strided convolution or pooling within early layers.There is also a tip for selecting the size of input image when you use Tensorflow Lite quantized model. The optimized implementations of convolution run best when the width and height of image is multiple of 8. Tensorflow Lite first loads multiples of 8, then multiples of 4, 2 and 1 respectively. Therefore, it is the best to keep the size of every input of layer as a multiple of 8.Substituting multiple operations into single operation can improve speed a bit. For example, convolution followed by max pooling can be usually replaced by strided convolution. Transpose convolution can also be replaced by resizing followed by convolution. In general, these operations are substitutable because they perform the same role in the network. There are no big empirical differences among these operations. <!-- substitutable -->Tips described above help to accelerate inference speed but they can also hurt accuracy. Therefore, we adopted some state-of-the-art blocks rather than using naive convolution blocks.Figure 5.  Atrous spatial pyramid pooling (figure courtesy of Liang-Chieh Chen) Atrous spatial pyramid pooling[1] is a block which mimics the pyramid pooling operation with atrous convolution. DeepLab uses this block in the last layer.We also substitute most of the convolution layers with efficient depthwise separable convolution layers. They are basic building blocks for MobileNetV1[3] and MobileNetV2[4] which are well optimized in Tensorflow Lite.4) Folding batchnorm into atrous depthwise convolutionWhen quantizing convolution operation followed by batchnorm, batchnorm layer must be folded into the convolution layers to reduce computation cost. After folding, the batchnorm is reduced to folded weights and folded biases and the batchnorm-folded convolution will be computed in one convolution layer in TensorFlow Lite[5]. Batchnorm gets automatically folded using tf.contrib.quantize if the batchnorm layer comes right after the convolution layer. However, folding batchnorm into atrous depthwise convolution is not easy.In TensorFlow’s slim.separable_convolution2d, atrous depthwise convolution is implemented by adding SpaceToBatchND and BatchToSpaceND operations to normal depthwise convolution as mentioned previously. If you add a batchnorm to this operation by including argument normalizer_fn=slim.batch_norm, batchnorm does not get attached directly to the convolution layer. Instead, the graph will look like the diagram below: SpaceToBatchND → DepthwiseConv2dNative → BatchToSpaceND → BatchNorm The first thing we tried was to modify TensorFlow quantization to fold batchnorm bypassing BatchToSpaceND without changing the order of operations. With this approach, the folded bias term remained after BatchToSpaceND, away from the convolution layer. Then, it became separate BroadcastAdd operation in TensorFlow Lite model rather than fused into convolution. Surprisingly, it turned out that BroadcastAdd was much slower than the corresponding convolution operation in our experiment:Timing log from the experiment on Galaxy S8 ... [DepthwiseConv] elapsed time: 34us [BroadcastAdd] elapsed time: 107us ... To remove BroadcastAdd layer, we decided to change the network itself instead of fixing TensorFlow quantization. Within slim.separable_convolution2d layer, we swapped positions of BatchNorm and BatchToSpaceND. SpaceToBatchND → DepthwiseConv2dNative → BatchNorm → BatchToSpaceND Even though batchnorm relocation could lead to different outputs values compared to the original, we did not notice any degradation in segmentation quality.5) SIMD-optimized implementation for quantized resize bilinear layerAt the time of accelerating TensorFlow Lite framework, conv2d_transpose layer was not supported. However, we could use ResizeBilinear layer for upsampling as well. The only problem of this layer is that there is no quantized implementation, therefore we implemented our own SIMD quantized version of 2x2 upsampling ResizeBilinear layer.Figure 6. 2x2 bilinear upsampling without corner alignnment. To upsample image, original image pixels (red circles) are interlayed with new interpolated image pixels (grey circles). In order to simplify implementation we do not compute pixel values for the bottommost and rightmost pixels, denoted as green circles.for (int b = 0; b < batches; b++) { for (int y0 = 0, y = 0; y <= output_height - 2; y += 2, y0++) { for (int x0 = 0, x = 0; x <= output_width - 2; x += 2, x0++) { int32 x1 = std::min(x0 + 1, input_width - 1); int32 y1 = std::min(y0 + 1, input_height - 1); ResizeBilinearKernel2x2(x0, x1, y0, y1, x, y, depth, b, input_data, input_dims, output_data, output_dims); } } } Every new pixel value is computed for each batch separately. Our core function ResizeBilinearKernel2x2 computes 4 pixel values across multiple channels at once.Figure 7. Example of 2x2 bilinear upsampling of top left corner of image. (a) Original pixel values are simply reused and (b) – (d) used to interpolate new pixel values. Red circles represent original pixel values. Blue circles are new interpolated pixel values computed from pixel values denoted as circles with black circumference. NEON (Advanced SIMD) intrinsics enable us to process multiple data at once with a single instruction, in other words processing multiple data at once. Since we deal with uint8 input values we can store our data in one of the following formats uint8x16_t, uint8x8_t and uint8_t, that can hold 16, 8 and 1 uint8 values respectively. This representation allows to interpolate pixel values across multiple channels at once. Network architecture is highly rewarded when channels of feature maps are multiples of 16 or 8:// Handle 16 input channels at once int step = 16; for (int ic16 = ic; ic16 <= depth - step; ic16 += step) { ... ic += step; } // Handle 8 input channels at a once step = 8; for (int ic8 = ic; ic8 <= depth - step; ic8 += step) { ... ic += step; } // Handle one input channel at once for (int ic1 = ic; ic1 < depth; ic1++) { ... } SIMD implementation of quantized bilinear upsampling is straightforward. Top left pixel value is reused (Fig. 7a). Bottom left (Fig. 7b) and top right (Fig. 7c) pixel values are mean of two adjacent original pixel values. Finally, botom right pixel (Fig. 7d) is mean of 4 diagonally adjacent original pixel values.The only issue that we have to take care of is 8-bit integer overflow. Without a solid knowledge of NEON intrinsics we could go down the rabbit hole of taking care of overflowing by ourself. Fortunately, the range of NEON intrinsics is broad and we can utilize those intrinsics that fit our needs. The snippet of code below (using vrhaddq_u8) shows an interpolation (Fig. 7d) of 16 pixel values at once for bottom right pixel value:// Bottom right output_ptr += output_x_offset; uint8x16_t left_interpolation = vrhaddq_u8(x0y0, x0y1); uint8x16_t right_interpolation = vrhaddq_u8(x1y0, x1y1); uint8x16_t bottom_right_interpolation = vrhaddq_u8(left_interpolation, right_interpolation); vst1q_u8(output_ptr, bottom_right_interpolation); 6) Pitfalls in softmax layer and demo codeThe first impression of inference in TensorFlow Lite was very slow. It took 85 ms in Galaxy J7 at that time. We tested the first prototype of TensorFlow Lite demo app by just changing the output size from 1,001 to 51,200 (= 160x160x2)After profiling, we found out that there were two unbelievable bottlenecks in implementation. Out of 85 ms of inference time, tensors[idx].copyTo(outputs.get(idx)); line in Tensor.java took up to 11 ms (13 %) and softmax layer 23 ms (27 %). If we would be able to accelerate those operations, we could reduce almost 40 % of the total inference time!First, we looked at the demo code and identified tensors[idx].copyTo(outputs.get(idx)); as a source of problem. It seemed that the slowdown was caused by copyTo operation, but to our surprise it came from int[] dstShape = NativeInterpreterWrapper.shapeOf(dst); because it checks every element (in our case, 51,200) of array to fill the shape. After fixing the output size, we gained 13 % speedup in inference time.<T> T copyTo(T dst) { ... // This is just example, of course, hardcoding output shape here is a bad practice // In our actual app, we build our own JNI interface with just using c++ code // int[] dstShape = NativeInterpreterWrapper.shapeOf(dst); int[] dstShape = {1, width*height*channel}; ... } The softmax layer was our next problem. TensorFlow Lite’s optimized softmax implementation assumes that depth (= channel) is bigger than outer_size (= height x width). In classification task, the usual output looks like [1, 1(height), 1(width), 1001(depth)], but in our segmentation task, depth is 2 and outer_size is multiple of height and width (outer_size » depth). Implementation of softmax layer in Tensorflow Lite is optimized for classification task and therefore loops over depth instead of outer_size. This leads to unacceptably slow inference time of softmax layer when used in segmentation network.We can solve this problem in many different ways. First, we can just use sigmoid layer instead of softmax in 2-class portrait segmentation. TensorFlow Lite has very well optimized sigmoid layer.Secondly, we could write SIMD optimized code and loop over depth instead of outer_size. You can see similar implementation at Tencent’s ncnn softmax layer, however, this approach has still its shortcomings. Unlike ncnn, TensorFlow Lite uses NHWC as a default tensor format:Figure 8. NHWC vs NCHW In other words, for NHWC, near elements of tensor hold channel-wise information and not spatial-wise. It is not simple to write optimized code for any channel size, unless you include transpose operation before and after softmax layer. In our case, we tried to implement softmax layer assumming 2-channel output.Thirdly, we can implement softmax layer using pre-calculated lookup table. Because we use 8-bit quantization and 2-class output (foreground and background) there are only 65,536 (= 256x256) different combinations of quantized input values that can be stored in lookup table:for (int fg = 0; fg < 256; fg++) { for (int bg = 0; bg < 256; bg++) { // Dequantize float fg_real = input->params.scale * (fg - input->params.zero_point); float bg_real = input->params.scale * (bg - input->params.zero_point); // Pre-calculating Softmax Values ... // Quantize precalculated_softmax[x][y] = static_cast<uint8_t>(clamped); } } ConclusionIn this post, we described the main challenges we had to solve in order to run portrait segmentation network on mobile devices. Our main focus was to keep high segmentation accuracy while being able to support even old devices, such as Samsung Galaxy J7. We wish our tips and tricks can give a better understanding of how to overcome common challenges when designing neural networks and inference engines for mobile devices.At the top of this post you can see portrait segmentation effect that is now available in Azar app. If you have any questions or want to discuss anything related to segmentation task, contact us at [email protected]. Enjoy Azar and mobile deep learning world!References[1] L. Chen, G. Papandreou, F. Schroff, H. Adam. Rethinking Atrous Convolution for Semantic Image Segmentation. June 17, 2017, https://arxiv.org/abs/1706.05587[2] C. Szegedy, V. Vanhoucke, S. Ioffe, J. Shlens, Z. Wojna. Rethinking the Inception Architecture for Computer Vision. December 11, 2015, https://arxiv.org/abs/1512.00567[3] A. Howard, M. Zhu, B. Chen, D. Kalenichenko, W. Wang, T. Weyand, M. Andreetto, H. Adam. MobileNets: Efficient Convolutional Neural Networks for Mobile Vision Applications, April 17, 2017, https://arxiv.org/abs/1704.04861[4] M. Sandler, A. Howard, M. Zhu, A. Zhmoginov, L. Chen. MobileNetV2: Inverted Residuals and Linear Bottlenecks. January 18, 2018, https://arxiv.org/abs/1801.04381[5] B. Jacob, S. Kligys, B. Chen, M. Zhu, M. Tang, A. Howard, H. Adam, D. Kalenichenko. Quantization and Training of Neural Networks for Efficient Integer-Arithmetic-Only Inference. December 15, 2017, https://arxiv.org/abs/1712.05877
조회수 7005

BPM, RPA 그리고 Process Mining(프로세스마이닝)

새로운 IT 기술의 등장과 기업 환경의 변화로 새로운 과학 경영 기법들이 비즈니스 유행어처럼 등장하고 사라지지만 그 가운데서도 변하지 않는 것이 있다면 프로세스 개선과 관련된 대한 끊임없는 노력과 관심일 것입니다.프로세스 마이닝은 이벤트 로그를 기반으로 비즈니스 프로세스를 분석할 수 있는 프로세스 관리 기술입니다. 정보 시스템에서 기록한 이벤트 로그에 포함된 패턴 및 세부 정보를 식별하기 위해 별도의 분석 알고리즘이 이벤트 로그 데이터에 적용됩니다. 프로세스 마이닝은 프로세스의 효율성과 이해를 향상시키는 것을 목표로 하며, “자동화된 비즈니스 프로세스 발견” ABPD (Automated Business Process Discovery)이라는 좀 더 일반화된 명칭으로 불리기도 합니다.이러한 프로세스 마이닝은 어디서 갑자기 나온 개념은 아니고 기존 비즈니스 프로세스 관리 기법에 대한 연구와 데이터 분석 기술이 합쳐져서 나온 산물이기에 “비즈니스 프로세스”와 관련된 기술들을 살펴보고 프로세스 마이닝과의 연관성을 찾아보고자 합니다.BPM (Business Process Management)프로세스 마이닝은 일반적으로 BPM과 데이터 마이닝이 겹치는 중간 영역에 위치합니다.BPM은 비즈니스 프로세스를 발견, 모델링, 분석, 측정, 개선, 최적화 및 자동화하기 위해 다양한 방법을 사용하는 운영 관리 기법을 의미하며, 프로세스를 관리하여 기업 성과를 향상시키는 데 중점을 둡니다. 좁은 의미에서 BPM은 업무 프로세스를 사전에 모델링하고, 설계된 프로세스 대로 업무 결제, 승인, 구매 등의 업무 등이 자동화되어 흘러갈 수 있도록 도와주는 IT 시스템을 지칭하기도 합니다..BPM은 Top-Down 방식으로 프로세스 모델을 그려서, 해당 프로세스 모델 대로 업무를 수행하도록 강제하는 방식이라면 프로세스 마이닝은 이미 수행된 업무로부터 프로세스 모델을 도출하는 Bottom-up 방식을 따릅니다.  하지만 점점 복잡해져 가는 기업 업무 활동을 BPM처럼 중앙 집권적 방식으로 모든 것을 통제하기에는 한계가 있습니다.  BPM의 통제를 벗어난 다양한 여러 시스템을 업무 관점에서 통합적으로 관리하고 모니터링하기 위해서는 개별 시스템은 그대로 두고 이로부터 쏟아져 나오는 로그를 통해 프로세스를 관리하는 분권적 방식이 BPM의 한계를 보완하는 역할을 합니다. RPA (Robot Process Automation)로봇 프로세스 자동화 (RPA)는 소프트웨어 로봇 또는 AI (인공 지능) 작업자의 개념을 기반으로 하는 사무 자동화 기술의 새로운 형태입니다.소프트웨어 '로봇'은 컴퓨터 시스템의 사용자 인터페이스와 상호 작용하는 인간의 행동을 복제하는 소프트웨어 응용 프로그램입니다. 예를 들어, ERP 시스템에 데이터 입력을 실행하거나 실제로 비즈니스 프로세스를 수행하는 것이 소프트웨어 로봇의 일반적인 활동이 될 것입니다. 소프트웨어 로봇은 사람과 동일한 방식으로 사용자 인터페이스(UI)에서 작동합니다. 이것은 기존에 애플리케이션 프로그래밍 인터페이스(API)에 기반한 전통적 형태의 IT 통합과 크게 다릅니다. 즉, 사용자 인터페이스의 데이터 아키텍처 계층을 기반으로 한 기계 간(machine-to-machine) 통신 형태를 취합니다.앞서 언급한 BPM이 프로세스 개선을 위해 프로세스 자체를 재설계하고 변경하려는 방식이라면 RPA는 사람이 하던 현재 방식을 그대로 모방하여 소프트웨어로 대체하여 자동화하는 방식입니다. 이러한 RPA가 업무에 더 많이 적용될 수록 더 많은 시스템 로그가 나올 것이고 이에 대한 성과 분석과 모니터링이 필요해질 것입니다. 프로세스 마이닝은 RPA 도입 전 초기 단계에 전체 프로세스를 분석하여 RPA가 적용될 만한 구간을 식별하여 타당성을 검증하고, RPA 도입 이후의 전후 비교를 통해 지속적으로 업무 효율성을 측정할 수 있는 방법을 제공합니다.앞서 살펴본 것처럼 BPM, RPA, Process Mining 이 세 가지는 서로의 영역을 다투는 것이 아니라 상호 보완적인 존재로 볼 수 있습니다.프로세스 마이닝은 “프로세스”와 관련된 다양한 시스템과 활동들에 대해서 데이터에 근거한 현재 프로세스의 모델링 및 성과 측정 방법을 제공합니다. 이를 통해 과거 혹은 신규 프로세스 혁신 기법들과 맞물려 해당 시스템 및 방법론이 성공적으로 수행될 수 있도록 자동화된 “업무 조언자” 역할을 수행하게 됩니다. [참고 자료]https://en.wikipedia.org/wiki/Business_process_managementhttps://en.wikipedia.org/wiki/Robotic_process_automationhttps://www.minit.io/blog/robotic-process-automation-and-process-mininghttps://medium.com/towards-data-science/unleash-the-value-of-process-mining-4e3b5af4e9d8http://www.cdevworkflow.com/bpm-life-cycle/https://www.uipath.com/blog/the-robotic-process-automation-infographic#퍼즐데이터 #개발팀 #개발자 #개발후기 #인사이트

기업문화 엿볼 때, 더팀스

로그인

/