Categories
Offsites

Personal Assistant Kino Part 1 – Overview

Kino 프로젝트는 QS를 통해서 자신에 대해서 알고, 불필요한 일들을 자동화시키고 삶의 질을 증진시키기 위한 프로젝트 입니다.

images

출처 : http://quantifiedself.com/

지금까지의 시리즈

Github: https://github.com/DongjunLee/quantified-self

Introduction

최근에 Bot에 대한 글도 많이 읽고.. 조그만 미니 프로젝트로 Bot도 개발해보면서, 그 동안 마음 속에 계속해서 자리잡고 있던! 제가 가장 만들어보고 싶던 개인 프로젝트를 진행할 때가 되었구나 생각이 되었습니다.

그 프로젝트는 바로.. 개인용 비서 Bot을 만드는 프로젝트입니다. 다른 누구도 아닌 오직 나 자신만을 위한 개인 비서를 만들어보자라는 생각은 예전부터 했었고, 그것에 대비하여 Toggl 을 통해서 내가 보내는 시간을 기록하고, RescueTime 을 통해서는 어떤 프로그램을 사용하고, 생산성은 어떠한지 기록하고 있었습니다. 그 외에는 Pebble Time을 통해서 걸음걸이와 수면시간 또한 Tracking이 되고 있었습니다. 그리고 Todoist 를 통해서는 일정관리를 하고 있었습니다. 이렇듯 저에 대한 정보들은 수치화된 데이터가 되어 여기저기 쌓이고 있던 것이죠.

그래서 이렇게 쌓은 나에 대한 Data를 바탕으로.. 나를 아는 Bot을 만들고 싶다는 생각을 해왔습니다. 물론 위의 서비스들은 대부분 API를 제공하고 있는 상황입니다.

images

Data 수집 정리

  1. Toggl: 시간을 트래킹하기 편한 앱. 일을 시작하기전에 타이머를 누르고, 일이 끝나면 타이머를 종료해서 내가한 작업들을 기록하는데 많이 사용한다.
  2. RescueTime: 생산성을 관리해주는 툴로서, PC에서 사용한 앱들의 시간을 기록해서 보여줍니다.
  3. Pebble: 걸음걸이와 수면시간을 Tracking. Data는 스마트워치 안에 기록되는 시스템으로 보이나.. 간편하게 사용하기에는 어려워 보이네요.
  4. Todoist: 온라인 작업 관리 및 할일 목록 관리 앱 입니다. 스마트폰, PC, 웹 등.. 다양한 플랫폼을 제공하고 있어서 편하게 사용하고 있습니다.
  5. 그 외 수면 시간, 행복도, 집중도, 생산성 데이터를 수집하는 App (없을 경우 Bot에 붙여서 만들기)

Bot Platform

Bot을 개발하기 이전에 Data 수집에 대한 셋팅은 위와 같이 맞춰놓고, 다음으로 무엇으로 Bot 만들 것인가.. 고민을 해보았습니다. 최근에 Telegram, Facebook Messenger, Line 등.. 많은 Messaing App 기업들이 Bot API를 공개하고 있지만, 개인용으로 사용하기에는 조금 부적합한 면들이 있었습니다.

그렇게 알아보던 중, 제가 사용하고 있는 개인용 Slack이 눈에 들어왔습니다. IFTTT를 연동하여 여러 서비스들에 대한 정보를 Slack에 기록하고 있었고, 개인적인 메모를 하거나 정보를 볼 때 사용하고 있었습니다. 또한 Slack은 굉장히 간단하게 BOT_TOKEN 만 있어도 통신을 주고 받을 수 있고, Slack으로 Salady Bot을 만들면서 이미 개발경험을 가지고 있기에 더욱 적합하다고 생각을 했습니다.

images

출처 : Slack

Slack의 선정이유

  1. 개인용으로 만들어서 운영하는 Slack이 있다. (개인용으로 사용가능)
  2. 다른 App들은 Server로 구성하고, Webhook 설정들의 작업들이 필요하지만, Slack은 Token만 있으면 통신이 가능.
  3. 이미 Slack Bot을 개발해본 경험이 있다.
  4. Team용으로 이미 친숙하게 사용하고 있었다.
  5. 외부 서비스들을 Integration 해서 사용하기 쉽다.

Chat Bot

최근에 Chat Bot이 큰 화두가 되면서, 여러가지 bot에 대한 글을 많이 접하기도 하고 api.ai, wit.ai, 최근 한국에서는 AMICA.ai, fluenty.ai 등의 프레임워크들도 나오면서 쉽게 Chat Bot을 만들 수 있게 되었고.. 조금 더 대중적이게 되었습니다.

이러한 Bot 프레임워크를 사용하다보면 Bot의 Flow도 대략적으로 감이 오고, 어떤 방식으로 이루어져있는지 알 수가 있습니다. 대부분의 프레임워크에서는 NLP 엔진을 이용해서 intent, named entity, sentiment, domain 등을 추출하고, 그에 대한 답을 사용자가 입력하여 연결하는 방식으로 진행이 됩니다. 아직은 Short-term 즉, 조금 전에 대화했던 것들을 기억해서 처리하는 것은 잘 해내지만, Long-term 조금 더 오래된 대화의 경우, 그 대화를 기억하고 말을 이어가는 것은 훨씬 어렵습니다. 그래서 위의 bot 프레임워크들도 long-term까지 지원하는 것을 목표로 개발하고 있습니다.

그래서 제가 생각하기에 봇은 크게 3가지 종류의 봇으로 구분이 된다고 생각을 합니다.

  1. Basic Chatbot: Bot이 그저 하나의 UX인 경우입니다. 정해진 입력에 따라서 정해진 응답을 하는 경우입니다. 보통 정규화 표현식으로 입력을 처리하게 되고, 여기서는 NLP가 들어가지 않습니다.
  2. Smart Chatbot: 이 단계부터는 NLP가 적용된 단계입니다. 각종 Bot framework에서 제공하는 기능처럼, intent, named entity, sentiment, domain 등을 추출하여 그에 따른 응답을 처리합니다. 여기서는 Dialog manager가 대화를 파악하고 관리하며, 자연스러운 응답을 생성하는 NLG까지 포함됩니다.
  3. A.I Chatbot: 이 단계의 Bot은 강 인공지능을 의미합니다. 아직은 어떤 모습으로 나타날지 상상할 수 없는 모습이기도 합니다. Deep Learning + Reinforce Learning 가 합쳐지면서 조금씩 조금씩 이 단계를 향해 나아가고 있다고 생각합니다.

여기서 제가 만들고자 하는 Bot의 목표는 우선.. Smart Chabot 입니다. 하지만 문제는 Data가 어느정도 필요하다는 문제가 있습니다.

그래서 보통 Basic Chatbot부터 시작을 하여 Data를 모으는 것이 일반적 입니다. 그리고 일정량의 Data가 모인 다음에 기존의 로직을 바탕으로 학습을 하는 Imitation Learning을 통해 자동화를 시킬 수 있습니다. 그 후로 더 데이터를 모으고, 기준이 생긴다면 그 것에 맞춰서 조금 더 똑똑하게 행동하도록 만들 수 있을 것 입니다.

저는 이와 같은 장기프로젝트에 대한 계획을 세우면서, 이 과정에서 필요한 기능들이 무엇인지 떠올랐습니다.
우선, 개인용 봇으로 사용하는데, 괜히 어렵게 말을 알아들을 필요가 없다는 것 입니다. 최소한의 자연어 처리를 하고 간단한 키워드 매칭을 통해서 의도를 파악하는 것.
다음으로 새로운 기능들을 다 만들 필요없이 기존의 서비스들을 사용해서 기능들을 만들 것.
마지막으로 나에게 필요한 기능을 내가 지정한 시간에 실행할 수 있는 것.

그렇게 여러가지 서비스들을 Kino의 Skill 들로 등록해서 사용하고, 간단한 자연어 처리로 간단하게 Job을 등록해서 내가 원하는 시간에 그 일을 하도록 만들었습니다 (예, 2시간 마다 날씨 알려줘, 오전 8시에 하루 브리핑 등…) 아래는 지금까지 Kino의 중간 결과물 입니다.

다음에는 Skill과 Scheduling 에 대해서 조금 더 세부적인 내용들을 다뤄보겠습니다.

Kino

그렇게 만들기 시작한 저만의 개인 비서 Kino. 아래는 중간 결과물을 입니다.

skill_example1

Weather Skill

skill_example2

Kino는 아침에 스케쥴을 알려줍니다

guide

인트로 & 가이드

functions

사용할 수 있는 Skill들
Categories
Offsites

Personal Assistant Kino Part 2 – Chatbot의 기본구조 Skill & Scheduler

Kino 프로젝트는 QS를 통해서 자신에 대해서 알고, 불필요한 일들을 자동화시키고 삶의 질을 증진시키기 위한 프로젝트 입니다.
이번 편에서는 가장 기본이 되는 Skill (기능) 등록과 스케쥴링 관리에 대해서 다룹니다.

images

출처 : http://quantifiedself.com/

지금까지의 시리즈

Github: https://github.com/DongjunLee/quantified-self

Skill & Scheduler

Kino에 대해서 간단히 소개를 했던 1편에 이어서, 내부의 핵심이 되는 Skill과 Scheduler 기능에 대해서 이야기하고자 합니다. 이 두가지 기능은 다음과 같은 생각들을 하다가 나오게 되었습니다.

어떻게 하면 나에게 맞는 똑똑한 개인용 봇을 만들 수 있을까?

  1. IFTTT 같은 서비스 처럼 내가 사용하는 서비스들을 자유롭게 Customizing 해서 사용한다면 좋겠다.
  2. 그렇다면 이렇게 만든 함수들을 Skill로 명명하고 관리해야겠다.
  3. 마지막으로 내가 원하는 시간에 이 Skill이 돌아가도록 할 수 있다면?

이 Skill과 Scheduler 를 조금 더 세부적으로 보면 이렇습니다.
외부 서비스의 API를 wrapping해서 custom한 function을 만든다. 그리고 crontab 기능을 이용해서 정해진 시간에 그 기능이 돌아가도록 한다.

이렇게 2가지 기능에 대한 세부적인 내용들이 나오고, 어떻게 구현하는 것이 좋을지 간단한 설계 후 작업을 진행하였습니다. 저는 이 kino 프로젝트를 진행하면서 한가지 중요하게 생각했던 부분이 있습니다. ‘Python 3.6을 이용해서 새롭게 나온 Feature들을 최대한 활용하자.’ 그래서 구현에 대한 설명에도 새로운 Feature들이 포함되어 있습니다. 이 글을 읽으시는 독자 분들이 Python 3.6의 매력을 느낀다면 좋겠습니다.

Skill

Skill은 계속해서 추가될 수 있기 때문에, 프로젝트 구조에서부터 나누는 것이 필요했습니다.

- functions.py : skills에 있는 함수들을 등록하는 파일
- skills : Skill들을 모아놓는 디렉토리
	- weather.py : 날씨 skill
	- todoist.py : Todoist skill
	- ....

다음으로는 ‘어떻게 Skill을 사용할 것 인가’ 입니다.
저는 처음에 간단한 Keyword 매칭 만으로 Skill 을 사용할 생각이였으나.. 모든 Keyword 들을 다 입력해 놓는 것은 좋은 방법이 아니였습니다. Keyword 들을 입력하는 것도 큰 수고가 필요하기 때문에, 조금 더 간결하게 사용할 수 있는 방법을 생각해보았습니다. 그래서 추가된 것이 disintegrator 입니다.

여기서 간단한 NLP 를 사용합니다. 한글의 경우 konlpy가 형태소 분석이나 품사태깅의 기능을 지원합니다. konlpy를 이용해서 하려는 작업은 간단합니다. 문장을 그대로 받아서 Simple하게 만드는 것.

class KorDisintegrator:

    def __init__(self):
        self.ko_twitter = Twitter()

    def convert2simple(self, sentence="", norm=True, stem=True):
        disintegrated_sentence = self.ko_twitter.pos(
            sentence, norm=norm, stem=stem)
        convert_sentence = []

        for w, t in disintegrated_sentence:
            if t not in ['Eomi', 'Josa', 'KoreanParticle', 'Punctuation']:
                convert_sentence.append(w)
        return " ".join(convert_sentence)

위 코드는 신정규님께서 Python 2016에서 발표하신 프로그램 참고하였습니다.
이 KorDisintegrator 를 실제 문장이 통과하면 어떻게 되는지 보시죠!

>>> disintegrator.convert2simple(sentence="키노야 날씨 어때?")
'키노 날씨 어떻다'
>>> disintegrator.convert2simple(sentence="키노야 날씨 알려줘!")
'키노 날씨 알다'

이렇게 간단한 문장으로 변하게 되고, 이제 키워드를 쓸때 고려하면 되는 문장은 원형을 사용하면 되는 것이죠.

이제 문제는 Keyword를 어디에 저장할까? 그리고 skill에 필요한 param은 어떻게 관리할까? 였습니다.
처음에는 ‘외부에 새로 파일을 하나 만들어 skill에 대한 정보를 가지고 있도록 할까?’ 였습니다. 음.. 그렇다면 Bot을 시작할 때, Skill들에 대한 정보를 읽어서 skills.json이라는 파일을 만들면 어떨까? 저는 이 방법이 괜찮다고 생각이 들었고, 여기서 python의 __doc__, __annotations__을 이용하게 됩니다.

아래는 실제 Skill로 사용하고 있는 Code입니다.

def forecast(self, timely: str="current"):
    """
    keyword: ["날씨", "예보", "weather", "forecast"]
    description: "Weather forecast"
    icon: ":sun_with_face: "
    """

    ...
    weather.forecast(timely=timely)

여기서 timely: str 은 Python 3.6에서 새로 추가된 type annotation 기능입니다.
여기서 timely의 타입을 입력해놓으면..

>>> forecast.__annotations__
{"timely": str}

__annotations__를 통해서 필요한 param 정보를 얻어올 수 있습니다. 마찬가지로 __doc__을 이용하면 아래 적혀있는 keyword, description, icon에 대해서 얻어올 수 있습니다. 그래서 이 정보들을 모아서 skills.json 파일을 만들면 Skill 등록은 끝이나게 됩니다.

다음으로는 Python에서 제공하는 any(), all()을 사용해서 키워드 매칭 코드를 작성하고,
Regex(정규식)을 통해서 Skill의 Param을 전달하는 코드를 작성하면!
아래와 같이 날씨 스킬을 손쉽게 사용할 수 있습니다. 참 쉽죠?

images

Scheduler

Skill을 이제 추가하고 사용할 수 있는 구조를 정립했으니, scheduler를 만드려고 합니다.
Python에는 schedule 이라는 스케쥴링을 손쉽게 사용할 수 있도록 도와주는 Package가 있습니다.

아래는 schedule Github페이지에 나와있는 간단한 예제코드입니다. 다른 설명이 필요없이 아주 직관적으로 구성되어있습니다.

schedule.every(10).minutes.do(job)
schedule.every().hour.do(job)
schedule.every().day.at("10:30").do(job)
schedule.every().monday.do(job)
schedule.every().wednesday.at("13:15").do(job)

schedule Package를 이용하면 제가 작업하면 되는 부분은

  1. 백그라운드에서 스케쥴링을 돌린다.
    • Threading 을 사용.
  2. 동적으로 스케쥴링을 돌리도록 하는 것.
    • Python에서 제공하는 built-in funciton인 getattr을 사용

이 부분에 대한 자세한 코드를 알고 싶으시다면, Github 저장소를 참고해주세요! 다음으로 이 함수대로만 사용하는 것은 조금 제한적인 부분들이 있어서 아래와 같은 조건을 추가하게 됩니다.

Between : 시간대를 나타냄. schedule에서는 10분 마다, 1시간 마다 이렇게도 조작할 수 있기 때문에 특정 시간대에 10분 마다, 1시간 마다를 가능하게 하도록 하기 위하여 추가하였습니다.

images

이제 제가 원하는 대로 Scheduling을 사용할 수 있는 준비가 되었습니다!
다음으로는 간단하게 자연어로 Scheduling을 등록하고 작업을 진행시킬 수 있으면 되겠네요.

여기서도 간단한 Keyword 매칭을 사용합니다.

images

이렇게 Scheduling 기능까지 추가를 완료하였습니다!

Example

이제 이 기능들이 실제로 어떻게 쓰이는지 같이 보시죠!

images

images

이렇게 일을 시작하면, 등록된 Scheduling Job 들에 따라서 진행이 됩니다.

images

  • 10시 ~ 22시까지 2시간마다 날씨
  • 오전 8시 하루 프리핑
  • 오후 10시 남은 작업 알려주기 등..

위의 예시들은 실제로 제가 사용하고 있는 Scheduling Job 들 입니다.

이 정도면 제법 개인 비서처럼 행동할 수 있을 것 같지 않나요? 😀
이렇게 Kino 는 똑똑해지고 저를 위한 비서가 되고 있습니다!

다음에는 손쉽게 Todoist에 있는 작업들을, Trello에 카드를 통해 관리하며.. 자동으로 Toggl에 시간 기록까지 되고, 마지막에는 작업 리포트도 받을 수 있는 T3 기능에 대해서 다루도록 하겠습니다.

모든 코드는 여기서 확인하실 수 있습니다.
Kino를 더욱 똑똑하게 만들도록 도와주시는 분들은 언제든 환영입니다^^

Categories
Offsites

Personal Assistant Kino Part 3 – 작업을 최대한 간편하게 관리하고, 데이터를 기록하자

Kino 프로젝트는 QS를 통해서 자신에 대해서 알고, 불필요한 일들을 자동화시키고 삶의 질을 증진시키기 위한 프로젝트 입니다. 이번 편에는 사용하고 있는 다양한 앱들을 연결하여 사용하는 작업 간편 관리 기능에 대해서 이야기 합니다.

images

출처 : http://quantifiedself.com/

지금까지의 시리즈

Github: https://github.com/DongjunLee/quantified-self

저번 편인 Part 2 - Skill & Scheduler 은 Kino가 내가 원하는 대로 돌아가도록 준비하는 과정이였습니다. 이제 이 프로젝트의 목표인 Quantified Self를 다루려고 합니다. 나 자신에 대한 데이터를 손쉽게 모으고, 차트를 보며 피드백을 나 자신에게 주고, 이를 통해 삶의 질을 증진시키는 과정을 말이지요.

T3 (Todoist + Toggl + Trello)

오늘 다루려는 이야기는 Task에 관한 이야기입니다. 저는 Todoist를 굉장히 애용하고 있습니다. Premium 기능으로 업그레이드해서 사용할 정도로 말이죠. 모바일, 데스크탑 전부 사용할 수 있는 app이 있어서 언제 어디서든 간편하게 To do list를 관리할 수 있었습니다.

여기서 더 나아가 나는 작업들을 진행할 때, 시간이 얼마나 걸리는지.. 그리고 이 작업에 대해서는 얼마나 집중을 했는지, 하루의 시간을 어떻게 보냈는지 알고 싶었습니다.

제가 원하는 것들을 충족하기 위해서는 Todoist를 사용하는 것만으로는 부족했습니다. 그래서 필요한 서비스들을 찾아보게 되었습니다. 시간 측정에는 Toggl이 가장 잘 만들어진 서비스였고, 작업을 시작하고 (Doing), 끝내는 것(Done)을 가장 쉽게 다룰 수 있는 것은 Trello칸반 보드를 통해서 Task, Doing, Done 의 리스트로 관리 하는 것이라는 결론을 낼 수 있었습니다.

그렇게해서 만들어진 것이 T3

즉, T3 = Todoist + Toggl + Trello 를 통한 Task 간편 관리 기능입니다.
아래는 T3에 사용되는 Skill들 입니다.

Todoist

  • 🌆 today_briefing : Todoist에 등록된 Task들을 브리핑합니다.
  • 📃 todoist_remain : 남은 작업들에 대해서 안내합니다.

Toggl

  • ⌚️ toggl_timer : Toggl Timer 시작 혹은 정지.
  • 🔔 toggl_checker : 30분 마다 시간 체크. (150분 이상 작업 시, 휴식 추천)
  • 📊 toggl_report : Toggl task 리포트.

Trello

  • 📋 kanban_init : Trello 보드를 초기화 합니다.
  • 📋 kanban_sync : Todoist의 Task들과 Trello 보드의 싱크를 맞춥니다.

Question

  • ✍️ attention_question : 작업 후, 집중도 물어보기 (100점 만점)

  • ✍️ attention_report : 집중도 리포트

작업 관리 시나리오

이렇게 Kino Slack Bot에 연결된 스킬들을 통해서 유기적으로 작업들이 돌아가게 됩니다. 이 프로젝트에서 가장 초점을 맞추고 있는 것 중에 하나가 직접 데이터를 기록하기 위한 노력을 최대한 줄이는 것이기 때문이죠.

위의 스킬을 기준으로 조금 더 자세히 설명드리면 다음과 같습니다.
아침이 되면, Todoist 에 등록된 일감들이 Trello 보드에 자동으로 추가가 됩니다. (kanban_init)

images

여기에서 Tasks 에 있는 작업을 Doing 으로 옮기게 되면, 그때 Toggl의 시간 기록이 동작하게 됩니다.

images.png

단순하게 해당 작업을 끝낸 후에는 Done으로 옮기면 Toggl 시간 기록 역시 정지가 되고, 집중도를 물어보게 되어있습니다.

images.png

그리고 이렇게 카드를 옮겨주면서 작업을 진행해주면, 밤에 이런 리포트를 생성해주도록 되어 있습니다.

images

toggl_report Skill

이상이 제가 하루의 Task를 관리하는 시나리오입니다.
제가 하는 것이란 Trello의 카드를 옮기는 것 뿐이에요. 참 간단하죠?

Quantified Self에서 가장 중요한 것은 데이터를 손쉽게 모을 수 있어야하고, 한눈에 볼 수 있는 차트를 제공함으로서 간편하게 자기자신에게 피드백을 줄 수 있어야 한다는 것 입니다. 그런 의미로 T3는 굉장히 간편하고 유용한 기능입니다. 그리고 Task 관련 데이터들을 Toggl에 쌓여있으므로, 언제든 데이터를 받을 수 있고 더 복잡한 분석 또한 할 수 있을 것 입니다.

Trello Webhook

Skill들을 Python으로 wrapping 되어있는 package 들을 사용하면 간편하게 내가 원하는 커스텀 스킬들을 만들 수 있습니다. 각각 서비스에 필요한 TOKEN, ACCESS_KEY 등을 준비하고 연결하면 손쉽게 끝낼 수 있습니다.

그 외에 작업이 필요한 부분은 Webhook입니다. IFTTT에서도 Trello를 연결해서 사용할 수 있지만, 기본적으로 IFTTT는 실시간이 아닙니다. 하지만 Trello로 Task들을 관리하려면 실시간으로 반응을 해야합니다. 작업을 시작한다고 Doing에 올린지 10분이나 지나서 Toggl Timer가 작동하는 것은 너무나도 불편하니까요.

Trello에서는 Webhook를 추가할 수 있도록 지원하고 있습니다. 이 Webhook을 처리하려면 callback을 처리하는 간단한 서버가 있어야 합니다. 이 callback을 처리하기 위해서 Server를 빌리는 것은 너무 아깝습니다. 이럴때는 Serverless Framework를 사용해서 간단하게 처리할 수 있습니다. AWS에 대한 간략한 설정을 마치고, callback에 대한 함수를 정의한다음 배포를 하면 AWS API Gateway + Lambda가 자동설정 되는 것을 보실 수 있습니다.

def kanban_webhook(event, context):
    input_body = json.loads(event['body'])
    print(event['body'])

    action = input_body["action"]
    action_type = action["type"]

    if action_type == "createCard":
        list_name, card_name = get_create_card(action["data"])
    elif action_type == "updateCard":
        list_name, card_name = get_update_card(action["data"])

    kanban_list = ["DOING", "BREAK", "DONE"]
    if list_name in kanban_list:
        payload = make_payload(action=list_name, msg=card_name)
        r = send_to_kino({"text": payload})
    ...

kino-webhook 여기에서 kanban_webhook이 구현되어 있습니다.

이제 Webhook을 다 정의했으니, 이 Webhook을 Kino가 처리하면 모든 문제는 끝이 납니다! 아래는 카드 이동에 따라서 Skill들의 연결을 정리해놓은 것입니다.

   def KANBAN_handle(self, event):
        toggl_manager = TogglManager()

        action = event['action']
        description = event['msg']
        if action.endswith("DOING"):
            toggl_manager.timer(
                description=description,
                doing=True,
                done=False)
        elif action.endswith("BREAK"):
            toggl_manager.timer(doing=False, done=False)
        elif action.endswith("DONE"):
            toggl_manager.timer(doing=False, done=True) ## Todoist 연결되어 있음
  • Doing : Toggl Timer 시작

  • Done : Toggl Timer 정지 & Todoist 작업 완료

  • Break : Toggl Timer 정지

Monotasking, 한 번에 한가지 일에만 집중을

T3 라는 이름의 작업 관리기능을 만들게 된 것에 대해서는 데이터 수집 이외에도 이유가 하나 더 있습니다.
이렇게 칸반으로 작업을 관리하게 되면, 자연스럽게 강제되는 것이 있습니다. 바로 멀티테스킹을 하지 않도록 되는 것이죠.
멀티태스킹은 여러가지 작업을 동시에 하면서 더 빠르게 작업들을 할 수 있다고 생각을 하게 되지만, 실상은 그렇지 않다고 합니다.

≪정리하는 뇌≫ 의 저자 대니얼 J. 래비틴에 말에 따르면 이렇습니다

우리는 자기가 멀티태스킹을 하고 있다고 생각하지만, 이것은 강력하고도 사악한 착각이다. MIT의 신경과학자이자 분할 주의(divided attention)의 세계적 권위자인 얼 밀러는 우리 뇌가 멀티태스킹에는 별로 적합하지 않게 만들어져 있다고 말했다. 사람들은 자기가 멀티태스킹을 하고 있다고 생각하지만, 실제로는 한 과제에서 다른 과제로 아주 신속하게 전환하고 있을 뿐이라는 것이다.
(중략 …)
멀티태스킹은 투쟁-도피 호르몬인 아드레날린은 물론 스트레스 호르몬인 코르티솔의 생산도 증가시킨다. 또한 뇌를 과도하게 자극해 생각을 뒤죽박죽으로 만든다. 멀티태스킹은 도파인 중독 피드백 고리를 만들어내고, 보상작용을 통해 뇌가 초점을 잃고 끊임없이 외부자극을 찾아 나서게 만든다. 설상가상으로 전전두엽피질은 새로움 편향이 있다. 무언가 새로운 것이 등장하면 쉽게 주의를 뺏긴다는 의미다. – 154 페이지 중에서

물론, 멀티태스킹이 실제로 가능한 사람들도 있다고 합니다. 굉장히 소수의 사람들이라고 하고 대부분의 사람들에게는 한 번에 하나의 일에 집중하는 것이 더 효율적이라고 합니다. 저 역시 한번에 여러가지를 잘 하지는 못하는 사람이기에 이렇게 한번에 한가지 일에 집중하는 시스템을 통해서 강제해보려고 합니다. Doing 에 개발 작업을 옮겼다면, 딱 그 작업만 할 수 있게 말이죠.

끝으로

이번 T3에서는 여러가지 서비스들과 Kino를 전부 연결해서 작업을 간편하게 관리하고, 차트를 제공하는 T3 기능에 대해서 살펴보았습니다. 이렇게 하루하루 Task에 대한 데이터들을 모아서 내가 어떤 작업에 잘 집중하는지, 어느 시간대에 집중을 잘 하는지.. 이런 여러가지 분석을 할 수 있을 것 입니다. 저도 데이터를 모으는 중이라, 나중에 꼭 분석하려고 합니다. 😀

모든 코드는 여기서 확인하실 수 있습니다.

다음에는 최신 Feed를 바로바로 알려주고, 자동으로 모은 데이터를 통해서 분류까지 알아서 하는 Smart Feed 기능에 대해서 알아보겠습니다.

Categories
Offsites

Personal Assistant Kino Part 4 – 자주 읽은 글들은 자동으로 저장하는 Smart Feed

Kino 프로젝트는 QS를 통해서 자신에 대해서 알고, 불필요한 일들을 자동화시키고 삶의 질을 증진시키기 위한 프로젝트 입니다. 이번 편에서는 자동으로 자주 읽는 글들을 저장해주는 Smart Feed 에 대해서 다뤄보고자 합니다.

images

출처 : http://quantifiedself.com/

지금까지의 시리즈

Github: https://github.com/DongjunLee/quantified-self

저번 편에서 Kino의 T3, Task들에 대해서 자동으로 기록하고, 리포팅도 해주는 Task Master 로서의 기능을 살펴보았습니다. 이번 편에는 제가 애용하고 있는 또 하나의 기능. Feed & Pocket 에 대해서 다뤄보고자 합니다.

RSS Feed

RSS Feed는 많은 웹사이트에서 제공하는 RSS를 사용해서 새로운 Article이 등록 되었을 때, 알림을 받을 수 있는 기능을 말합니다. 여기서 잠시 RSS에 대해서 알고 넘어가겠습니다.

RSS(Rich Site Summary)는 뉴스나 블로그 사이트에서 주로 사용하는 콘텐츠 표현 방식이다. 웹 사이트 관리자는 RSS 형식으로 웹 사이트 내용을 보여 준다. 이 정보를 받는 사람은 다른 형식으로 이용할 수 있다.RSS 리더에는 웹기반형과 설치형이 있다. 웹기반형 리더는 간단한 계정등록으로 어디에서든 이용할 수 있다는 장점을 가지고 있다. – 위키백과 RSS

기본적으로 많은 웹사이트들이 RSS를 제공하고 있습니다. 그리고 이것을 이용하는 서비스들도 많이 있지요. 그 중 하나가 Feedly 라는 서비스 입니다. 자주 들어가서 보는 사이트들을 등록해두면, 편하게 새로운 글들을 볼 수 있습니다. 저는 이 서비스를 잘 사용하고 있었지만, 제가 원하는 기능들을 전부 지원하고 있지는 않았습니다.

Pocket

그리고 제가 애용하는 또 하나의 서비스는 Pocket 입니다. 이 서비스가 하는 일은 아주 간단합니다.

When you find something you want to view later, put it in Pocket.

무언가 나중에 읽고 싶은 Article이 생기면, Pocket 에 넣고 아무때나 편하게 보면 되는 것이죠. 저는 유심히 읽고 싶은 Article에 대해서는 Pocket에 저장을 하곤 합니다. 그리고 읽다가 정말 좋은 글이면 Favorite로 옮겨놓곤 하죠.

Smart Feed

저는 이렇게 새로운 글들을 훑어보고, 관심있는 글들을 Pocket에 저장하고, 읽다가 좋다고 느껴지는 글을 Favorite로 옮기는 저의 패턴을 자동화하고 싶었습니다. 그래서 생각하고 만들게 된 기능이 Smart Feed 입니다.

먼저 이 기능에 필요한 것은 RSS 주소들 입니다. 그래야 여기서 RSS를 읽고 새로운 글이 나오면 저장을 하던 알림을 주던 할 수 있겠죠. 그래서 만들게 된 awesome-feeds Repository 입니다. 자주 보는 웹사이트들의 RSS를 Git으로 관리를 하면 편할 것 같기도 하고, 여러 좋은 RSS 주소를 가지고 있는 awesome 시리즈로 만들고 싶었습니다.

이제 RSS가 준비 되었으니, 최신 글이 등록되면 알림을 주면 됩니다!
여기에서는 feedparser를 사용했습니다.

f = feedparser.parse(feed_url)

f.entries = sorted(
    f.entries, key=lambda x: x.get("updated_parsed", 0), reverse=True
)

# get Latest Feed
noti_list = []
if feed_url in cache_data:
    previous_update_date = arrow.get(cache_data[feed_url])
    for e in f.entries:
        e_updated_date = arrow.get(e.updated_parsed)
        if e_updated_date > previous_update_date:
            noti_list.append(self.__make_entry_tuple(category, e, feed_name))

스케쥴 기능은 2편 Skill & Scheduller 에서 다룬 것처럼 지정할 수 있습니다. 매분마다 Feed를 새로 확인하는 것은 과부하가 크기 때문에, 제가 테스트를 해봤을 때는 20분 정도의 interval이면 충분하다고 느껴졌습니다.

def __excute_feed_schedule(self, interval):
    schedule.every(interval).minutes.do(
        self.__run_threaded,
        self.function_runner,
        {
            "repeat": True,
            "func_name": "feed_notify",
            "params": {},
            "day_of_week": [0],
            "not_holiday": False,
        },
    )

이제 Kino가 최신 RSS Feed 들을 바로바로 알려주고 있습니다. 지금도 유용하기는 하지만, 여기서 더 나아가 만들고 싶은 기능이 있었습니다. 제가 무조건 Pocket에 저장을 하는 이미 신뢰받고 있는 웹사이트들은 바로 자동으로 저장을 하는 것!

이것 역시 Pocket 을 연동하고, 간단한 Classification 알고리즘이면 똑똑하게 만들 수 있습니다. 기계학습에서 가장 중요한 것은 Data 입니다. 이런 데이터는 Log들을 이용하면 간단히 만들 수 있습니다. 먼저 Feed 기능에서 알림을 주는 모든 글을 전체 data로 볼 수 있습니다. 이 중에서 Pocket에 저장되는 글만 label 값을 1로 주면, 자연스럽게 전체 데이터들이 관심있는 글 / 관심 없는 글로 나뉘게 됩니다. 여기에 웹사이트의 이름까지 정보로 준다면, 간단한 Decision Tree를 만들 수 있습니다.

images

출처: 위키백과

예를 들어, Google AI Blog 웹사이트에서 새로운 글이 등록 되었을 때, 제가 그 동안 여기서 봤던 글이 총 5개이고, 그 중 4개를 Pocket에 저장했다면, 새로운 글도 관심을 가질만한 글이라고 보는 것이죠.

Decision Tree는 scikit-learn 을 이용하면 아주 간단하게 사용할 수 있습니다.

class FeedClassifier:
    def __init__(self):
        train_X = FeedData().train_X
        train_y = FeedData().train_y
        
        model = tree.DecisionTreeClassifier()
        model.fit(train_X, train_y)  # Training
        self.clf = model

    def predict(self, link, category):
        result = self.clf.predict(category_id)[0]
        if result == FeedDataLoader.TRUE_LABEL:
            ...
        else:
            ...

Online Learning

다음으로 중요한 것은, online learning 입니다. 제가 Pocket에 넣는 Feed들은 그때그때 달라지게 됩니다. 그에 맞춰서 모델 또한 이러한 변화를 감지하고 최신의 정보를 가지고 판단을 해야합니다. 이때 사용되는 방법이 online learning 입니다.

지속적으로 새로운 데이터를 모형에 적용해 모형이 항상 최신의 상태로 유지되기 하는 방식

키노의 Smart Feed는 이 방식을 통해서, 더 똑똑해지고 있습니다. online learning은 하나의 싸이클을 만들어주는 것으로 가능해집니다.

images

  1. Logging: 알람을 받고 있는 Feed의 모든 정보들, 그 중에서 Pocket에 저장한 Feed들 정보
  2. Data Processing: Log를 파싱하여 카테고리, 제목, 날짜, 링크 등의 정보로 가공하고, 라벨 또한 추가해줍니다. (0: Pocket에 추가하지 않음 / 1: Pocket에 추가)
  3. Model: 준비된 데이터를 모델에 Fit 시킵니다. (Training)
  4. Predict: 훈련된 모델을 기반으로 새로운 Feed를 보고 Pocket에 저장할지 말지 판단합니다. 그리고 이때 모델이 잘못 내린 판단에 대해서 Feedback을 제공하여 올바른 라벨이 저장되도록 합니다.

여기서 실시간으로 학습하는 것이 부담이 된다면, 하루에 한번 새로 학습시키는 것도 방법이 될 수 있을 것 입니다.

Conclusion

이번에는 아주 간단한 기능이지만, 정말 유용한 Smart Feed 기능을 살펴보았습니다. 현재는 단순하게 Count를 기반으로 하고 있기 때문에 좀 더 정교한 예측을 하지는 못 합니다. 추후에 Text Classification 문제로서 제목이나 소개글을 통해서 제가 관심을 가질만 한 글인지 예측하도록 만들 생각입니다. 또한 Text Summarization 문제로 다가선다면, 바쁜 저를 위해서 요점만 쏙쏙 정리해줄 수도 있을 것 입니다. 이렇게 Smart Feed 기능의 발전가능성은 열려있다고 생각이 듭니다. 데이터를 많이 모아서, 얼른 Deep Learning 모델로 교체를 해야겠네요!

모든 코드는 여기서 확인하실 수 있습니다.

Categories
Offsites

아주 늦은 2019년 회고

개인적으로 간단하게 작년 연말에 회고를 하였지만, 블로그를 제대로 시작할 겸..!
2019 회고글을 작성해보고자 한다. 생각보다 시간이 지났기 때문에 당시 회고하면서 바라보는 관점과 지금의 관점은 약간의 차이가 있을 수는 있을 것 같다.

회사

그 어느 때보다도 회사의 일에 집중을 했던 한해라고 생각을 한다.
매일 매일 되도록이면 일기를 쓰려고 하는데, 지금 돌아가서 그때의 일기들을 들여다보면 회사에서 느꼈던 감정들이 대부분인 정도이다. 그래서 2019년 회고는 회사 관련한 글이 많지 않을까 싶다.

LaRva 프로젝트와 PM

18년도 10월에 BERT라는 논문이 공개되고, NLP 분야에서는 바야흐로 BERT의 시대가 되었었다. 논문의 abstract에도 다음의 문구가 포함되어 있을 정도이다.

It obtains new state-of-the-art results on eleven natural language processing tasks

“물 들어올 때, 노 저어라” 라는 말처럼, 이 BERT 모델의 급 물살을 탈 수 있었던 프로젝트가 LaRva 프로젝트이다. (이 자리를 빌려, 프로젝트를 셋팅하고 PM을 맡겨주신 서민준님께 감사의 말씀을 드린다.)

최근에는 PM으로서, 제품에 대한 공부를 많이 하고 있다보니 지금의 시각으로 프로젝트를 돌아보려고 한다.
LaRva는 연구위주의 프로젝트이지만, 좋은 언어모델을 만드는 목적을 가진 하나의 제품으로서도 바라볼 수 있다고 생각이 든다.
다음은 ≪인스파이어드≫ 에서 제품 발견의 목적으로서, 아래 4가지 항목의 위험에 대비해야 함을 말하고 있다.

  • 고객이 과연 이 제품을 구매하거나 사용할 것인가? (가치 위험 Value Risk)
    ⇒ 다양한 NLP 서비스에 쉽게 적용할 수 있고, 성능을 올림으로서 서비스 품질에 기여할 수 있다는 것
  • 사용자가 이 제품의 사용 방법을 이해할 수 있는가? (사용성 위험 Usability Risk)
    ⇒ Github에 코드들이 계속 공개 되고 있었고 특히, huggingface에서 지금의 transformers 저장소를 만들어낸 것과 같이, 누구나 쉽게 쓸 수 있도록 정말 빠른 속도로 작업을 하고 있었다.
  • 우리가 만들 수 있는 것인가? (실현 가능성 위험 Feasibility Risk)
    ⇒ 가장 큰 제약은 GPU 일 것이다. 네이버이기 때문에 이런 제약은 해결이 될 수 있었다.
  • 우리 사업에 효과가 있는 솔루션인가? (사업 유효성 위험 Business Viability Risk)
    ⇒ 시간이 지나면서 이 BERT라는 모델의 중요성은 부각이 될 것이고, 이에 따라서 자연스럽게 풀릴 수 있는 문제라고 생각했다.

지금 와서 생각해보면.. 이렇게 잘 정의될 수 있는 프로젝트는 드물 것이라 생각을 한다.

위와 같이 위험한 부분이 없는 프로젝트로서 좋은 결과물들을 만들어 나아갈 수 있었고, KorQuAD v1 리더보드와 연말에는 Deview 2019 발표 그리고 사내 연구 아이템 2위로 선정이 되는 등 여러가지로 정말 얻은 것들이 많다고 생각한다.

2019%204d025a4489ba4870828420f0b38f75b6/Untitled.png

[KorQuAD v1 Leaderboard](https://korquad.github.io/category/1.0_KOR.html), Anonymous 때문에 힘들었던..

2019%204d025a4489ba4870828420f0b38f75b6/Untitled%201.png

출처 : https://www.pinterest.co.kr/pin/515662226060509153/

2019%204d025a4489ba4870828420f0b38f75b6/Untitled%202.png

출처 : https://m.mk.co.kr/news/english/view/2017/11/777734/

여담이지만, 프로젝트의 이름을 LaRva로 짓게 된 이유도 이야기해보려고 한다.
NLP에 계시는 분들은 ELMo, BERT, ERNIE 이런 식으로 네이밍을 하는 이유가 Sesame Street의 캐릭터들의 이름에서 가지고 왔다는 것을 알고 있을 것이다. 어쩌다보니 우리팀 역시 이러한 네이밍 트랜드를 따라서 국산 애니메이션 LaRva로 이름을 짓게 되었다. 지금도 참 입에 잘 붙고, 좋은 네이밍이였다고 생각을 한다.
(LaRva의 풀네임은 Language Representations by Clova 이다.)
그리고 실제로 이러한 네이밍이 가지는 영향에 대해서도 어느정도 실감할 수 있었다. KorQuAD와 Deview 등 모두 LaRva 라는 이름으로 리더보드에 등록하고, 발표를 진행하면서 하나의 팀으로서, LaRva는 어떠한 팀인지 브랜드를 구축했다고 본다.

그리고 또 한가지 나에게 이 프로젝트가 중요한 의미를 가지는 것은 PM 으로서의 커리어이다.
기존에는 팀에서 ML Engineer 로서 역할을 하고 있었지만, 새롭게 프로젝트가 정리되고 기존 팀이 나눠지면서 PM 제안을 받게 되었다.
시작은 나를 포함해서 총 4명으로 일을 진행했고, 매니징보다는 실무를 많이 보았었다. 소수이지만 모두 정말 잘하시는 분들이었기 때문에 위처럼 좋은 결과를 냈다고 생각한다. 그리고 당연하게도 좋은 결과를 내면서 PM이라는 Role에도 약간의 재미를 느꼈던 것 같다.
또한 이때는 이러한 PM이라는 역할이 더 나아가게 될지는 알지 못 했다…!

CLaF 오픈소스화

2019%204d025a4489ba4870828420f0b38f75b6/Untitled%203.png

CLaF(Clova Language Framework)는 Clova AI에 합류했을 때, 기존의 팀에서 진행했던 프로젝트이다. NLP 프레임워크로서 다양한 Task들을 셋팅하고 돌릴 수 있으며 각각의 Experiment 들을 Pipeline 화하여, 하나의 모듈 혹은 컴포넌트를 쉽게 만들 수 있도록 틀을 제공해주는 프레임워크로 설계하였다.

개인적으로 아쉬웠던 점은 Github을 살펴보면.. 다수의 ML/DL 관련한 프레임워크들이 오픈소스화 되어있었는데, 대부분이 미국회사였다. 특히 Google과 Facebook..! 이러한 여러 좋은 프로젝트들에 뒤지지 않는 좋은 퀄리티의 프레임워크를 만들어보고 싶었고, 특히 오픈소스를 만들어서 많은 NLP 종사자분들과 이야기하며 퀄리티도 올리고, 이력서의 하나의 아이템으로도 쓸 수 있도록 만들고 싶었다. 솔직하게, 기대보다는 관심을 받지 못 하였다. Github의 Star가 높다고 맹목적으로 좋다라고 말을 할 수는 없지만.. 관심과 영향도의 측면은 충분히 보여주는 지표라고 생각한다. 현재 기준으로.. Star 182개 이고 나 역시 유지보수에 시간을 사용하지 못하고 있으므로 확실히 정체가 되어있는 상황이라 볼 수 있다.

‘차별화’는 무엇이든 만들 때 적용되는 원리이다. 프레임워크를 만들때 역시 차별화를 확실하게 해야한다는 점을 뼈저리게 느꼈다. 혹은 선구자가 되거나. 사내의 팀용으로 처음 개발을 시작했기 때문에, 다양한 요구사항을 충족시키기 위해 점점 하나의 특징보다는 다양한 케이스를 커버할 수 있는 프레임워크가 되어갔고 이때, 가장 많이 참고했던 allennlp 와의 차별점을 가지지 못 했던 것 같다.

이렇게 비슷한 방향으로 프레임워크가 발전해 나아갔지만, AllenNLP의 경우 그때 당시에도 이미 유명한 프로젝트 였으며 이렇게 될 수 있었던 것은 2가지 요인이 있다고 본다. 첫 번째는 Zero to One 으로 당시에는 이 정도로 잘 만들어져있는 범용 NLP 프레임워크가 없었던 것. 다음으로는 allenai 에서 나오는 논문들이 이 프레임워크에서 릴리즈가 되기도 하면서 특히 수혜를 많이 보았다고 생각한다. ELMo가 대표적일 것이다. claf의 경우에는 후발 주자 이기도 하였고, 논문이 구현되는 등의 이런 수혜를 받을 수 없었기 때문에 어쩌면 당연한 결과일지도 모르겠다.

나에게는 다소 아쉬울 결과였다고 해도, 시작부터 끝까지 만들어나가면서 결국 오픈소스화까지 할 수 있었고 여기서 배운 것들은 매우 많다. 특히 설계부분에 대해서 신경을 썼고, 내부적으로 여러 PR들에 대해 이야기할 때도 설계자의 관점으로서 이야기를 할 수 있다는 것은 특별한 경험이기도 하였다. 앞으로 시간이 좀 날 수 있다면, 유지보수와 더불어서 확실한 색깔을 갖춰보고 싶다.

DUET PM 그리고 AiCall

2019%204d025a4489ba4870828420f0b38f75b6/Untitled%204.png

출처: [아웃백 미금점 AiCall 이벤트](https://clova.ai/m/ko/events/aicall/)

커리어측면으로 확실한 방향 전환이 일어난 사건이다. 사내에서 개발 중인 ‘전화를 대신 받아주는 AI 서비스’ 프로젝트인 DUET의 Project Manager를 9월 혹은 10월 쯤에 맡게 된 것이다. 기존 연구 중심의 LaRva 프로젝트와는 다르게 철저히 Product 중심의 프로젝트였기 때문에 내가 해야하는 역할 또한 기존과는 확실히 달랐다. 이때부터 실무를 볼 수 있는 시간이 급격하게 줄어드는 것이 느껴졌다…

해당 프로젝트는 이미 잘 셋팅이 되어있었기 때문에, 내가 주로 맡았던 일은 ‘제품 실행 관리자 (Delivery Manager)’ 에 가깝다. 어느 회사나 비슷할 것 같지만.. PM 이라는 역할은 필요한 일이면 무엇이든 하고 있는 사람이기에.. ≪인스파이어드≫에서 ‘제품 실행 관리자’ 의 역할을 다음과 같이 소개하고 있다.

제품 실행 관리자는 특별한 유형의 프로젝트 관리자로서 모든 장애물과 방해 요소를 없애는 임무를 가지고 있다. 때로는 다른 제품팀이 장애물이 되기도 하고, 어떤 경우는 제품 외부의 기능 조직이 되기도 한다. 하루 동안 그들은 여러 종류의 일을 해낸다. 마케팅 부서의 누군가를 찾아서 의사결정이나 승인을 요구하고, 다른 팀의 제품 실행 관리자와 협업하여 의존성이 있는 일에 대한 우선순위를 논의한다. … 제품 실행 관리자는 보통 팀의 스크럼 마스터 역할도 수행한다. … 채찍을 휘두르는 것이 아니라 일을 가로막는 방해 요소를 제거함으로써 이를 가능하게 한다.
━ 19. 제품 실행 관리자의 역할 중에서

모든 장애물과 방해 요소를 없애는 임무라.. 이미 들어가 있는 단어가 굉장히 위험하다고 생각이 든다. ‘모든’ 이라니… 그래도 이 한 문장은 PM의 특히나 중요한 임무이면서, 어려운 일을 이야기하고 있다. 위험 요소를 미리 파악하고 대처하며 제거한다는 것. 여기에는 굉장히 많은 일들이 포함될 수 있다. 성능이 안 나오는 컴포넌트의 성능을 올리는 일, 다른 부서와의 커뮤니케이션, 팀원 분들과의 면담과 계속해서 일하고 싶은 팀을 만드는 일 등 사실상 모든 일들이 포함될 수 있다.

그리고 조금 더 많은 인원의 PM으로 일을 하면서 느꼈던 점은 감정노동이 굉장히 많다는 것이다. PM의 기본적인 롤에서 한 가지는 사람을 적재적소에 배치해야 한다는 것이다. 이를 피터드러커의 ≪매니지먼트≫에서는 아래와 같이 이야기를 하고 있다.

가장 중요한 것은 실제로 행하는 일이다.

  • 일과 직장에 대해 성과와 책임을 부여한다.
  • 함께 일하는 사람들을 활용해야 할 대상으로 파악한다.
  • 강점이 성과에 결부되도록 사람을 배치한다.
    이에 따라 사람을 활용해야 할 대상으로 보고 적재적소에 배치할 수는 있을 것이다. 또한 조직의 목표가 온전히 업적으로 향하도록 만들 수도 있다. … 분명한 것은 신뢰와 성과가 눈에 보일 것이라는 점이다. 확신컨대 매니지먼트는 진정한 리더십으로 나아갈 수 있을 것이다.
    ━ 13. 사람이 최대의 자산이다 중에서

사람을 활용해야 할 대상으로 본다는 것은 얼핏보면 거부반응이 일어날 수도 있다. 하지만 이는 정확한 표현이라는 것들을 시간이 지나면서 실감하였다. 이렇게 일을 할 수 있다면, 자연스럽게 아래의 매니저가 해야하는 일 또한 달성이 가능하기 때문이다.

첫 번째 역할은 투입한 자원의 합계보다 큰 것을 만들어 내는 생산체를 조직하는 것이다.
두 번째 역할은 모든 결정과 행동에 있어 필요한 것과 미래에 필요하게 될 것을 조화시켜가는 것이다.
━ 20. 매니저의 일 중에서

그리고 많은 사람들이 함께 일을 하면 할 수록, 수 많은 문제가 생기기도 하고 그러한 문제를 팀원들을 모두 고려하면서 푸는 것이 기본이 된다. 이때에는 감정노동이 꼭 수반된다. 한창 힘들때, 감정노동 관련해서 공감과 위로를 받았던 부분이 ≪실리콘벨리의 팀장들≫ 에 있어 적어보고자 한다.

“제 일이 훌륭한 기업을 만드는 걸까요, 아니면 감정적인 보모 노릇을 하는 걸까요?”
“보모 노릇이 아닙니다. 그걸 관리라고 부릅니다. 바로 당신이 해야하는 일이죠!”

특히 위의 ≪실리콘 벨리의 팀장들≫ 이라는 책에서는 ‘완전한 솔직함’을 기준으로 신뢰관계를 구축하는 것의 중요성을 말하고 있는데, 이러한 신뢰관계가 구축할 수 있다면 팀내의 ‘심리적 안정감’ 역시 구축할 수 있을 것이라 생각을 한다.

여기서 ‘심리적 안정감’은 ≪두려움 없는 조직≫, ≪함께 자라기≫ 등의 책에서 많이 다뤄지는 개념이다. 김창준님이 저술하신 ≪함께 자라기≫ 에 있는 구절을 적어보자면 다음과 같다.

구글은 데이터 중심 회사답게 데이터 기반으로 뛰어난 관리자의 특징을 찾는 옥시전 프로젝트 이후에도 뛰어난 팀의 특징을 찾기 위해 2년간 노력했습니다. 이름하여 아리스토텔레스 프로젝트 입니다.

  1. 팀에 누가 있는지 (전문가, 내향/외향, 지능 등) 보다 팀원들이 서로 어떻게 상호작용하고 자신의 일을 어떻게 바라보는지가 훨씬 중요했다.
  2. 5가지 성공적 팀의 특징을 찾았는데, 그중 압도적으로 높은 예측력을 보인 변수는 팀의 심리적 안전감이었다.
  3. 팀 토론 등 특별히 고안된 활동을 통해 심리적 안전감을 개선할 수 있었다.
    ━ 구글이 밝힌 탁월한 팀의 비밀 중에서

이런 ‘심리적 안정감’ 개선하기 위한 특별한 활동들을 해보지는 못하였는데, 다음에는 기회가 되면 이러한 여러가지 활동을 해보는 것도 많은 것을 배울 수 있을 것이라 생각한다.

마지막으로 ≪매니지먼트≫ 에서 피터드러커가 말하는 매니저의 근본적인 자질에 대한 글로 PM 파트를 마무리하고자 한다.

먼저 사람을 관리하는 능력을 배워야만 한다. … 근본적인 자질이 필요하다. 바로 성실함이다.
조직원들에게 업무 처리를 일류로 해 낼 것을 요구하며 똑같이 엄격한 기준을 스스로에게도 적용한다. 기준을 높이 설정하고 그것을 지킬 것이라 기대한다. ‘무엇’이 옳은지만 생각할 뿐 ‘누가’ 옳은지는 생각하지 않는다. 성실함보다 지적 능력을 평가하는 일도 없다. … 매니저의 일은 체계적인 분석의 대상이다. 매니저가 할 수 있어야 하는 일은 그 대부분이 가르쳐 주지 않아도 배울 수 있는 것이다. 그러나 배울 수 없는 자질, 후천적으로 획득할 수 없는 자질, 처음부터 갖추고 있지 않으면 안 되는 자질이 한 가지 있다. 다시 말하지만 재능이 아니라 성실함이다.
━ 20. 메니저의 일 중에서

대외활동

Deview 2019 발표

2019%204d025a4489ba4870828420f0b38f75b6/Untitled%205.png

Deview 발표는 네이버로 이직을 했을 때부터 막연하게라도 생각해오던 목표 중 하나이다. 그 동안 Deview 에서 다양한 세션들을 들어오면서 한번 쯤은 발표자가 되는 것을 생각해보기도 하였고, 무엇보다 회사 내부에서 한 일을 외부에도 공유하고 알리고 싶었다. 개인은 물론이고, 팀의 기술력 또한 홍보할 수 있는 아주 좋은 기회이기에..!

엄~청 큰 언어 모델 공장 가동기! (LaRva: Language Representation by Clova)

위에서 다루었던 LaRva 프로젝트에 대한 발표였고, 굉장히 많은 분들께서 자리를 채워주셔서 BERT 라는 모델의 영향력이 어느정도 인지 실감할 수 있었다. 발표자료를 준비하면서.. 조금이라도 더 의미있는 시간으로 만들고 싶어서 욕심을 부리다보니.. 발표자료가 총 109장이 되었었다. 막상 발표 떄는 장수에 비해서 조금 빨리 마무리하기는 하였지만 그래도 잘 마무리 했다고 생각을 한다.

아쉬웠던 점은 결국, BERT 모델을 학습할 수 있는 것은 개인으로는 어려움이 있고 기업 특히, GPU 리소스가 풍족한 곳에서 가능한 일이기에 많은 분들에게는 실효성이 조금 적었을 수 있다. 이 분야에 종사하고 있는 분들에게는 도움이 될 수 있겠지만, NLP에 관심이 있는 정도라면.. 조금은 멀게 느껴졌을 수도 있다고 생각이 된다.

그래도 발표를 준비하면서 청중이 누구인지, 그리고 발표의 흐름이 매끄럽게 잘 이어지는지, 각 페이지의 설명이 충분한지, 시간 배분 등.. 이러한 다양한 포인트에 대해서 피드백도 받고 준비를 하면서 많이 배웠다고 생각한다. 20년에도 이렇게 인사이트를 공유할 수 있었으면..!

Quantified Self

주간 작업 리포트의 변화

약 3~4년 전에 사이드 프로젝트로 kino-bot 이라는 나 자신의 데이터를 수집하고, 반복되는 일들을 자동화 시켜주는 개인용 봇을 만들면서 계속해서 데이터를 수집해오고 있었다. 그 중 하나가 생산성을 측정하기 위해, 진행하는 일들에 대해서 시간을 기록하는 것이다.

Task의 카테고리들은 아래와 같다.

  • Seminar: 스터디, 강연 등
  • Review: 명상, 일기, TIL 정리, 회고, Deview 발표연습 등
  • Research: 논문을 포함한 연구관련 작업들
  • Develop: 개발관련 작업들
  • Planning: 계획
  • Meeting: 미팅..
  • Management: 프로젝트 관리에 해당하는 일들 (e.g. 칸반보드, 스크럼 운영, 문서 정리, 커뮤니케이션 등)
  • Exercise: 운동 (산책, 자전거 출퇴근, 요가, 근력 등)
  • Book: 종류에 상관없이 책을 읽는 경우
  • Article : 새로운 정보들을 확인하는 경우 (RSS 피드, 뉴스레터, 블로그, Reddit 등등)

2019%204d025a4489ba4870828420f0b38f75b6/Untitled%206.png

위 차트들은 주 단위의 Task 로서, 월요일부터 일요일까지의 Task들의 시간을 Stacked Bar Chart로 보여준다. 위 카테고리에 해당하는 시간들을 생산적인 일에 사용했다고 가정하고 시간을 기록해오고 있다.

이 차트를 보면, Role의 변화를 체감할 수 있다. 19년 초반에는 주로 Develop 에 대한 일들이 많았다면, 점점 시간이 지나면서.. 8월부터는 Management 라는 카테고리가 추가되기 시작했으며.. 시간이 갈수록 Meeting 과 매니징의 시간이 늘어나고 개발에는 시간을 사용하지 못하는 모습을 보이고 있다.

실제 ManagementMeeting 만 따로 뽑아보면, 아래와 같은 양상을 보인다. 뒤로 갈수록 개발에는 시간을 쓰기 어려워지면서 시간에 대한 고민과 커리어에 대한 고민이 계속 되었던 것이 기억이 난다.

2019%204d025a4489ba4870828420f0b38f75b6/Untitled%207.png

생활 습관을 만든다는 것

Quantified Self로 사이드 프로젝트를 진행했던 이유 중의 하나는 습관이 한 사람에 대해서 가장 많은 것을 설명할 수 있는 단면이라고 생각하기 때문이다. 이러한 습관이 가지는 꾸준함을 개인적으로는 굉장히 중요하게 생각하는데, 꾸준함이 끝내는 변화를 만들어낸다고 생각하기 때문이다. 이 글 (습관은 자신의 참 모습을 보여주는 창이다)은 내가 바라보는 습관에서 잘 말해주고 있어서 덧붙인다.

여기서 특별히 신경을 쓰고 있는 생활 습관은 아래 3가지 이다.

  1. Exercise : 운동
  2. Diary : 일기
  3. BAT : Today I Learnd 오늘 배운 것을 정리

19년 3월

2019%204d025a4489ba4870828420f0b38f75b6/Untitled%208.png

20년 3월

2019%204d025a4489ba4870828420f0b38f75b6/Untitled%209.png

19년도에는 BAT(Today I Learned)를 해야지해야지 했지만.. 제대로 된 작업을 진행하지 못했던 때이다. 그래도 일기와 운동은 19년도나 20년도나 꾸준히 진행 중에 있다. 이 글을 작성하는 지금은 20년 5월인데, 지금은 어느정도 습관으로서 자리가 잡혀있는 상태이다.

위의 Heatmap은 했다/안 했다 의 유무만 보여주고 있지만, 실제로는 시간도 꾸준히 트랙킹을 하면서 적정 시간을 유지할 수 있도록 노력하고 있다.

2019%204d025a4489ba4870828420f0b38f75b6/Untitled%2010.png

위의 3가지 요소 말고도 한가지 더 신경을 쓰는 카테고리는 ‘Book’ 이다. 책 읽는 시간은 현재 4~6시간은 주에 사용할 수 있도록 노력 중이다!

(11월 쯤에 있는 Review 가 아주 높은 주는.. Deview 준비의 흔적으로 보인다!)

마무리

개인적으로 19년은 굉장히 다사다난하기도 했고, 커리어적으로도 큰 변화가 있었던 해이다.

새로운 경험 또한 할 수 있었으며.. 무엇보다 좋은 사람들을 만나고, 프로젝트에 대한 결과까지 만들어 낼 수 있었기에 만족하는 해이기도 하다. 물론 수 많은 고생들이 있었지만, 되돌아 보았을 때는 추억으로 되는 것처럼 좋은 기억들이 남아있다.

Categories
Offsites

Product Manager를 위한 교과서, 인스파이어드

제품에 관한 책을 추천받을 때 항상 가장 먼저 추천하는 책이 있습니다. ‘인스파이어드’라는 책으로 마티 케이건의 저서입니다.

마티 케이건은 기술 제품 관리(Technology Product Management) 분야의 선구적인 사상가이자 실리콘밸리 제품 그룹(SVPG)의 창업자입니다. 또한 그는 HP, 넷스케이프, 이베이 등 세계 최고의 기업에서 제품을 정의하고 구현하는 책임자급 임원으로 근무한 경력이 있다고 합니다.

이 책에서는 Product Manager(제품 관리자)과 Product Team(제품팀)에 대해서 주로 이야기를 하고 있습니다. 개인적으로는 옮긴이의 말에 있는 문구가 이 책을 아주 잘 설명하고 있다고 생각합니다.

몇 년이 지난 지금까지도 ≪인스파이어드≫는 참고서처럼 수시로 꺼내 보게 되는, 제품 관리자로서 배우고 성장하는 데 도움을 준 가장 기본적인 안내서입니다. – 옮긴이 머리말 중에서

images

이미치출처: https://www.aladin.co.kr/shop/wproduct.aspx?ItemId=176659275

Product Manager는 CEO의 역할과 가장 비슷하다.

그렇다면 마티 케이건이 말하는 제품관리자는 어떤 사람인지 한번 들어보시죠.

이 책을 관통하는, 내가 확신하고 있는 중심적인 개념이 있다. 바로 모든 훌륭한 제품 이면에는 지칠 줄 모르고, 무대 뒤에서 최선을 다하는 누군가가 있다는 것이다. 그 사람은 제품팀을 이끌며, 비즈니스 목표에 맞는 방향으로 기술과 디자인을 통해 고객의 실제 문제를 해결한다. 이런 사람들을 우리는 제품 관리자(product manager)라고 부른다.
… (중략)
나는 주어진 업무를 충실하게 수행하면서 주 60시간보다 적게 일하는 제품 관리자를 많이 보지는 못했다.

뛰어난 제품 관리자는 네 가지 중요한 책임을 다 한다.

  1. 고객에 대한 깊은 이해: 제품 관리자는 실제 제품에 대해 모두가 인정하는 전문가가 되어야 한다.
  2. 데이터에 대한 깊은 이해: 오늘 날 제품 관리자는 데이터와 분석 정보에 익숙해야 한다. … 데이터를 분석하고 고객을 이해하는 일은 다른 사람에게 맡기면 안 된다.
  3. 비즈니스에 대한 깊은 이해: 성공적인 제품은 고객에게 사랑받는 것은 물론이고, 사업적인 성과도 함께 창출한다.
  4. 시장과 산업에 대한 깊은 이해: 당신이 경쟁하고 있는 시장과 산업에 대한 깊이 있는 지식이다.

피터 드러커의 ≪매니지먼트≫에서 매니저를 ‘조직의 성과에 책임을 지는 자’ 라고 정의한 것처럼, 케이건은 Product Manager를 ‘제품을 완성하는 것에 그치는 것이 아니라 비즈니스 성과를 책임지는 자’라고 말하고 있습니다. CEO의 역할과 가장 비슷하다고 말하는 이유이기도 합니다.
또한 마티 케이건은 Product Manager는 누구보다 똑똑하고 창의적이며, 집요한 사람이어야 하기에 이는 자 이여야 한다고 말하고 있습니다. 비즈니스에 대한 성과를 만들 수 있다는 것은 곧 제품 백로그에 있는 일이 만들만한 가치가 있는지 확실할 수 있다는 뜻이기도 하기 때문입니다.

진정으로 제품팀이 전부다.

다음으로 저자가 특히 강조하는 ‘제품팀’ 에 대한 설명입니다. 제품팀에 대해서는 책의 전반에 걸쳐서 다뤄지고 있고, 이는 제품팀이 훌륭한 제품을 만드는 데 있어 얼마나 필수적인 요소인지를 말하고 있다고 생각합니다.

제품팀은 아마도 이 책 전체에서 가장 중요한 개념일 것이다. 진정으로 제품팀이 전부다.

존 도어 (John Doerr)의 말이 제품팀의 목표를 가장 잘 표현한다. “우리가 원하는 것은 용병팀이 아닌 미션팀(team of missionary)이다.” 용병팀은 지시한 것만을 만든다. 미션팀은 진심으로 비전을 믿고 그들의 고객 문제 해결을 위해 최선을 다한다.

위의 존 도어의 말은 제품팀을 정의하는 데 있어서 가장 중요한 문장일 것입니다. 제품팀은 미션팀 이여야 한다는 것. 그리고 제품을 만들어본 사람들이라면 용병팀과 미션팀이 보여주는 모든 차이에 대해서 이해할 수 있을 것입니다.

기억하라. 제품팀의 가장 중요한 속성은 용병이 아닌 미션팀을 원한다는 것이다. 이것은 주인 의식과 자율성의 개념과 직접 연관되어 있다. 팀이 충분한 권한을 가지고 있고, 제품에서 중요한 부분을 책임지고 있다는 것을 느낄 수 있어야 한다. … 우리는 최대한의 자율성을 위해 늘 최선을 다해야 한다.

스타트업은 이러한 제품팀의 관점으로 바라본다면, 다음과 같을 것입니다.

“오직 우리만이 저 문제를 제대로 풀 수 있고, 이 문제를 풀어서 고객에게 가치를 준다.” (How to Start a Startup – Stanford에서 말하는 스타트업의 창업자에 대한 강의에서 인용하였습니다)

저는 제품팀이 만들어지는 것은, 창업한 후에 약 10명 규모가 되기까지 초기 직원들을 고용하는 과정에 가깝다고 생각을 합니다. 초기에 꾸려지는 제품팀의 팀원들은 만들고자 하는 제품에 대한 비전을 믿으며, 개개인이 가지고 있는 능력 또한 출중한 사람일 것이기 때문입니다.
여기서 특히 중요한 것은 제품의 미션을 믿는 것입니다. 능력에 대해서는 개개인의 노력에 해당하는 일이지만, 해결하고자 하는 문제와 제품에 대한 비전을 팀원들에게 관철되도록 하는 것은 제품 관리자가 꼭 해야 하는 일이기 때문입니다. (이것이 가장 어려운 일이 아닐까 싶기도 합니다) 또한 다시 한번 말하지만, 용병팀과 미션팀은 일을 진행하는 방식과 퍼포먼스 그리고 일을 진행하면서 배우는 학습까지 모든 것이 다르기 때문이기도 합니다.

제품에 대한 기본적인 안내서

처음 이 책을 소개할 때, 이 책을 ‘제품 관리자로서 배우고 성장하는 데 도움을 준 가장 기본적인 안내서’ 라고 이야기했었습니다. 바로 ‘각각의 역할’, ‘제품’ 그리고 ‘프로세스’ 에 대해서도 다루고 있기 때문입니다.

제품 관리자, 디자이너와 엔지니어, 제품 로드맵과 전략, 원칙 그리고 OKR 등 프로세스에서는 제품 발견부터 프로토타이핑과 발견한 제품에 대한 테스트, 이후의 프로세스 확장까지 다양한 주제에 관해서 이야기를 합니다. 하나하나 깊이 들어가는 것보다는 하나의 주제에 대해서 핵심이 되는 부분을 이야기합니다.

또한, 단순하게 제품을 만들어 가는 과정만을 이야기하는 것이 아닌, 각 회사 크기에 대한 접근법과 특징들, 제품 로드맵, 출시되기까지의 전체 프로세스, 이해 관계자의 관리 역시 중요함을 이야기하고 있습니다. 어느 규모에서든 제품을 만들어본 경험이 있으시다면 자연스럽게 공감할 수 있는 내용 또한 담겨 있습니다.

마지막으로 무엇보다 어려운 일은 훌륭한 제품을 계속해서 만들 수 있도록 환경을 만드는 일일 것입니다. 저자는 마지막 파트로서 ‘문화’를 이야기합니다.

이 책에서 진정으로 말하고자 하는 것이 바로 제품 문화라는 것을 당신이 느꼈을 것으로 생각한다. … 나는 제품 문화를 두 가지 관점으로 바라본다. 첫 번째 관점은 회사가 고객을 위해 가치 있는 솔루션을 만들어 내기 위해 끊임없는 혁신을 할 수 있느냐는 것이다. 이것이 바로 제품 발견이다. 두 번째 관점은 실행이다. 아무리 훌륭한 아이디어라도 만들 수 없고 고객에게 전달할 수 없는 버전이라면 아무 소용이 없다. 이것이 바로 제품 실행이다.

끝으로

저 역시 이 책을 프로젝트 중간중간 참고하기도 하면서 제품이 제대로 나아갈 수 있도록, 비행기의 항로를 조절하듯이 계속해서 방향을 잡기 위해 노력을 하곤 합니다. 프로젝트에서 실제로 무슨 역할을 맡고 있던, 제품에 대한 제대로 된 시각과 접근법을 알고 있다면, 일하면서도 더욱더 많은 것을 배울 수 있으며 그 자체로 훌륭한 제품을 만드는 데 한 걸음 더 다가간 것으로 생각합니다.

저자가 강조한 것처럼 저 역시 제품팀이 전부라고 생각을 합니다. 자신이 만드는 제품에 대한 애정을 가지며, 실제로 사용자에게 가치를 줄 수 있는 제품을 내보이는 것. 단순한 문장이지만… 정말로 어려운 일이고, 제대로 하고 싶은 일 이기도 합니다.

이 책에 있는 벤 호로위츠의 ‘(좋은 제품 관리자/나쁜 제품 관리자)’ 의 일부로 글을 마무리하고자 합니다.

  • 좋은 팀은 강렬한 제품 비전이 있고, 그들은 마치 선교사와 같은 열정을 추구한다.
    나쁜 팀은 용병들이다.
  • 좋은 팀은 어떤 아이디어가 진정으로 만들 만한 가치가 있는지를 결정하기 위해 빠르게 제품 아이디어들을 시도해 볼 수 있는 많은 기법에 능숙하다.
    나쁜 팀은 우선순위 로드맵을 만들기 위해 미팅을 진행한다.
  • 좋은 팀은 최종 사용자 및 고객과 매주 직접 만난다. 그리고 최신 아이디어에 대한 고객들의 반응을 확인한다.
    나쁜 팀은 그들 자신이 고객이라고 생각한다.
  • 좋은 팀은 속도의 중요성과 빠른 이터레이션이 혁신의 핵심임을 이해하고 있다. 그리고 이러한 속도는 일의 양이 아닌 올바른 기법을 사용하는 것에서부터 시작되는 것임을 안다.
    나쁜 팀은 그들의 동료가 충분히 최선을 다하지 않는 것이 속도가 느린 원인이라고 불평한다.

부록

  • 2019년 11월 5일에 ‘Product is Hard’ 라는 주제로 마티 게이건이 강연을 하고, 정리가 잘 된 좋은 글이 있어서 추가합니다. (https://medium.com/@kevinsohn/product-is-hard-marty-cagan-2df47835aa1d)

  • Wanted Con : Product & Strategy 에서 라인 서비스 플래너 옥지혜님이 ‘지금 프로덕트 매니저는 무슨 일을 하고 있을까?’ 라는 주제로 이야기한 세션에서 공감가는 내용이 있어서 덧붙입니다.

    제품은 PM과 제품팀이 버텨서 성공할 수 있는 것이 아닌가… 제품의 한계는 제품팀의 한계와 같다.

Categories
Offsites

Quantified Self Part 5 – 데이터 시각화와 대쉬보드 with KPI

Kino 프로젝트는 QS를 통해서 자신에 대해서 알고, 불필요한 일들을 자동화시키고 삶의 질을 증진시키기 위한 프로젝트 입니다. 이번 편에서는 그 동안 모아온 데이터에 대한 시각화와 대쉬보드에 대해서 다뤄보고자 합니다.

images

출처 : http://quantifiedself.com/

지금까지의 시리즈

Github: https://github.com/DongjunLee/quantified-self

데이터 시각화와 대쉬보드가 필요한 이유

어떠한 문제를 해결하고 싶다면, 어떻게 풀어나갈 것인지 생각하는 것보다 중요한 것은 문제 자체에 조금 더 파고 들어가 그 문제를 제대로 이해하는 것입니다. 저는 지금까지 QS 프로젝트를 진행하면서 여러가지 데이터들을 쌓아왔습니다. 데이터 종류에는 각종 작업들에 대한 데이터와, 행복도, 잠에 대한 것들을 포함되어 있습니다. 기본적으로 시계열 데이터의 형식으로 되어있으나, 다양한 범주로 구성되어 있을 것 입니다. 이러한 데이터에서 실제 문제를 발견하는 가장 좋은 방법이 무엇일까요?
바로 ‘시각화(Visualization)’ 입니다.

어떻게 시각화를 하였는지 바로 넘어가기 전에 ‘시각화’ 에 대해서 조금 더 이야기 해보는 것이 글을 이해하는데 있어 도움이 될 것 같습니다. ≪데이터 시각화 교과서≫ 라는 책을 지은 클라우스 윌케는 데이터 시각화에 대해 다음과 같이 이야기하고 있습니다.

데이터 시각화는 다양한 의미가 담긴 숫자들을 점으로, 선으로, 면으로 그려내는 작업입니다. 수학적 언어를 시각적 언어로 ‘번역’하는 작업이죠.

이렇게 숫자를 ‘보는’ 언어로 바꿔서 우리에게 보여주는 이유는 데이터가 가지고 있는 무언가를 알아내기 위함입니다. 윌케는 시각화에 목적에 대해서는 이렇게 이야기합니다.

데이터를 시각화하는 목적은 주로 소통이다. 우리에게는 데이터셋에서 얻은 통찰(insight)이 있고, 잠재 독자가 존재하며, 우리가 얻은 통찰을 독자에게 전달하고자 한다. 통찰한 결과를 성공적으로 알리려면 독자에게 의미 있고 흥미로운 이야기를 들려줘야 한다.

이번 시각화에서의 독자는 저 자신이며, 하루하루 지내면서 지나쳤던 삶의 패턴들을 발견하고 다양한 통찰을 스스로에게 전달하는 것이 목적이 될 것입니다.

그리고 몇가지 차트들을 엮어서, 화면을 구성하는 것을 보통 대쉬보드(Dashboard)라고 합니다. 위키백과에서는 대쉬보드를 다음과 같이 설명합니다.

대시 보드는 특정 목표 또는 비즈니스 프로세스와 관련된 주요 성과 지표를 한 눈에 볼 수있는 그래픽 사용자 인터페이스 유형입니다.

이 QS 프로젝트의 대쉬보드 역시, 저 스스로 목표로 하는 주요 성과 지표를 한 눈에 볼 수 있어야 할 것입니다.

Python 시각화 라이브러리, Ploty

Quantified%20Self%20Part%205%20-%20Dashboard%2079c29c2ae22e47fdb65d96843a8a2566/Untitled.png

시각화 및 대쉬보드에 사용된 라이브러리는 ploty 라는 라이브러리 입니다. 차트를 그리는데 있어서는 matplotlib 을 기본으로 다양하게 사용되기는 하지만, plotly를 선택한 이유는 다음과 같습니다.

  • Plotly는 대쉬보드 용 프레임워크인 Dash 와 호환되어 지원합니다.
  • 간단하게 Interactive 차트를 구현할 수 있습니다.

(Plotly에 적혀있는 소개 한 문장: The interactive graphing library for Python (includes Plotly Express)

최근에 업데이트 된 것과 더불어서 간단히 소개를 더 드리자면, 최근에는 Plotly Express 가 업데이트 되면서 다양한 차트들을 더 간단하게 그릴 수 있게 되었습니다.

import plotly.express as px

df = px.data.gapminder()
fig = px.scatter(
    df.query("year==2007"),
    x="gdpPercap",
    y="lifeExp",
    size="pop",
    color="continent",
    hover_name="country",
    log_x=True,
    size_max=60
)
fig.show()

Quantified%20Self%20Part%205%20-%20Dashboard%2079c29c2ae22e47fdb65d96843a8a2566/Untitled%201.png

이미치 출처: https://plotly.com/python/plotly-express/ (링크에서는 Interactive Chart로 경험하실 수 있습니다.)

이제 도구가 정해졌으니, QS 데이터를 시각화에 대해서 설명드리고자 합니다.

QS를 위한 데이터 시각화

먼저 수집하고 있는 데이터에 대해서 알고 있어야, 무엇을 어떻게 시각화할 것인지 결정할 수 있을 것 입니다. 아래 데이터는 하루를 기준으로 수집되고 있는 데이터의 예시입니다.

  • record: 2020-08-07.json
{
    "activity": {
        "task": [
            {
                "toggl_id": 123,
                "start_time": "2020-08-07T00:27:45+09:00",
                "end_time": "2020-08-07T00:54:23+09:00",
                "project": "Review",
                "description": "...",
                "color": "#566614"
            },
            {
                "toggl_id": 124,
                "start_time": "2020-08-07T00:55:22+09:00",
                "end_time": "2020-08-07T01:05:54+09:00",
                "project": "Article",
                "description": "...",
                "color": "#e36a00"
            },
            ...
        ],
        "happy": [
            {
                "time": "2020-08-07T14:40:17+09:00",
                "score": 4
            },
            {
                "time": "2020-08-07T21:20:26+09:00",
                "score": 5
            }
        ],
        "sleep": [
            {
                "is_main": true,
                "start_time": "2020-08-07T01:52:00.000",
                "end_time": "2020-08-07T09:25:00.000"
            }
        ],
    },
    "summary": {
        "habit": {
		        "exercise": true,
		        "bat": true,
		        "diary": true,        
        },
        "productive_details": {
            "rescue_time": 91.25,
            "toggl": 100.0,
            "github": 40.0,
            "todoist": 88.0
        },
        "attention": 87.6,
        "happy": 96.0,
        "productive": 87.12,
        "sleep": 100.0,
        "repeat_task": 85.0,
        "total": 96.76
    }
}
  • activity: 활동에 대한 로그들을 담고 있습니다. 활동로그의 대표는 task , happy 그리고 sleep 입니다. 그 외에는 in_home, out_company 등.. 출퇴근 시간을 자동으로 추가한 로그 또한 포함되어 있습니다.
  • summary: 각 점수 기준에 맞춰서 계산된 점수들을 기본으로 가지고 있습니다. (계산하는 점수에 대해서는 다음 포스트에서 설명드릴 수 있도록 하려고 합니다.) 추가로 제가 중요하게 생각하는 habit 즉, 습관에 대한 기록 또한 true 혹은 false 로서 저장이 됩니다.

Daily Schedule

Quantified%20Self%20Part%205%20-%20Dashboard%2079c29c2ae22e47fdb65d96843a8a2566/Untitled%202.png

이 차트는 Gantt Chart 를 응용한 것으로서, 각 시간에 대한 작업들을 보여줍니다. 여기서 Y축은 집중도 로서 1~5점의 점수를 가지고 있습니다. 여기에 행복도 점수(초록색 동그라미)까지 더 해줌으로써, 어떤 작업들을 얼마나 집중하며 진행하였는지 그리고 각 시간 때의 기분에 대해서도 알 수가 있습니다. 하루를 대략적으로 돌아보기에 효과적인 차트입니다.
(원래 상세내역은 작업에 대해서 적혀 있지만, 위 차트에서는 ‘…’ 으로 표현하고 있습니다.)

Quantified%20Self%20Part%205%20-%20Dashboard%2079c29c2ae22e47fdb65d96843a8a2566/Untitled%203.png

Pie Chart 는 비율을 보는 용도로 사용이 됩니다. 위의 Daily Schedule과 함께 하루에 어떤 작업을 위주로 하였는지 알 수가 있죠. 여기서 비율은 작업의 시간을 기준으로 합니다.

Daily Habit

Quantified%20Self%20Part%205%20-%20Dashboard%2079c29c2ae22e47fdb65d96843a8a2566/Untitled%204.png

습관은 Heatmap 을 사용합니다. 보통 히트맵은 색상으로 각 데이터의 수를 표현할 때, 많이 사용합니다. 습관을 나타냄에 있어서 히트맵을 사용한 이유는 사실 단순합니다. Github의 개인 Contribution Chart와 같은 컨셉으로 만들고 싶었기 때문입니다.
이와 같이 만든 이유는 ‘일일커밋’ 때문입니다. 하루하루 꾸준히 커밋을 하는 것 역시 습관이라고 말할 수 있습니다. 위의 네모칸을 초록색으로 꽉 채우는 것이 습관을 잘 지켜 나가고 있다는 것을 보여줄 것입니다. 위 이미지의 기간 때에는 일기는 꾸준히 쓰지만, 운동은 중간중간 쉴때가 있고, BAT(공부한 것 정리)는 목표만 있지 실행되지 않던 때 이네요.

  • 참고) Github Contribution Chart

Quantified%20Self%20Part%205%20-%20Dashboard%2079c29c2ae22e47fdb65d96843a8a2566/Untitled%205.png

이미지 출처: https://github.com/jbranchaud/commitart

Daily Summary

Quantified%20Self%20Part%205%20-%20Dashboard%2079c29c2ae22e47fdb65d96843a8a2566/Untitled%206.png

각 날짜 별로 summary 에 포함되어 있는 점수들이 Line Chart 로 표현이 됩니다. 이 값들을 통해서 어떤 것이 잘 지켜지고 있고, 아닌지 점수로서 확인을 할 수 있습니다. 간단하게 보았을 때, repeat_task 의 점수가 보통 가장 낮은 점수를 받고 있음을 알 수 있습니다. 보통 이 차트를 부면서 ‘가장 부족한 부분을 하나씩 끌어올려야겠다.’ 고 자신을 푸쉬하게 됩니다.

Task Report (Daily/Weekly)

하루 혹은 일주일을 기준으로 진행한 Task들을 Stacked Bar Chart 로 시각화합니다.

Quantified%20Self%20Part%205%20-%20Dashboard%2079c29c2ae22e47fdb65d96843a8a2566/Untitled%207.png

이러한 누적막대 그래프는 기준에 따라서 (하루 혹은 월요일~일요일까지의 일주일)의 시간 총량을 알 수 있으며, 각 카테고리 별 시간의 합 그리고 각각의 날들을 비교하기에 좋은 시각화 방식입니다. 위의 차트를 보았을 때, 미팅을 몰아서 하려는 성향과 개발에 대한 시간이 꾸준하게 포함되는 것을 확인할 수 있습니다.

주요 성과지표 관리 (KPI)

위의 각각의 차트들은 실제로 제가 어떻게 생활을 해왔는지에 대해서 시각적으로 말을 해주게 됩니다. 하지만 이 차트들은 바로 보자마자 1) 하루를 생산적으로 보냈는지, 2) 습관으로 만드려고 하는 목표들이 제대로 지켜지고 있는지 명확하게 말해주지 않습니다. 예를 들어, ‘일주일에 5시간 이상은 책을 읽겠다’ 라는 목표를 확인하기 위해서는 주간 Task Report 에서 Book 카테고리의 시간을 직접 확인해봐야 합니다. 그래서 위의 시각화와는 별개로 주요 성과지표를 명확하게 확인할 수 있는 대쉬보드를 따로 개발하였습니다.

먼저 성과의 기준을 가지고 있는 kpi.json 파일이 있습니다.

{
  "daily": {
    "task_hour": [5, 8],
    "exercise": ["X", "O"],
    ...
  },
  "weekly": {
    "book": [2.5, 5],
    ...
  }
}

기준은 단순합니다. 각각의 항목에 대해서 왼쪽은 숫자의 경우 최소값, 오른쪽은 목표로 하는 수치입니다. 위의 값을 예시로 든다면, daily.task_hour 의 경우는 ‘최소 5시간 에서 최대 8시간 정도를 작업에 사용하라’ 는 지표를 의미하게 됩니다. 숫자가 아닌 daily.exercise 의 경우에는 운동을 안 했으면 X, 했으면 O로 인식이 될 것입니다. 이렇게 스스로 정한 KPI 에 맞춰서 대쉬보드가 제대로 하고 있는지 보여줍니다.

Quantified%20Self%20Part%205%20-%20Dashboard%2079c29c2ae22e47fdb65d96843a8a2566/Untitled%208.png

위의 보는 것처럼, Task Hour는 8시간을 넘어서 KPI를 달성했으므로 초록색으로, Diary의 경우는 아직 진행하지 않았기 때문에 빨간색으로 경고를 주고 있습니다. 위 대쉬보드는 Daily(하루 기준) 예시로서 Weekly(일주일 기준) 에서는 각 Task의 종류 별로 시간을 다루고 습니다. 예를 들어, KPI 첫 문단에서 이야기한 ‘일주일에 책 5시간은 보기’ 이러한 목표를 Weekly 대쉬보드로 확인할 수 있는 것입니다.

끝으로

그 동안 QS 프로젝트를 진행하면서, 데이터를 계속해서 모으고 있었습니다. 하지만 이 데이터들을 2020-08-07.json 과 같이 단순 텍스트 저장되어 있기 때문에 문제점을 정확하게 파악하고 있지 못했습니다. 대쉬보드를 만들면서 다양한 시각화 차트들을 그려보고 나니 실체가 적나라하게 보이는 느낌이였습니다. 이때 생산성 높은 생활패턴을 만드는 것과는 거리가 멀게 행동하고 있음을 알 수 있었습니다. 이것이 저에게는 데이터의 패턴을 변화시키는 자극제가 되어주었습니다. (물론, 계속해서 대쉬보드를 보다보면, 이것 또한 자극이 없어질 수도 있습니다..!) 글의 시작에서 이야기한 것처럼, 문제를 해결하기 위해서는 제대로 문제를 이해하는 것이 중요함을 다시 한 번 강조하고 싶습니다.
다음 포스트에서는 이 프로젝트의 1차 정리로서, 목표로 해왔던 ‘좋은 습관 만들기’ 에 대해서 지금까지의 데이터를 기반으로 정리해보려고 합니다.

Categories
Offsites

Quantified Self Part 6 – 생산적인 하루에 대한 정량적인 표현과 4년간의 데이터 이야기

이번 포스트에서는 그 동안 모아온 데이터에 대한 이야기를 집중적으로 해보려고 합니다. 왜 이 데이터들을 모았는지, 그리고 그 동안 모아온 데이터가 무엇을 말하고 있는지에 대한 이야기 입니다.

images

출처 : http://quantifiedself.com/

지금까지의 시리즈

Github: https://github.com/DongjunLee/quantified-self

‘오늘 하루 알차게 보냈다!’ 이 말을 숫자로 표현할 수 있을까요?

Quantified Self 를 주제로 사이드 프로젝트를 진행하면서, 정량적으로 정의하고 싶던 것이 있습니다.
‘오늘 하루 알차게 보냈다!, 뿌듯하다!’ 혹은 ‘아.. 오늘은 아무것도 한 것이 없네..’ 이러한 하루하루에 대한 느낌들 입니다. 느낌이라는 것 자체가 주관적이라는 것을 알고 계실 것입니다. 하루에 대한 평가는 주관이 기본이 되며, 특히 개개인이 가지는 가치관 이라던가, 선호에 따라서 각각 다르게 평가할 수 있습니다. 그래서 실 데이터를 보면서 오늘 하루를 제가 어떤 식으로 평가를 할 것인지 이야기하기 전에, 제가 중요하게 여기는 것을 먼저 이야기 해보고자 합니다.

저는 ‘습관’을 굉장히 중요시 여기고, 하루하루 무언가 꾸준히 하면서 쌓아올린 것이 결국에 나중에 결과를 만들어 낸다고 믿고 있습니다. 이 칼럼(습관은 자신의 참 모습을 보여주는 창이다)에서, 제가 생각하는 습관의 중요성을 잘 말해주고 있습니다.

계몽주의 철학자 데이비드 흄은 이런 습관에 대한 고대와 중세의 해석을 더욱 확장했습니다. 흄은 습관이 바로 인간을 인간으로 만든다고 생각했습니다. 그는 습관을 모든 ‘정신이 그 작동을 의지’하는, ’우주의 접착제(cement)’라 불렀습니다. 예를 들어, 우리는 공을 높이 던지고 떨어지는 것을 볼 수 있습니다. 그는 습관에 의해 우리가 몸을 움직여 공을 던지고 그 공의 궤적을 바라볼 수 있으며, 이를 통해 원인과 결과의 관계를 파악할 수 있다고 생각했습니다. 흄에게 인과론은 바로 습관에 의한 연상작용이었습니다. 그는 언어, 음악, 인간관계 등 경험을 유용한 무언가로 바꾸는 모든 기술이 습관에 의해 만들어진다고 믿었습니다. 곧, 습관은 우리가 세상을 살아가고 이 세상의 원리를 이해하기 위해 반드시 필요한 도구였습니다. 흄에게 습관은 ‘인간 삶의 거대한 안내자’였습니다.

습관에서 ‘무엇을 할 것인가’ 역시 굉장히 중요한 주제입니다. 여기에는 당장 해야 하는 일 보다는 자기 계발에 해당하는 일들이 해당됩니다.

image.png

스티븐 코비의 시간관리 매트릭스

작업에는 몇가지 종류가 있습니다. <성공하는 사람들의 7가지 습관> 에서 나오는 시간관리 매트릭스가 그 중의 하나 일 것 입니다. ‘긴급함’ 과 ‘중요함’ 2가지 척도로서 작업들을 나누는 것입니다. 여기서 제 2사분면은 중장기 계획, 인간관계 유지, 자기계발 등 당장 급하지는 않지만 미래에 큰 보상을 주는 일들이 포함됩니다.

목표는 이렇게 정리할 수 있겠네요.

  1. 자신이 발전할 수 있는 일들에 시간을 많이 사용하고,
  2. 이 활동들을 꾸준히 지속하는 것

이제 제가 어떠한 하루들을 목표로 하는 지 이해 하셨을 것이라 생각이 됩니다. 그 동안의 포스트에서는 데이터를 수집하기 위한 준비와, 그 과정에서 몇가지 자동화도 작업 해보았습니다. 최근 포스트에서는 데이터 시각화도 하면서 대쉬보드도 만들어보았지요. 이 일련의 작업들의 궁극적인 목적은 실제 데이터들을 보면서, ‘생산적인 하루’를 보내고 있는지, 이런 하루를 보내려면 앞으로 어떻게 하면 좋을지 데이터를 보면서 저 자신을 더 깊게 이해하기 위함이였습니다.

생산적인 하루에 대한 Metric 을 정해보자.

생산적인 하루를 정량적인 숫자로 표현하기 위해서는 몇가지 단계를 거칠 필요가 있습니다. ‘오늘 하루 알차게 보냈다!’ 이 문장을 분해를 하는 것입니다. 먼저 이 느낌에 포함이 되는 요소들은 무엇이 있을까요? 예를 들어, 이런 요소들이 있을 것 입니다. 잠은 잘 잤는지, 하려고 했던 To do list 는 다 완료 했는지, 또 그 작업들을 할때 집중해서 했는지, 하루를 기분 좋게 보냈는지, 크게는 이러한 요소들이 있을 것 입니다.

제가 뽑은 요소는 다음의 5가지 입니다. Attention, Happy, Productive, Sleep, Habit. 이제 각각의 요소들을 어떻게 정량적으로 평가할 수 있을지 한번 더 단계를 들어가서 보시죠.

Attention, 작업에 대한 집중

  • 진행한 작업들에 대해서 얼마나 집중했는지는 의미합니다.
  • 기본적으로 40분 이상 진행한 작업에 대해서 집중도를 물어보게 만들어 놓았습니다.
  • 집중도에 대한 점수는 1~5점 척도로 되어있습니다.

image.png

Happy, 그 당시의 기분

  • 하루를 기준으로 200분 마다 그 당시의 기분에 대해서 물어봅니다.
  • 행복도에 대한 점수 역시 1~5점이 기준입니다.

image.png

Productive, 각종 Tool을 활용한 생산성 점수

  • RescueTime 는 PC/모바일을 사용한 기록을 가지고 Productive Pulse 라는 점수를 계산하여 제공해주고 있습니다. (이 값이 어떻게 구해지는 지는 이 링크에서 확인하실 수 있습니다.)

image.png

  • Toggl 에서는 오늘 하루 기록된 작업시간이 8시간 이상부터 100점, 그 아래로는 시간에 비례해서 점수를 차감합니다.
    (예를 들어, 7시간 작업 시 → 87.5점, 6시간 작업 시 → 75점)

image.png

  • Github 에서 오늘을 기준으로 10일 동안의 Commit의 합이 10개 이상부터 100점, 그 아래로는 커밋 수에 비례해서 측정됩니다. 아래 contributions 의 수를 의미합니다.

image.png

  • Todoist 에서는 100점을 기준으로, 완료하지 못한 일들 (+ 기한이 지난 경우 포함)을 우선순위에 따라 차감을 하게 됩니다.
    우선순위는 아래와 같이 4가지가 있고, 높은 우선 순위부터 5, 4, 3, 2 점씩 차감이 됩니다.

image.png

  • 위의 각각의 항목들은 모두 100점 만점이 기준이 됩니다. 여기서 종합 생산성 점수는 다음과 같습니다

Productive = Github (10) + Toggl (30) + Todoist (50) + RescueTime (10)

Sleep, 수면 시간

  • 7시간을 이상은 전부 100점, 그 아래로는 시간과 비례해서 점수를 측정합니다.
  • 수면에 대해서는 약간 관대하게 점수를 재고 있었습니다. (너무 잠을 많이 자는 것이 오히려 피곤함을 초래하는 경우도 있기 때문이죠)

Habit, 매일하는 습관에 해당하는 활동들

  • 개별 항목 하나하나가 5점의 점수를 가집니다.
  • 매일 하려고 하는 습관에 해당하는 일로서 다음의 일들이 습관에 해당합니다.
    • 운동
    • 공부한 것 정리
    • 일기

이제 각각의 항목들을 정리했으니, 마지막으로 이 각각의 점수를 종합해서 오늘 하루를 점수를 매겨보겠습니다.

오늘 하루에 대한 점수 = Attention(20) + Happy(10) + Productive(30) + Sleep(20) + RepeatTask(10) + Habit(15)

이 기준을 모두 만족하면서, 100점을 얻으려면 다음과 같은 일들이 필요합니다. 모든 진행한 작업들에 대해서 집중을 해야하고, 기분도 좋아야 하며, 일일커밋 역시 꾸준히 해주고, 8시간 이상 생산적인 작업을 하고, 등록했던 모든 To do list는 완료를 하고, 컴퓨터를 생산적인 소스 위주로 작업을 하면서, 잠은 7시간 이상 자고, 운동, 공부한 것 정리, 일기 모두 완료를 해야겠네요….😱

이렇게 제가 바라보는 오늘 하루에 대한 점수를 정량화 할 수 있는 식을 만들어 보았습니다. 처음 말한 것처럼, 무엇을 중요시하는 지에 따라서 점수에 대한 비중은 조절할 수 있을 것이고 자신의 생활 패턴에 따라서 기록할 수 있는 수단 또한 달라 질 수 있을 것 입니다.

4년 간의 데이터가 말해주는 것들

사이드 프로젝트를 시작하고, 데이터를 수집하기 시작한 것은 2017-01-30 부터 입니다. 대략 4년째 이렇게 계속해서 Kino를 사용하고 있습니다. 특별히 문제가 없는 이상, 앞으로도 이렇게 사용을 하게 될 것 같네요. 🙂

그럼 그 동안의 데이터의 변화를 보면서 제가 생산적인 하루들을 보냈는지 이야기 해보려고 합니다.

image.png

그림 1 – 월간 종합점수의 변화

위의 도표는 월간 평균 total_score(종합 점수) 를 기준으로 만들어진 라인 차트입니다. 사이드 프로젝트를 시작하는 초반에 열심히 하면서 점수가 올라가다가.. 2018년 상반기부터 떨어지기 시작하는 것이 보입니다. 이 시기가 네이버로 이직을 했던 시기인데, 이직을 하고 나서는 사이드 프로젝트 및 따로 진행했던 ML/DL 논문 구현 들은 하지 않고 회사 일에 더 집중했던 시기입니다. 그리고 2020년부터는 다시 이 QS 프로젝트에 신경을 쓰면서 점수가 올라간 시기입니다. 회사 일도 좋지만, 그 외의 시간을 잘 활용하는 것이 중요한 것 같다는 생각이 드네요. 조금 더 자세히 데이터들을 살펴보려고 합니다.

사이드 프로젝트의 재미 그리고 연구/개발에서 미팅/관리 작업으로

image.png

그림 2 – 각각의 월을 기준으로 각 작업들의 작업시간을 카테고리에 따라 누적한 누적막대차트

이 도표는 월을 기준으로, 각 작업들의 시간을 누적막대로 구성하였습니다. 주요한 작업들인 연구/개발과 미팅/관리만을 표시해보았습니다. 위에서 언급한 것처럼, 2017년에는 이 QS 사이드 프로젝트를 정말 재미있게 개발을 하던 시기이기도 하고, Deep Learning 공부를 하면서 논문 재구현에 힘을 쓰던 시기이기도 합니다. 실제로 작업 시간의 차이가 유의미하게 크기 때문에.. 종합 점수의 차이 또한 이해가 되네요.
2019년도에는 미팅/관리의 작업들이 확 늘어나게 되는데, 커리어의 변화가 이렇게 보이기도 합니다. 관련해서 커리에 대한 이야기는 2019 회고에서 더 자세히 확인할 수 있습니다.

생산적인 하루에 대한 상관관계

이제 다음 데이터를 조금 더 자세히 살펴볼까요? 먼저 보고자 하는 것은 잠 입니다.
‘잠을 더 자면, 그 날의 기분이 더 좋을 것인가?’ 라는 직관적인 생각을 확인해보고자 합니다.

image.png

그림 3- 수면시간에 따른 행복도 점수의 산점도 차트

위 도표를 보면 2017년도의 점들이 2020년에 비해서 왼쪽에 위치한 경향성을 확인할 수 있습니다. 2017년에는 평일에는 보통 5~6시간을 잤고, 2020년에 7~8시간을 자는 것을 확인할 수 있습니다. 이것은 사실 통근 시간에 따른 삶의 질을 확연하게 보여주는 것이기도 합니다. 각각의 연도에 집에서 회사까지 출근하는 시간이 각각 1시간 30분, 30분 이 걸리기 때문이죠. (이렇게 통근 시간이 중요합니다..)

수면 시간은 여러가지로 생활에 영향을 미칠 것 입니다. 예전에 왕복 3시간 통근 하던 때를 추억해보면, 아주 큰 영향이라고 느껴집니다. 데이터는 어떻게 말을 해줄까요? 수면시간과 하루의 행복도 점수 평균에 대한 상관 계수 (0.252) 로 약한 상관관계가 있다는 것을 알 수가 있습니다. 제가 느끼는 느낌의 정도와 상관계수는 차이가 있어보이나, 수면시간의 중요성은 똑같이 말을 해주고 있습니다.

다음으로 종합 점수에 대해서 전체 상관관계를 한번 봐볼까요?

image.png

그림 4 – 수면시간, 작업 수와 작업시간, 그리고 각종 점수들의 상관계수

종합점수에 대해서 가장 강한 상관관계를 가지고 있는 것은 ‘생산성 점수’ (0.62) 입니다. 종합 점수에서 30%으로 가장 큰 비율을 차지하고 있기도 하고, 생산성 점수와 작업의 집중도는 연결되어 있기 때문에 어쩌면 당연한 결과일 것 입니다. 그 다음으로는 점수의 변동이 큰 반복작업 점수 (0.54), 작업시간 (0.42) 으로 작업에 관한 것들이 또한 강한 상관관계를 보여주고 있습니다. 수면 점수(0.20) 와 행복도 점수(0.22) 그리고 집중도 점수(0.27) 는 상대적으로 약한 상관 관계를 보여주고 있네요. 지금의 Metric에서의 생산성은 일정한 기준을 통과하면 만점을 주고 있기 때문에, 질보다는 기본적인 양이 더 중요하게 고려되고 있음을 말해주고 있습니다.

상관계수를 보다 보니, 막연하게 알고 있던 것을 데이터를 통해 확인할 수 있었습니다. 바로 잠을 포기하고 작업을 하는 경우 입니다. 수면시간과 작업시간 간의 상관계수는 (-0.37)으로 음의 상관관계를 보여주고 있습니다. 그에 비해서 수면시간과 집중도 간의 상관계수는 (0.11)으로 아주 약한 상관관계를 보이고 있습니다. 직관적으로는 수면시간이 작업 집중도에 중요한 요소일 것이라고 생각하였는데, 생각과는 약간 다른 결과를 보여주고 있네요. 오히려 수면 시간 보다는 그날의 기분 (0.25) 가 더 상관계수가 높았습니다. 위에서 수면시간과 행복도에 대한 약한 상관관계가 있음을 확인했었기 때문에, 같이 연결해서 봐야할 것입니다.

여기서 지금 Metric의 한계점이 보이기도 합니다. 잠을 줄이면서 공부를 더 한다면, 평소보다 점수는 더 높게 나올 것 입니다. 하지만 ‘꾸준함’을 생각해보면 잠 또한 더 중요하게 생각해야 할 것입니다. 이런 한계점들에 대해서는 아래에서 더 나은 Metric 에서 이야기를 해보고자 합니다.

다음으로는 종합점수와 가장 높은 상관 관계를 보이는 작업에 대해서 조금 더 디테일하게 살펴보겠습니다.

작업의 종류에 따른 집중도와 각 변수들과의 상관계수

어떠한 작업에 대해서 집중도에 관여하는 요소들은 무엇이 있을까요? (Category) 어떠한 작업을 하는지, (When) 언제 하는지, (Mood) 그 당시의 기분은 어떠한지, 조금 더 세부적으로 들어가면 방해요소는 없었는지, 난이도는 적절한지 등의 다양한 요소가 있을 것 입니다. 여기서 데이터를 보려고 하는 것은 작업의 종류, 시간대, 기분을 가지고 살펴보려고 합니다.

여기에는 아래와 같은 가정을 가지고 살펴보려고 합니다.

  • 일에 더 집중하게 될 수록, 그에 따라서 기분도 좋다.
    (몰입, 집중도 점수와 행복도 점수의 상관관계가 높은 경우)
  • 작업을 오래 할수록, 해당 작업에 집중할 수도 있고 집중이 안 될 수도 있다.
    (집중도 점수와 작업시간의 상관관계)
  • 작업은 언제 시작하느냐에 따라서 집중도에 영향을 줄 수 있다.
    (집중도 점수와 시작시간의 상관관계)
    예) 12시에 점심식사 시간인데, 11시 반에 개발을 시작하는 경우

지금처럼 고려되는 범주의 수가 3개 이상일 때는 산점도 매트릭스가 범주 간의 관계를 확인할 수 있어서 적합한 방식입니다.

image.png

그림 5-1 평일 오전 시간대(오전 10시 ~ 오후 1시)의 ‘개발’, ‘미팅’ 에 대한 산점도 매트릭스

작업은 굉장히 다양한 조건들이 있기 때문에, 세부적으로 조건을 가지고 살펴볼 필요가 있습니다. 위 도표는 오전시간을 대상으로 ‘개발’, ‘미팅’ 에 대한 산점도 매트릭스 입니다. 눈으로 확인할 수 있는 작업의 특성은 다음과 같습니다. 미팅은 보통 1시간 단위로 진행이 되므로 10, 11, 12 시에 몰려있는 것을 알 수가 있습니다. 그리고 개발에 대한 작업은 최소 1시간 정도의 집중을 위한 시간을 필요로 하기 때문에, 10시~11시 사이에 밀집되어 있는 것을 확인할 수 있습니다. 이러한 현상을 상관계수로도 확인을 할 수가 있습니다.

image.png

그림 5-2 평일 오전 시간의 ‘개발’, ‘미팅’ 에 대한 상관계수

위의 상관계수 도표를 통해서, 집중도와 각 변수들간의 관계가 차이가 있음이 눈에 확인히 보입니다. 눈에 띄는 차이는 1) 행복도점수와 2) 작업 시간 입니다.

먼저 행복점수 입니다. 개발을 하면서 일에 집중하면 할수록, 그에 따라서 기분이 좋음(0.44)을 보이고 있습니다. 그에 비해서 미팅의 경우에는 작업과 행복도 간에는 상관관계가 없다(-0.07)고 볼 수 있습니다. 오전에는 미팅보다는 개발 작업을 하는 것이 더욱 집중하면서 진행한다는 것을 말해주고 있지 않나 싶습니다.

다음은 작업 시간입니다. 위에서 말한 것과 비슷한 경향으로서, 오전에 진행하는 미팅의 경우 시간이 길수록 집중력이 떨어지는 경향(-0.39)이 보이는 반면 개발은 작업시간이 길수록 더 집중력이 높았던 경향(0.29)을 보이고 있습니다.

image.png

그림 6-1 평일 오후 시간대 (오후 1~8시)의 ‘개발’, ‘미팅’ 에 대한 산점도 매트릭스

위의 도표는 오후 시간대에 대한 같은 조건의 산점도 매트릭스입니다. 오전보다는 오후 시간을 더 길게 활용하고 있기 때문에 점들이 확실히 더 많아 보이네요. 여기에서도 오전에서와 비슷하게, 미팅작업은 대부분 정시에 몰려있는 모습이 보이고, 미팅은 보통 1시간, 개발은 1~2시 사이에 몰려있는 것을 확인할 수 있습니다. 그리고 개발에서는 대부분 행복도 점수가 3~5 점이나, 미팅 작업에서 행복도 점수가 2점으로 떨어진 경우들도 보입니다. 이것은 개발에 비해서 상대적으로 컨트롤이 어려운, 미팅이 가지는 특성이 아닐까 싶습니다.

image.png

그림 6-2 평일 오후 시간의 ‘개발’, ‘미팅’ 에 대한 상관계수

오전과 마찬가지로 상관계수를 살펴보겠습니다. 위와 마찬가지로 1) 행복도 점수 2) 작업 시간으로 집중도의 상관 관계를 비교해보려고 합니다.

가장 큰 차이는 오후에 진행한 미팅은 집중도와 행복도 간의 상관관계가 높다는 것(0.41) 이였습니다. 개별 케이스마다 차이점이 있겠지만, 점들을 보면 행복도 점수 2~5점에 많이 흩어져 있는 것이 보입니다. 미팅은 불필요하게 초대를 받거나 때로는 참관을 하기도 하고, 제대로 된 진전이 없는 경우도 있습니다. 이러한 상황에 따라서 집중도 점수도 차이가 날 것이고 그때의 기분 또한 자연스럽게 연결이 될 것이기 때문입니다.

그에 비해서 오후에는 미팅의 작업시간이 길어도 별로 문제가 되지 않았습니다. 상관계수 0.01 으로 아무런 상관관계가 없다고 보이고 있었습니다. 개발의 경우에는 오전보다는 조금 낮지만 (0.21), 여전히 오래 개발을 할수록 집중을 했었다라는 것을 말해주고 있습니다.

image.png

그림 6-3 개발/미팅/관리 작업들의 몰입 점수에 대한 오전/오후 별 히스토그램

위의 상관관계들을 보았을 때, 아침 혹은 오후에 따라서 작업에 몰입하는 경향에 차이가 있음을 알 수가 있습니다. 집중도가 높으면서 기분이 좋은 경우를 몰입하는 상황(집중도 점수 + 행복도 점수)이라고 가정하고 작업들의 분포를 살펴보았습니다. 미팅/관리 쪽의 일들은 오전보다는 오후에 높은 점수 쪽으로 분포가 되어있고, 개발은 거의 비슷한 분포를 보이고 있네요.

이렇게 시간대에 따라서 영향을 받는 작업들이 있는 반면에, 아무런 상관관계가 없는 것도 있을 것입니다.
바로 ‘운동’ 입니다.

image.png

그림 7-1 평일 모든 시간대의 ‘운동’ 대한 상관계수

운동의 특성상, 언제 시작을 하던 (0.08), 혹은 얼마나 하던 (-0.02), 집중해서 하게 되고, 하고 나면 상쾌하기 때문이 아닐까 싶습니다. 실제로 운동에 대한 작업들은 거의 대부분이 5점에 있습니다. 이 조커와 같은 운동에 대한 효과는 운동을 한 날과 하지 않은 날을 비교해보면 더 그 차이를 체감할 수 있습니다.

image.png

그림 7-2 운동 여부에 따른 바이올린 차트

운동을 한 경우인, 빨간색 바이올린 도표가 조금 더 위로 위치해 있는 것을 볼 수 있습니다. 운동을 한 날이 하지 않는 날보다 조금 더 집중하는 편이고, 작업을 더 많이한다는 것을 말해주고 있습니다.

지금까지의 정보들을 종합해보면 다음과 같은 전략으로 하루의 작업들을 배치할 수 있을 것 입니다.

  • 오전에는 일찍 연구/개발을 시작해서 점심 먹기 전까지 쭉 집중
  • 연구/개발 작업은 오래할 수 있는 시간을 충분히 확보하고 진행
  • 미팅/관리 작업들은 오전보다는 오후에 진행
  • 운동은 아무때나 일정에 맞춰서 하되, 꼭 해주기

자기계발에 해당하는 우선 순위 2 의 작업들

image.png

그림 8 – 우선순위 2에 해당하는 작업 카테고리의 월간 작업시간 막대 그래프

흔히 자기계발에 해당하는 시간들로 카테고리를 정리하였습니다. 여기에는 책을 읽고, 운동을 꾸준히 해주며, 각종 세미나와 강의를 듣고, 공부한 것을 정리하기도 하고, 일기를 쓰고, 명상을 하는 등의 다양한 활동들이 포함됩니다. 2018년 1월에 이상하게 데이터가 틔는 부분이 있지만, 전체적으로는 조금씩 시간을 늘려나가는 모습을 볼 수 있습니다. 명확한 목표 수치가 있는 것은 아니지만, 계속해서 공부를 하면서 꾸준히 월마다 80~100 시간은 쓰고자 하고 있습니다.

Metric은 고정이 아니라, 목표에 맞춰서 계속 변화합니다.

그 동안의 데이터를 분석해보니, 지금은 처음에 목표로 잡고 있던 Metric에는 근접한 생활을 하고 있다고 생각이 들었습니다. 이렇게 습관을 만드는 것이 어렵네요.

살면서 목표는 계속해서 변화하게 됩니다. 그에 따라서 Metric 또한 달라져야 할 것 입니다. 그리고 목표가 바뀌는 것 외에도 새로운 데이터를 수집할 수 있게 되거나, 기존의 데이터를 다른 방식으로 수집하게 된다면 이에 따른 목표 설정이 새로 필요할 것입니다. 현재 몇가지 변화를 주고 있는데, 가장 큰 변화는 아래 3가지 입니다.

image.png

출처: https://blog.fitbit.com/sleep-stages-explained/
  1. 수면 : 현재는 Fitbit Versa 를 통해서 조금 더 정확한 수면에 대한 정보를 얻고 있습니다. 개인적으로 잠에 대한 관심도 많고, 가끔 밤에 잠이 안 와서 밤잠을 설친 날에는 그날 하루가 피곤해지는 것을 경험하다보니.. 자연스럽게 수면에 대해서 점수도 더 올리고 엄격한 기준을 적용해보려고 하고 있습니다.
    위의 그래프에서 보는 것처럼, 잠은 REB, Light, Deep 의 단계를 순환하고 그것을 잡아주고 있기 때문에 더 정확한 진단이 가능할 것입니다. 그 동안은 단순히 잠을 자기 시작한 시간, 일어난 시간을 기준으로 했기 때문에 수면시간과의 상관관계도 더 정확히 알아볼 수 있을 것입니다.
  2. 블로그 작성 : 많은 개발자분들에게 블로그는 꾸준히 해보려고 하지만 정말 잘 안되는 것 중의 하나일 것이라고 생각합니다. 저에게는 특히 그랬네요. 최근에는 글로 자신의 생각을 정리해보는 것이 정말 중요하고, 저만의 컨텐츠를 만들어보고 싶다는 생각으로 노력을 해보고 있습니다.
    블로그는 특별히 습관으로 만들기 위해 신경 쓰는 부분이라 habit 에 포함시켜서 진행 시, 5점을 주도록 설정하였네요. (다른 습관들은 자리를 잡아서 점수를 줄였습니다. 5→3점)
  3. 작업의 질 : 지금까지 집중했던 것은 ‘일단 하자’ 였습니다. 어느정도는 습관으로 자리를 잡은 지금은 ‘더 잘하자’ 에 포커스를 맞춰보고자 합니다. 작업의 질에 집중하는 이유는 의 한 구절을 인용해 보고자 합니다. 이제야 A작업을 어느정도는 해냈고, B 작업을 할 수 있는 단계가 되었다고 생각하기 때문입니다.함께>

    더글러스는 작업을 세 가지 수준으로 구분합니다. A, B, C 작업입니다.
    A 작업은 원래 그 조직이 하기로 되어 있는 일을 하는 걸 말합니다.
    B 작업은 A 작업을 개선하는 걸 말합니다. 제품을 만드는 사이클에서 시간과 품질을 개선하는 것이죠
    C 작업은 B 작업을 개선하는 것 입니다. 개선 사이클 자체의 시간과 품질을 개선하는 것입니다. … 한마디로 개선하는 능력을 개선하는 걸 말합니다.
    더글러스는 “우리가 더 잘하는 것을 더 잘하게 될수록 우리는 더 잘하는 걸 더 잘 그리고 더 빨리 하게 될 것이다” – 복리의 비밀, 34 페이지 중

끝으로

이번 포스트에서는 ‘생산적인 하루’ 를 정량적으로 수치화해보고, 직접 정의를 해본 Metric으로 점수를 산정해보았습니다. 그 동안의 데이터를 살펴보면서, 예전에는 더 열심해 했었는데.. 라는 생각도 하고 조금 더 자세히 데이터들을 분석해보면 하루를 조금 더 효율적으로 보낼 수 있는 방향도 알아보았습니다. 무엇보다도 저 자신을 더 이해할 수 있었습니다. 또한 2020년이 되어서야 데이터를 모으는 것에 그치지 않고, 한 걸음 더 나아갈 수 있었습니다. 가장 중요한 것은 액션 즉, 행동이기에, 이렇게 자신에 대한 데이터들을 통해 현실을 제대로 인지하고 그에 따라서 조금 더 나은 방향으로 행동에 변화를 이끌어 낼 수 있으면 좋겠습니다.
여러분의 하루는 어떤 식으로 수치화를 할 수 있을까요? 또 데이터에서 어떤 것들은 얻고 있으신가요?

Categories
Offsites

클린 아키텍처: 아름다운 코드에서 아키텍처까지

이번 포스트에서는 로버트 마틴의 ‘Clean Architecture’ 을 읽고서 느꼈던 점들을 기반으로, 책에 대한 소개와 추천을 드리고자 합니다.

‘아름다운 코드’ 스터디

이 책은 오랜만에 예전에 했던 스터디를 떠올리게 해주었습니다. 바로 ‘아름다운 코드란 무엇인가?’ 를 주제로 진행했던 스터디 입니다. 이때 다뤘던 책 중의 하나가 로버트 마틴의 ≪클린코드≫ 입니다. 이 책에서는 코드를 어떻게 짜야하는지, 변수와 함수의 네이밍과 함수 간의 순서 등 주로 코드의 가독성을 기본으로 다양한 주제들을 다루고 있기 때문에, 흥미롭게 이야기할 수 있는 주제들이 많습니다. 다만 저자의 스타일이 자신의 주장을 명확하게 밝히는 편이기 때문에 무조건 이렇게 하는게 맞다 라는 관점 보다는 ‘A라는 상황에서는 이런 장점들이 있기 때문에 이렇게 해야 한다고 생각한다’ 에 가깝습니다. 그래서 저자가 제시하는 다양한 상황과 주장에 대해서 서로 어떻게 생각하는지 이야기 해보고 토론해보면서 많은 것들을 배웠던 기억이 납니다.

클린 아키텍처≫ 역시, 스타일은 ≪클린코드≫ 와 비슷합니다. 다만 뷰가 조금 다릅니다. 아주 가까이, 코드란 무엇인가 에서 부터 조금씩 Zoom-out을 하면서 프로그래밍 패러다임, 코드 설계 원칙, 컴포넌트, 아키텍처 까지 전반적인 내용들을 다루고 있습니다. 이번에는 혼자서 책을 보았는데, 같이 보면서 의견 나누면서 스터디 진행하면 좋겠다는 생각이 자연스럽게 들었습니다.

예전에 스터디 했을 때와는 다르게, 이제는 어느 정도 현업에서 개발하고 있기 때문에, 이 책을 볼때는 자연스럽게 그 동안의 경험들에 근거해서 바라보게 됩니다. 특히 아래의 말은 공감이 가는 말이기도 합니다.

소프트웨어 개발의 단순한 진리, 빨리 가는 유일한 방법은 제대로 가는 것이다.

  • 1장 설계와 아키텍처란? 중에서

그렇다면 아름다운 아키텍처란 무엇일까?

아키텍처가 가지는 의미는 무엇일까요? 건축에서 아키텍처는 다음과 같은 의미로 쓰입니다.

건물이나 다른 구조물을 계획하고 설계하고 건설하는 과정과 그 결과물

images

브루넬레스키가 설계하고 건축한 플로렌스 대성당, 출처: 건축 위키백과

CS 에서의 아키텍처 역시, 시스템을 계획하고 설계하는 전반을 포함하고 있다고 생각합니다. 조금 더 분리해서 들여다보면, 시스템을 튼튼하게 받쳐주는 구조를 의미한다고 생각을 합니다. 이 구조가 튼튼할 수록, 쉽게 변경할 수 있을수록 시스템은 무궁무진한 방향으로 발전할 수 있을 것 입니다. 그리고 계획과 설계는 한번에 끝나는 일이 아닌, 계속해서 상황에 따라서 변화해야하는 것이기도 합니다.

아키텍처는 종착지가 아니라 여정에 더 가까우며, 고정된 산출물이 아니라 계속된 탐구 과정에 더 가까움을 이해해야 좋은 아키텍처가 만들어진다.

  • 추천사 중에서

좋은 소프트웨어 설계의 목표는?
소프트웨어 아키텍처의 목표는 필요한 시스템을 만들고 유지보수하는 데 투입되는 인력을 최소화하는 데 있다.

  • 1장 설계와 아키텍처란? 중에서

저자는 아키텍처의 목표에 대해서 명확한 방향을 제시하고 있다고 생각을 합니다.

‘필요한 시스템을 만들 수 있으면서, 가장 적은 인원으로 대응할 수 있는 것.’

필요한 시스템에는 확장성과 속도, 견고함과 같은 측면들이 포함되어 있다고 보이기 때문에 그 외에 한가지 요소를 더 추가하면 조금 더 명확한 방향이라고 할 수 있을 것 같습니다. 바로 시간입니다.

‘요구되는 기간 안에 필요한 시스템을 만들 수 있으면서, 가장 적은 인원으로 대응할 수 있는 것.’

코드에서 패러다임, 컴포넌트, 아키텍처까지

아래에서는 저자가 이 책에서 이야기하는 다양한 주제에 대해서 다뤄보고자 합니다. 저자가 이 책을 쓴 것에는 다음과 같은 전제가 기본으로 들어가 있습니다.

코드는 여전히 순차 sequence, 분기 selection, 반복 iteration 의 집합체일 뿐이다. … 언어는 조금 발전했다. 도구는 환상적으로 좋아졌다. 하지만 컴퓨터 프로그래밍을 이루는 기본 구성요소는 조금도 바뀌지 않았다.

  • 서문 중에서

이를 가장 잘 표현하는 것은 추천사에도 있는 이 말이라고 생각합니다. ‘소프트웨어는 본질적으로 재귀적이고 프랙털구조로 되어 있으며…’ 아래 그림처럼, 일부 작은 조각(기본 구성요소)이 전체(소프트웨어)와 비슷한 형태를 지니는 것.

이제 기본 구성요소를 넘어서 패러다임 부터 간단하게 이야기 해보려고 합니다.

images

출처:[https://www.scienceall.com/프랙털fractal/)

프로그래밍 패러다임

패러다임이란 해당 문제에 접근하는 관점 혹은 방법론을 의미합니다. 프로그래밍 패러다임에는 크게 3가지가 존재합니다. 저자는 이 대표적인 3가지 패러다임을 ‘규제’의 관점으로 바라보고 있습니다. 우리에게 자유를 뺏어 가기 때문이죠.

  1. 구조적 프로그래밍 : 제어흐름의 직접적인 전환에 부과되는 규율이다. (goto문)
  2. 객체지향 프로그래밍 : 제어흐름의 간접적인 전환에 부과되는 규율이다. (함수포인터)
  3. 함수형 프로그래밍 : 변수 할당에 부과되는 규율이다. (할당문)

이렇게 규율을 부과하는 것은 해당 문제를 풀어감에 있어서, 규율이 도움이 되기 때문입니다. 그래서 위의 패러다임들은 배타적인 관계가 아닌, 상호 보완적인 관계에 가깝다고 볼 수 있습니다. 각각의 패러다임이 가지는 가장 큰 강점을 아래와 같이 추려 보았습니다.

구조적 프로그래밍

모든 프로그램을 순차(sequence), 분기(selection), 반복(iteration) 이라는 세 가지 구조만으로 표현할 수 있다는 사실을 증명했다.
(중략)
모듈을 증명 가능한 더 작은 단위로 재귀적으로 분해할 수 있게 되었고, 이는 결국 모듈을 기능적으로 분해할 수 있음을 뜻했다.

  • 4장 구조적 프로그래밍 중에서

객체지향 프로그래밍

images

구현체와 인터페이스 사이의 소스 코드 의존성(상속 관계)이 제어흐름과는 반대인 점을 주목하자. 이는 의존성 역전(dependency inversion)이라고 부르며, 소프트웨어 아키텍처 관점에서 이러한 현상은 심오한 의미를 갖는다.
OO 언어가 다형성을 안전하고 편리하게 제공한다는 사실은 소스 코드 의존성을 어디에서든 역전시킬 수 있다는 뜻이기도 하다.
(중략)
이것이 힘이다! 이것이 바로 OO가 제공하는 힘이다. 그리고 이것이 바로 OO가 지향하는 것이다(최소한 아키텍트의 관점에서는).

  • 5장 객체 지향 프로그래밍 중에서

함수형 프로그래밍

아키텍트는 왜 변수의 가변성을 염려하는가? 터무니없게도 대답은 단순하다. 경합(race) 조건, 교착상태(deadlock) 조건, 동시 업데이트(concurrent update) 문제가 모두 가변 변수로 인해 발생하기 때문이다. 만약 어떠한 변수도 갱신되지 않는다면 경합 조건이나 동시 업데이트 문제가 일어나지 않는다. 락(lock)이 가변적이지 않다면 교착상태도 일어나지 않는다.
다시 말해 우리가 동시성 애플리케이션에서 마주치는 모든 문제, 즉 다수의 스레드와 프로세스를 사용하는 애플리케이션에서 마주치는 모든 문제는 가변 변수가 없다면 절대로 생기지 않는다.
아키텍트라면 동시성(concurrency) 문제에 지대한 관심을 가져야만 한다. … 이 질문에 대한 대답은 대체로 긍정적이다. 단, 저장 공간이 무한하고 프로세서의 속도가 무한히 빠르다고 전제한다면 말이다.

  • 6장 함수형 프로그래밍 중에서

설계 원칙과 컴포넌트

다음으로는 코드의 설계 원칙을 이야기합니다. 다음과 같은 SOLID를 각 항목 별로 살펴보게 됩니다.

  • SRP: 단일 책임 원칙 Single Responsibility Principle
  • OCP: 개방-폐쇄 원칙 Open-Closed Priciple
  • LSP: 리스코프 치환 원칙 Liskov Substitution Principle
  • ISP: 인터페이스 분리 원칙 Interface Segregation Principle
  • DIP: 의존성 역전 원칙 Dependency Inversion Principle

각각의 원칙 마다도 이야기하는 것들이 많이 있습니다만, 이정도로 소개만 하고 컴포넌트에 대해서 이야기를 해보려고 합니다.

SOLID 원칙이 벽과 방에 벽돌을 배치하는 방법을 알려준다면, 컴포넌트 원칙은 빌딩에 방을 배치하는 방법을 설명해준다. 큰 빌딩과 마찬가지로 대규모 소프트웨어 시스템은 작은 컴포넌트들로 만들어진다.

  • 4부 컴포넌트 원칙 중에서

컴포넌트 역시 SOLID 와 비슷하게 몇가지 원칙들을 소개합니다. 다만 컴포넌트가 가지는 속성을 기반으로 설계 원칙들을 이야기 합니다. 코드가 로직의 구성체라면, 컴포넌트는 코드들의 구성체이면서 아래와 같은 특징들을 가지고 있습니다. 가장 중요하게 이야기되는 특징이 ‘배포’ 와 ‘독립성’ 이라는 것을 아실 수 있을 것 입니다.

컴포넌트는 배포 단위다. 컴포넌트는 시스템의 구성 요소로 배포할 수 있는 가장 작은 단위다. … 컴파일형 언어에서 컴포넌트는 바이너리 파일의 결합체다. 인터프리티형 언어의 경우는 소스 파일의 결합체다. 모든 언어에서 컴포넌트는 배포할 수 있는 단위 입자다. … 컴포넌트가 마지막에 어떤 형태로 배포되든, 잘 설계된 컴포넌트라면 반드시 독립적으로 배포 가능한, 따라서 독립적으로 개발 가능한 능력을 갖춰야 한다.

  • 12 장 컴포넌트 중에서

images

컴포넌트 응집도에 대한 균형 다이어그램
  • REP: 재사용/릴리즈 등가 원칙
  • CCP: 공통 폐쇄 원칙
  • CRP: 공통 재사용 원칙

컴포넌트 결합

  • ADP: 의존성 비순환 원칙
  • SDP: 안정된 의존성 원칙
  • SAP: 안정된 추상화 원칙

아키텍처

마지막으로 전체를 아우르는 아키텍처 입니다. 여기에서도 다양한 사례들을 기반으로 이야기하고, 또 저자가 주장하는 구조가 있습니다. 아마 이 책에서 가장 유명한 다이어그램이 아닐까 싶습니다.

images

https://blog.insightbook.co.kr/2019/08/08/클린-아키텍처/

코어인 업무 규칙이 담겨있는 ‘엔티티’, 사용자에 대한 입력과 출력을 기반으로 구성되는 ‘유스케이스’, 그 바깥으로는 컨트롤러와 프레젠터가 있고 마지막 외부 인터페이스들인 (세부사항이라고 표현하기도 하는) 웹, UI, DB 이 있습니다. 의존성은 안쪽을 향해 있으며, 제어흐름 역전을 위해서 DIP가 안에서 사용되는 모습들도 보이고 있습니다.

여기에는 다음과 같은 특징들이 담고 있다고 이야기하고 있습니다.

  • 프레임워크 독립성
  • 테스트 용이성
  • UI 독립성
  • 데이터베이스 독립성
  • 모든 외부 에이전시에 대한 독립성

위의 클린 아키텍처에 대한 조금 더 자세한 설명을 포함해서 경계와 험블 객체, 등 다양한 주제에 대해서 많은 이야기를 하고 있으니 자세히 읽어보시는 것을 추천드립니다.

정리를 하기 전에 마지막으로 ‘아키텍처’ 에 대한 저자의 생각을 인용하고자 합니다. 저 역시 해당 시스템의 아키텍트는 계속해서 코드를 다뤄야 한다는 점에 동의하기 때문입니다.

무엇보다도 소프트웨어 아키텍트는 프로그래머이며, 앞으로도 계속 프로그래머로 남는다. 소프트웨어 아키텍트라면 코드에서 탈피하여 고수준의 문제에 집중해야 한다는 거짓말에 절대로 속아 넘어가서는 안 된다. 소프트웨어 아키텍트는 코드와 동떨어져서는 안 된다. 소프트웨어 아키텍트는 최고의 프로그래머이며, 앞으로도 계속 프로그래밍 작업을 맡을 뿐만 아니라 동시에 나머지 팀원들이 생산성을 극대화할 수 있는 설계를 하도록 방향을 이끌어 준다. … 프로그래밍 작업을 계속하는 이유는, 발생하는 문제를 경험해보지 않는다면 다른 프로그래머를 지원하는 작업을 제대로 수행할 수 없기 때문이다.

  • 15장 아키텍처란? 중에서

끝으로

이 책은 정말 다양한 주제들을 다루고 있습니다. 엉클 밥의 다양한 인사이트 역시 확인하실 수 있을 것입니다. 그리고 서두에 이야기를 한 것처럼, 저자가 던지고 있는 다양한 화두를 살펴보면서 자신은 해당 상황에서 어떻게 생각하고 설계하고 있는지 비교해보면 더 많은 것들을 얻을 수 있을 것이라고 생각합니다. 이 책을 보시면서 설계에 대해서 더 깊이 고민하고, 아름다운 아키텍처를 만드는 아키텍트를 꿈꾸시는 분들에게 추천드리고 싶습니다.

부록

한가지 이야기하고 싶은 점은, 저자의 주 언어는 Java라고 알고 있습니다. 그런 만큼, 구체적인 예시로 들어가면 대부분 객체지향 언어가 많이 부각되기도 합니다. 마지막 장의 패키지를 보면 자바의 Spring에서 많이 보던 구조이기도 합니다. 하지만 처음에 저자가 이야기 한 것처럼 기본 구성요소는 변하지 않기 때문에 문제가 되지는 않을 것이라 생각합니다.

그 외 볼거리

Categories
Offsites

CodeReading – 1. PyTorch

Code Reading은 잘 작성되어 있는 프레임워크, 라이브러리, 툴킷 등의 다양한 프로젝트의 내부를 살펴보는 시리즈 입니다. 이번 포스트에서는 직관적인 사용이 가능한 PyTorch 에 대해서 다뤄보겠습니다.

Code Reading

글쓰기에는 이러한 말이 아주 널리 퍼져 있습니다. “글쓰기를 잘 하려면 먼저 글을 많이 읽어라.” 코드를 작성하는 것에도 적용될 수 있는 말이라고 생각을 합니다. 사실 우리는 이미 글을 많이 읽고 있습니다. 버그를 고치면서 혹은 다양한 코드 예제를 구글링해서 찾아보기도 하고, 동료가 짠 코드를 이해해야 하는 일 등.. 우리는 수많은 코드들을 읽고 있습니다. 하지만 이런 글들을 많이 본다고 해서 좋은 글을 쓸 수 있을까요? 위 글에 대한 전제에는 이부분이 포함되어 있을 것입니다. “글쓰기를 잘 하려면 먼저 (좋은) 글을 많이 읽어라.”

그래서 더 좋은 코드를 작성하기 위해 널리 사용되고 있고, 잘 작성된 코드들의 내부를 살펴보면서 하나하나 읽어보려고 합니다.

이번 포스트에서 코드를 바라보는 기준은 ‘프레임워크’ 로서 바라보려고 합니다. 즉, 일관된 협력을 위해서 어떻게 설계를 하였는지, 그리고 사용자들이 어떤 식으로 재사용을 할 수 있도록 정의했는지 등을 보려고 합니다. 프레임워크에 대해서, 객체지향 설계 책 ≪오브젝트≫에서는 아래와 같이 정의하고 있습니다.

프레임워크란 ‘추상 클래스나 인터페이스를 정의하고 인스턴스 사이의 상호작용을 통해 시스템 전체 혹은 일부를 구현해 놓은 재사용 가능한 설계’, 또는 ‘애플리케이션 개발자가 현재의 요구사항에 맞게 커스터마이징할 수 있는 애플리케이션의 골격(skeleton)’을 의미한다. 첫 번째 정의가 프레임워크의 구조적인 측면에 초점을 맞추고 있다면 두 번째 정의는 코드와 설계의 재사용이라는 프레임워크의 사용 목적에 초점을 맞춘다.

  • 15 Chapter 디자인패턴과 프레임워크 중에서

PyTorch

images

출처:https://github.com/pytorch/pytorch

처음 다뤄보고 하는 프로젝트는 현재 제가 가장 많이 사용하고 있는 프레임워크인 PyTorch 입니다. 딥러닝에서는 TensorFlow 와 같이 가장 널리 쓰이고 있는 프레임워크로서, Dynamic Graph 기반의 명령형 제어흐름과 모듈 구성 그리고 Python으로 손쉽게 사용할 수 있는 특징이 있습니다. 이 특징들로 인해서 직관적인 코드 작성이 가능하고, 디버깅도 편하게 할 수 있으며, 모듈화가 정말 잘 되어 있어서 코드를 사용하는 입장에서 아주 편한 장점이 있습니다. 이런 이유들로 인해서 첫 Code Reading의 사례로 선정하기도 하였습니다.

images

안타깝게도 코어는 C++ 으로 작성 되어있고, PyTorch는 이것을 python으로 사용할 수 있도록 wrapping 한 것입니다. 저의 내공이 부족하여 C++ 내부까지 자세히 살펴보지는 못하고 프레임워크로서 내부 구조와 Python 코드들을 위주로 살펴보려고 합니다. (그래서 살펴보는 코드의 반 이상은 빠져있지 않을까 싶네요..!)

프레임워크로서 살펴보는 것이기 때문에, 전체 코드를 살펴보는 것보다는 중심이 되는 코드들의 구조와 어떤 식으로 협력을 하는지, 또 사용자들이 쉽게 쓰기위한 특징 들은 무엇이 있는지 살펴보겠습니다.

먼저 전반적인 PyTorch의 특징은 다음과 같습니다.

  • Tensor computation (like NumPy) with strong GPU acceleration
  • Deep neural networks built on a tape-based autograd system

출처: https://github.com/pytorch/pytorch

간단하게 직역하면, Numpy 에서 사용하는 방식을 거의 그대로 Tensor 를 다룰 수 있고, 이 Tensor 연산들을 코드로 작성 하면 위에 그림처럼, 자동으로 미분이 가능한 그래프가 그려지면서 backward 호출만 하면 되는 강력한 프레임워크라고 말할 수 있습니다.

다음으로는 Github 에 있는 각 packages 에 대한 간단한 설명입니다.

Packages

  • torch : numpy 유사한 Tensor, GPU 지원
  • torch.autograd : 자동으로 backprop 이 가능하게 하는 패키지
  • torch.jit : Just-in-time compilation
  • torch.nn : neural network 용, 유연성을 최대의 목표로 디자인
  • torch.multiprocessing : data_loading, Hogwild training (without any locking)
  • torch.utils : DataSet, DataLoader 등의 유틸들

여러가지 Package 들이 있지만, 이번에 제가 다뤄보고자하는 것은 torch, torch.autograd , torch.nn 입니다. (코드는 v1.6.0 을 기준으로 살펴보았습니다.)

torch.tensor

여기에서는 Tensor에 대해서 간단하게만 살펴보려고 합니다. (대부분이 C++ 이 베이스이기 때문이죠..)
Tensor는 논리적인 View로서 실제 물리적인 저장소인 Storage 와 같이 이루어져 있습니다.

아래의 Tensor 를 복사하는 부분의 코드를 보시면 확인할 수 있습니다. 논리적인 뷰에서의 설정값들인 offset, size, stride 를 넣게 되어 있습니다.

(stride에 대해서는 이 Stride Visualizer에서 조금 더 자세히 이해할 수 있습니다.)

images

출처: http://blog.ezyang.com/2019/05/pytorch-internals/

# code: https://github.com/pytorch/pytorch/blob/v1.6.0/torch/tensor.py#L66

new_storage = self.storage().__deepcopy__(memo)
...

new_tensor = self.new()
new_tensor.set_(new_storage, self.storage_offset(), self.size(), self.stride())
new_tensor.requires_grad = self.requires_grad

단순하게 말하자면, Storage에는 물리적으로 매핑되는 값들이 관리되고 있고 storage_offset은 물리적인 주소에 대한 offset, strides 는 인덱스에 곱해지는 계수를 의미합니다. 이렇게 논리적 뷰/물리적 저장소가 나뉘어서 관리되고 있기 때문에, Tensor에 대한 단순 Transpose 등의 연산은 계산이 아주 단순하게 됩니다. 논리적인 뷰의 설정만 달라지면 되는 일이기 때문이죠.

Tensor에 대해서 조금 더 자세히 살펴보겠습니다. Tensor는 하나의 데이터 구조 입니다.

# code: https://github.com/pytorch/pytorch/blob/v1.6.0/torch/tensor.py#L35

class Tensor(torch._C._TensorBase):
    ...
# https://github.com/pytorch/pytorch/blob/v1.6.0/torch/_C/__init__.pyi.in

# Defined in torch/csrc/autograd/python_variable.cpp
class _TensorBase(object):
    requires_grad: _bool
    shape: Size
    data: Tensor
    names: List[str]
    device: _device
    dtype: _dtype
    layout: _layout
    real: Tensor
    imag: Tensor
    T: Tensor
    ndim: _int
    _version: _int
    _base: Optional[Tensor]
    grad_fn: Any
    ${tensor_method_hints}

위와 같은 속성들을 가지고 있는 것을 알 수 있습니다. 주요하게는 각 Tensor 마다 requires_grad 을 가지고 있고, grad_fn 또한 가지고 있는데요, PyTorch를 v0.4.0 버전 전부터 사용하시던 분들은 이후에 업데이트 된 내용이 눈에 들어오실 것 입니다. 바로 torch.autograd.VariableTensor 로 합쳐진 것이죠. 주석의 파일 경로를 보면 그 이전에는 Tensor 대신에 Variable 을 사용했던 것을 짐작할 수 있습니다. (/autograd/)

이렇게 데이터에 대한 속성들을 설정하는 것 외에도 코드 상에는 backward 라는 매서드를 가지고 있습니다. 이 함수는 바로 autograd package 로 연결이 되어 있습니다. 이를 통해서, 각 Tensor는 backward 메서드를 가지고 있지만 실질적으로 해당 로직은 autograd 에서 처리됨을 알 수 있습니다.

# code: https://github.com/pytorch/pytorch/blob/v1.6.0/torch/tensor.py#L155

def backward(self, gradient=None, retain_graph=None, create_graph=False, inputs=None):
    r"""Computes the gradient of current tensor w.r.t. graph leaves.
    
        The graph is differentiated using the chain rule.
    ...
    torch.autograd.backward(self, gradient, retain_graph, create_graph, inputs=inputs)

backward 메소드에 의해서 각 Tensor 에 누적되는 gradient → grad.

# code: https://github.com/pytorch/pytorch/blob/v1.6.0/torch/tensor.py#L725

    @property
    def grad(self):
        """
        This attribute is ``None`` by default and becomes a Tensor the first time a call to
        :func:`backward` computes gradients for ``self``.
        The attribute will then contain the gradients computed and future calls to
        :func:`backward` will accumulate (add) gradients into it.
        """
        ...
        return self._grad

또 한가지 눈 여겨서 볼 매서드는 register_hook 입니다.

# code: https://github.com/pytorch/pytorch/blob/v1.6.0/torch/tensor.py#L187

def register_hook(self, hook):
    r"""Registers a backward hook.

    The hook will be called every time a gradient with respect to the
    Tensor is computed. The hook should have the following signature::
      hook(grad) -> Tensor or None
    ...

    Example::
      >>> v = torch.tensor([0., 0., 0.], requires_grad=True)
      >>> h = v.register_hook(lambda grad: grad * 2)  # double the gradient
      >>> v.backward(torch.tensor([1., 2., 3.]))
      >>> v.grad
       2
       4
       6
      [torch.FloatTensor of size (3,)]
      >>> h.remove()  # removes the hook

Docstring 에 적혀있는 것처럼, hookbackward 가 호출 될때마다, 등록한 hook의 함수가 불러지는 것을 알 수 있습니다. 이는 각각의 상황에 맞춰서 gradient를 조절하는 hook이 등록될 수 있음을 의미합니다.

torch.autograd

다음으로는 이어서 autograd 팩키지를 살펴보겠습니다. 이 부분에 대한 소개는 다음과 같습니다.

Autograd is a hotspot for PyTorch performance, so most of the heavy lifting is implemented in C++. This implies that we have to do some shuffling between Python and C++; and in general, we want data to be in a form that is convenient to manipulate from C++.

성능에 주요한 부분으로서 C++ 으로 구현되어 있다는 것을 아실 수 있을 것 입니다. 여기에서 가장 중요한 컨셉은 Node, Function 이 2가지가 될 것입니다. Node 는 그래프에 대한 로직들, Function 은 forward, backward 에 대한 로직들을 담고 있다고 봐주시면 됩니다.

먼저 위의 Tenosrbackward 가 연결되는 autograd.backward 함수를 보시겠습니다.

# code: https://github.com/pytorch/pytorch/blob/v1.6.0/torch/autograd/__init__.py#L57

def backward(
    tensors: _TensorOrTensors,
    grad_tensors: Optional[_TensorOrTensors] = None,
    retain_graph: Optional[bool] = None,
    create_graph: bool = False,
    grad_variables: Optional[_TensorOrTensors] = None,
) -> None:
    r"""Computes the sum of gradients of given tensors w.r.t. graph leaves.
    ...
    """

    ...
    Variable._execution_engine.run_backward(
        tensors, grad_tensors, retain_graph, create_graph,
        allow_unreachable=True)  # allow_unreachable flag

이 부분이 chain rule 에 따라서 계산된 gradient (grad_tensors ) 가 명령형 엔진에 의해서 계산되고, 각 텐서에 grad 값을 누적시키게 됩니다.

다음으로 PyTorch 문서에는 Custom Function을 구현할 수 있는, 확장에 대한 방법이 정리되어 있습니다. autograd 에 정의가 되어 있는 Function 을 상속하면서 필요한 메서드들을 구현하면 되는 것 입니다. 바로 forward와 backward 를 추가하는 것이죠. 아래 코드의 예제를 보면 이해가 갈 것이라 생각이 됩니다.

# Inherit from Function
class LinearFunction(Function):

    # Note that both forward and backward are @staticmethods
    @staticmethod
    # bias is an optional argument
    def forward(ctx, input, weight, bias=None):
        ctx.save_for_backward(input, weight, bias)
        output = input.mm(weight.t())
        if bias is not None:
            output += bias.unsqueeze(0).expand_as(output)
        return output

    # This function has only a single output, so it gets only one gradient
    @staticmethod
    def backward(ctx, grad_output):
        # This is a pattern that is very convenient - at the top of backward
        # unpack saved_tensors and initialize all gradients w.r.t. inputs to
        # None. Thanks to the fact that additional trailing Nones are
        # ignored, the return statement is simple even when the function has
        # optional inputs.
        input, weight, bias = ctx.saved_tensors
        grad_input = grad_weight = grad_bias = None

        # These needs_input_grad checks are optional and there only to
        # improve efficiency. If you want to make your code simpler, you can
        # skip them. Returning gradients for inputs that don't require it is
        # not an error.
        if ctx.needs_input_grad[0]:
            grad_input = grad_output.mm(weight)
        if ctx.needs_input_grad[1]:
            grad_weight = grad_output.t().mm(input)
        if bias is not None and ctx.needs_input_grad[2]:
            grad_bias = grad_output.sum(0)

        return grad_input, grad_weight, grad_bias

위에서 확인하신 것처럼 Function 은 필요한 메서드들을 미리 정의해놓은 추상 클래스라고 말할 수 있습니다. 이제 코드 내부로 가서 이 Function 이 어떻게 구현되어 있는지 살펴보겠습니다.

# code: https://github.com/pytorch/pytorch/blob/v1.6.0/torch/autograd/function.py#L110

class Function(with_metaclass(FunctionMeta, _C._FunctionBase, _ContextMethodMixin, _HookMixin)):
    r"""Records operation history and defines formulas for differentiating ops.
    ...

Function 이라는 객체는 FunctionMeta 라는 Meta Class 로 만들어지며, _FunctionBase 를 상속하고, _ContextMethodMixin_HookMixin 를 통해 인터페이스로 확장이 되어 있다는 것을 알 수 있습니다. 조금 더 자세히 이해를 하려면, Python 이 지원하는 Meta Class와 Mixin에 대해서 간단히 이야기할 필요가 있을 것 같습니다.

Meta Class

메타 클래스에 대해서는 설명이 잘 되어있는 글을 참고해주시면 좋겠습니다. 메타 클래스에 대해서 알고 있다는 가정하에 글을 더 진행해보겠습니다. 메타클래스는 간단하게 아래와 같이 정의가 됩니다.

metaclass ——> (인스턴스) class ——> (인스턴스) object

메타클래스는 대부분 건드릴 일이 없으며, Python의 class를 수정하고 싶을 때 사용할 수 있습니다. 아래와 같이 __init__ , __new__ , __**prepare__** 등 클래스의 빌트인 매서드들을 수정할 수 있습니다.

# https://github.com/pytorch/pytorch/blob/v1.6.0/torch/_six.py#L39

def with_metaclass(meta, *bases):
    """Create a base class with a metaclass."""
    # This requires a bit of explanation: the basic idea is to make a dummy
    # metaclass for one level of class instantiation that replaces itself with
    # the actual metaclass.
    class metaclass(meta):

        def __new__(cls, name, this_bases, d):
            return meta(name, bases, d)
    return type.__new__(metaclass, 'temporary_class', (), {})

그럼 다시 Function 에서 사용되는 with_metaclass 는 어떤 역할을 하는지 살펴보겠습니다. 단순하게 이 코드는 정해진 Meta에 따라서, ‘temporay_class’ 라는 이름으로서 클래스들을 생성 하는 것입니다. Syntactic Sugar에 해당하는 경우라고 볼 수 있을 것 같습니다.

다음으로 넘어가서 여기 meta 에 연결되는 FunctionMeta 를 확인해볼까요?
FunctionMeta 는 생성자(__init__)에서 backward_fn 에 해당 Function 의 backward 메서드를 연결하는 역할을 합니다.

아래의 type 은 코드에 있는 것처럼, 동적으로 클래스를 생성하는 함수로 사용이 됩니다. BackwardCFunction 객체를 만들어서 _forward_clsbackward 메소드가 연결되는 것이죠.

# code : https://github.com/pytorch/pytorch/blob/v1.6.0/torch/autograd/function.py

class BackwardCFunction(_C._FunctionBase, _ContextMethodMixin, _HookMixin):
    _is_legacy = False

    def apply(self, *args):
        return self._forward_cls.backward(self, *args)

class FunctionMeta(type):
    """Function metaclass.
    ...
    """

    def __init__(cls, name, bases, attrs):
       ...

       backward_fn = type(name + 'Backward', (BackwardCFunction,), {'_forward_cls': cls})
       cls._backward_cls = backward_fn

여기서 FunctionBackwardCFunction 은 다중상속이 되어있는 것을 보셨을 것 입니다. 이렇게 구성된 두 클래스가 어떻게 동작하는지 이해하기 위해서는 Python의 상속구조를 이해하는 것이 필요합니다. 아래의 Class.mro() 를 통해서 객체의 매서드 실행 순서를 확인할 수가 있습니다. 호출되는 메서드를 찾을때 왼쪽부터 차례로 오른쪽으로 가는 것이죠.

class BackwardCFunction(_C._FunctionBase, _ContextMethodMixin, _HookMixin):
   ...

# BackwardCFunction.mro() : 해당 객체의 메서드 실행 순서를 표현합니다. (mro -> Method Resolution Order)
# [__main__.BackwardCFunction, __main__._C._FunctionBase, __main__._ContextMethodMixin, __main__._HookMixin, object]

Base가 두개가 되는 것은 흔히 알려져있는 다이아몬드 문제를 야기합니다. 대신 Mixin 이라는 방식을 통해서 다중상속을 하게 됩니다. 그래서 보통 Mixin 에는 attribute 보다는 method 확장이 주로 사용이 됩니다.

실제로 위 코드의 _ContextMethodMixin, _HookMixin 메서드 확장을 위해서 사용이 되고 있습니다.

믹스인에 대해서 보충 설명을 하자면, ≪오브젝트≫ 에서는 이렇게 정의를 하고 있습니다.

믹스인(mixin)은 객체를 생성할 때 코드 일부를 클래스 안에 섞어 넣어 재사용하는 기법을 가리키는 용어다. 합성이 실행 시점에 객체를 조합하는 재사용 방법이라면 믹스인은 컴파일 시점에 필요한 코드 조각을 조합하는 재사용방법이다.

  • 04 믹스인 중에서

다시 본론으로 돌아와서, Function 클래스의 내부를 살펴보겠습니다.

class Function(with_metaclass(FunctionMeta, _C._FunctionBase, _ContextMethodMixin, _HookMixin)):
    ...

    # for the tracer
    is_traceable = False

    @staticmethod
    def forward(ctx: Any, *args: Any, **kwargs: Any) -> Any:
        raise NotImplementedError("You must implement the forward function for custom"
                                  " autograd.Function.")

    @staticmethod
    def backward(ctx: Any, *grad_outputs: Any) -> Any:
        raise NotImplementedError("You must implement the backward function for custom"
                                  " autograd.Function.")

위와 같이 forward 그리고 backward 를 정의하도록 가이드하면서, 추상클래스로서의 역할을 하고 있는 것을 확인할 수 있습니다. 코드 내에는 공식적으로 InplaceFuction 그리고 NestedIOFunction 만 작성되어 있기는 하지만, 처음 예시로 봤던 LinearFunction 처럼 수 많은 연산로직들이 이 Function 의 정의된 규격을 따라가면 재사용이 가능함을 알 수 있습니다.

images

Module 에게 주어진 책임 예시 (GPU 할당 / 입력 계산)

일관된 객체들 간의 협력이 요구되는 프레임워크이기 때문에, 많은 연산의 기본이 되는 Function 클래스를 확인할 수 있었습니다. 다음으로는 PyTorch를 사용하신 분들은 친숙하게 느끼실 torch.nn 입니다.

torch.nn

여기부터는 순수 Python으로 코드가 구성되어 있습니다. 수 많은 코드들 중에서 살펴보려고 하는 것은 가장 기본이 되는 Module 클래스입니다. Python으로 전체가 구성되어 있는 것만큼, 여기에서는 모든 코드들이 typing 을 통해서 자료형이 모두 명시되어 있습니다.

images

Module 에게 주어진 책임 예시 (GPU 할당 / 입력 계산)

먼저 주어진 책임을 살펴보겠습니다.

  • 할당된 Tensor 관리 (Parameters, Buffer)
    • GPU 할당, 타입 변환, state_dict 저장 및 로드
  • Forward: 주어진 입력을 계산해서 반환
    • Forward 연산 및 Backward 등록

위의 책임에 따라서 필요한 속성들은 다음과 같이 확인할 수 있습니다.
(여기에서는 객체지향애 관련된 문법들이 포함되어 있기도 합니다. Python 에서 일반적으로 변수이름 은 public, _변수이름 은 private를 의미하게 되죠.)

  • training: (bool) 학습 모드 여부
  • _parameters : (OrderedDict) Learnable Parameters
  • _buffers : (OrderedDict) module 에서 사용은 되나, Parameter 는 아닌 경우 (persistent, non-persistent)
  • _non_persistent_buffers_set : (OrderedDict) persistent 의 여부를 관리하는 자료구조
  • _backward_hooks : (OrderedDict) Tensor에도 있었던 register_hook 과 같은 로직으로, backward 에 사용
  • _forward_hooks : (OrderedDict) Tensor에도 있었던 register_hook 과 같은 로직으로, forward 에 사용
  • _forward_pre_hooks : (OrderedDict)Tensor에도 있었던 register_hook 과 같은 로직으로, forward 전에 사용
  • _state_dict_hooks : (OrderedDict) 모듈의 state_dict를 만들 때, hook 로직 사용
  • _load_state_dict_pre_hooks : (OrderedDict) 모듈의 state_dict를 기반으로 load 할때, 로드 전 hook 로직 사용
  • _modules : (OrderedDict) 해당 Module 의 자식 Module들을 관리하기 위한 자료구조

위의 속성들을 보면 해당 Module 이 다양한 상황들을 커버하기 위해서 열어놓은 부분들이 눈에 보일 것 입니다. 가장 크게는 hook 이 모든 연산과 심지어는 저장(state_dict())과 로드(load_state_dict()) 에도 들어가고 있습니다. 다양한 세부적인 Module 들이 만들어질 수 있고, 무엇이 어떻게 추가될지 모르기 때문에 위와 같이 열어둔 것으로 이해가 되네요.

hook 에 대해서 코드를 자세히 살펴보기 전에, 먼저 Module의 기본 사용법에 대해서 잠시 이야기를 해보겠습니다. 일반적으로 Module을 새로 정의할 때는 아래와 같이 sub-module 들을 정의하고, 그에 따른 forward 메서드를 정의하면 됩니다.

# code: https://github.com/pytorch/pytorch/blob/v1.6.0/torch/nn/modules/module.py#L169

import torch.nn as nn
import torch.nn.functional as F

class Model(nn.Module):
	  def __init__(self):
	      super(Model, self).__init__()
	      self.conv1 = nn.Conv2d(1, 20, 5)
	      self.conv2 = nn.Conv2d(20, 20, 5)

	  def forward(self, x):
	      x = F.relu(self.conv1(x))
	      return F.relu(self.conv2(x)

이렇게 self.conv1 로 정의를 하면 다음의 메서드가 자연스럽게 호출됩니다. 바로 __setattr__ 입니다.

# code: https://github.com/pytorch/pytorch/blob/v1.6.0/torch/nn/modules/module.py#L774

def __setattr__(self, name: str, value: Union[Tensor, 'Module']) -> None:
    if isinstance(value, Parameter):
        ...
    elif params is not None and name in params:
        ...
    else:
        modules = self.__dict__.get('_modules')
        if isinstance(value, Module):
            ...
        elif modules is not None and name in modules:
            ...
        else:
            buffers = self.__dict__.get('_buffers')

위와 같이 Module 안에 정의된 속성이 어떤 객체 인가에 따라서, Parameter 로서 등록이 될 수도 있고, Module 혹은 Buffer 로도 등록이 될 수 있습니다. 이것 역시 Python의 빌트인(__builtlins__)로 미리 정의되어 있는 내장 함수입니다. Class 가 가지는 기본 속성들을 Module 이라는 Class에 맞는 직관적인 사용법으로 변환시킨 것이죠.

다음으로는 모델이 output 을 만드는 코드를 살펴보겠습니다. 바로 hook 이 연결되어 있는 부분이기도 하죠. 위의 Module은 다음과 같은 방식으로 사용하게 됩니다.

model = Model()
ouptuts = model(inputs)  # Model 의 __call__ 로 연결

위와 같이 model 이 넘겨 받는 부분은 __call__ 이라는 빌트인 함수로 연결이 됩니다.

# code: https://github.com/pytorch/pytorch/blob/v1.6.0/torch/nn/modules/module.py#L710

def _call_impl(self, *input, **kwargs):
    # 1. Forward Pre-hook
    for hook in itertools.chain(
            _global_forward_pre_hooks.values(),
            self._forward_pre_hooks.values()):
        result = hook(self, input)
        if result is not None:
            if not isinstance(result, tuple):
                result = (result,)
            input = result

    # 2. Forward
    if torch._C._get_tracing_state():
        result = self._slow_forward(*input, **kwargs)
    else:
        result = self.forward(*input, **kwargs)  # 우리가 정의하는 forward

    # 3. Forward Hook
    for hook in itertools.chain(
            _global_forward_hooks.values(),
            self._forward_hooks.values()):
        hook_result = hook(self, input, result)
        if hook_result is not None:
            result = hook_result

    # 4. Backward Hook 등록
    if (len(self._backward_hooks) > 0) or (len(_global_backward_hooks) > 0):
        var = result
        while not isinstance(var, torch.Tensor):
            if isinstance(var, dict):
                var = next((v for v in var.values() if isinstance(v, torch.Tensor)))
            else:
                var = var[0]
        grad_fn = var.grad_fn
        if grad_fn is not None:
            for hook in itertools.chain(
                    _global_backward_hooks.values(),
                    self._backward_hooks.values()):
                wrapper = functools.partial(hook, self)
                functools.update_wrapper(wrapper, hook)
                grad_fn.register_hook(wrapper)
    return result

__call__ : Callable[..., Any] = _call_impl

위의 코드를 보면 __call__ 에는 우리가 정의하는 forward 외에도 많은 로직들이 있음을 알 수 있습니다. 각 부분을 나누어서 주석을 추가하였습니다. 내부에서는 생각보다 많은 일들을 하고 있었네요!

Forward Pre-hook → Forward → Forward Hook → Backward Hook 등록 (grad_fn)

처음 예제에서 보신 것처럼, Module 안에는 Sub-Module 들이 정의되고 순차적으로 __call__ 이 호출되게 됩니다. 이때마다 위와 같은 로직이 실행되게 되는 것이죠.

그 외에 참고할 만한 직관적인 코드를 하나 더 살펴보고자 합니다.

    @overload
    def to(self: T, device: Optional[Union[int, device]] = ..., dtype: Optional[Union[dtype, str]] = ...,
           non_blocking: bool = ...) -> T:
        ...

    @overload
    def to(self: T, dtype: Union[dtype, str], non_blocking: bool = ...) -> T:
        ...

    @overload
    def to(self: T, tensor: Tensor, non_blocking: bool = ...) -> T:
        ...

    def to(self, *args, **kwargs):

        device, dtype, non_blocking, convert_to_format = torch._C._nn._parse_to(*args, **kwargs)

        if dtype is not None:
            if not dtype.is_floating_point:
                raise TypeError('nn.Module.to only accepts floating point '
                                'dtypes, but got desired dtype={}'.format(dtype))

        def convert(t):
            if convert_to_format is not None and t.dim() == 4:
                return t.to(device, dtype if t.is_floating_point() else None, non_blocking, memory_format=convert_to_format)
            return t.to(device, dtype if t.is_floating_point() else None, non_blocking)

        return self._apply(convert)

Java 혹은 다른 객체지향 언어들을 사용해봤다면, 위와 같은 overload 는 익숙할 것이라고 생각이 됩니다. 같은 메소드의 이름을 가지고 있으나, 요구하고 있는 파라미터가 다른 경우를 의미합니다. 시그니처가 다르다고 표현을 할 수 있죠.
typing 에서는 위와 같은 문법으로서 기능을 지원합니다. (참고로, typing 은 Python 3.5 부터 지원이 되고 있고, 그 이전 버전들을 위해서 [pip](https://pypi.org/project/typing/) 를 통해서 팩키지를 설치할 수 있습니다.)

다시 module.to(...) 라는 매서드로 돌아가서 보면, device(cpu/gpu), dtype 등의 연상장비 지정, 타입 변환 등을 한꺼번에 다룰 수 있는 모습을 보이고 있습니다. 사용을 하는 입장에서는 다른 것들을 신경쓰지 않고 to 라는 메소드에 원하는 것을 넣기만 하면 되는 것이죠.

끝으로

지금까지 이야기한 것을 간단하게 정리해보겠습니다. 가장 기본이 되는 Tensorautograd.Function 그리고 nn.Module 에 대해서 살펴보았습니다. 데이터 클래스로서 필요한 속성들이 명시되어 있고, 그것을 forward , backward 로 큰 틀을 잡아놓고 수 많은 연산들이 여기에 맞춰서 추가되고 있습니다. 그리고 이 연산들을 하나의 Module 로서 마음껏 조합해서 사용할 수 있도록 준비가 되어있죠.

PyTorch 는 객제치향의 여러가지 특징을 잘 살린 프레임워크라고 생각이 됩니다. 그와 동시에 Python 언어가 가지는 특징들 또한 잘 활용하여 사용자들이 직관적으로 코드를 작성할 수 있도록 돕고 있습니다.

References