2017년 9월 5일 화요일

Python Dynamic Programming (Longest Palindrome)

언어가 뭐시 중헌디, 이겠지만 교육 / 강화학습 관련으로 간만에 Python으로 Dynamic Programming을 짜본 김에 오랜만에 Algorithm정리도 하며 기록을 남김.

* 강화학습(Reinforcement Learning)에서 MDP (Markov Decision Process)에서의 상태 천이는, 이전 State까지의 연산결과 값을 이용하기 때문에 Dynamic Programming Technique과 직접적으로 연관된다.

(벌써 10년도 더 전에 ㅠㅠ C로 복잡스럽게 짰던 기억이 아련히 스쳐간다.)


o 문제 정의
  • Input Text에서 Palindrome의 최대 길이 구하기. Longest Palindrome Substring, Search 등으로 보통 이야기 하는 문제. 
  • Palindrome은 우리말로 '회문'이라 한다. "madam"처럼 앞으로 읽으나, 역순으로 읽으나 똑같은 단어/어절이 되는 문자열을 의미한다.  


o Dynamic Programming
Dynamic Programming로 풀수 있는 문제는 두 가지 특성을 만족한다.
  • Optimal Substructure
    • Subproblem들의 솔루션을 조합하여 Global Optimum을 구할 수 있다. 
    • 즉 이전 단계(Subproblem 1)의 Best솔루션 + 현재 단계(Subproblem 2)의 Best Solution = 다음 단계의 Best 솔루션
  • Overlapping Subproblem
    • 점화식을 가지는 Recursion 구조라 생각하면 쉽다. 대표적으로 피보나치 수열처럼 이전 단계의 Subproblem의 해를 다음 단계를 위해 재귀적으로 계속 재이용한다. 
상기 이유로, DP를 짜기 위해서는 문제를 점화식 구조로 바꿀 수 있어야 한다.  그럼 Palindrome 문제를 점화식 문제로 정의해보자.

o Palindrome 문제의 점화식 (Recurrence Formula)
  • f(text) = ...
    • 0, if n = 0
      • text 길이가 0이면 당연히 longest value = 0
    • 1, if n = 1
      • text 길이가 1이면 당연히 longest value = 1
    • 2 + f(text[2:n-1]), if n>1 and text[1] = text[n]
      • n이 1 이상이고 첫글자와 끝 글자가 같은경우 (예를 들어 "madam" 같은 경우)
      • 일단 길이가 2 이상이니 2 + f(ada) 수행
    • max(f(text[1:n-1]), f(text[2:n]), if n>1 and text[1] != text[n]
      • 첫글자와 끝 글자가 다른 경우, (예를 들어... "abededeaace" 와 같은 경우)
      • 첫글자를 제외한 f(bededeaace), 끝 글자를 제외한 f(abededeaac) 중 큰 값을 취한다. 

o 위 점화식을 그대로 Recursion Version으로 구현해보자. 
def computeLongestPalindrome_recur(text):
    input_len = len(text)
    
    if input_len == 0: 
        return 0
    
    elif input_len == 1: 
        return 1
    
    elif (input_len > 1) and (text[0] == text[-1]): 
        return 2 + computeLongestPalindrome_recur(text[1:input_len-1])
    
    elif (input_len > 1) and (text[0] != text[-1]):
        return max(computeLongestPalindrome_recur(text[:input_len-1]), computeLongestPalindrome_recur(text[1:input_len]))
    
    return 0

이 짧은 몇줄로 구현되는건 신나지만 계산 복잡도가 높기 때문에, text가 조금만 길어져도 답을 구하는게 불가능할 정도로 계산시간이 엄청나게 증가한다.


o DP Version Code 
Optimal Substructure, Overlapping Subproblem 특성을 이용하려면 이를 어딘가에 저장해두고 재이용하면서 점진적으로 다음 step을 계산해 나가야 한다.

C, C++에서는 전역적으로 이 Overlapping subprogram 결과물을 저장할 변수를 array로 미리 정의하여 잘 만들어두고 index로 access 해야 한다. 하지만 Python에서는 collection의 defaultdict를 쓰면 이 과정이 너무너무x100 간단하게 해결된다. (사랑해요 파이썬 -.-)

각 위의 각 step에서 계산 결과물을 저장할 공간을 두는 것 외에는 코드가 거의 똑같다.

from collections import defaultdict
memory = defaultdict(int)

def computeLongestPalindrome(text):
    if text in memory: 
        return memory[text]
    else:
        text_length = len(text)

        if text_length == 0:
            return 0

        elif text_length == 1:
            return 1

        else:
            if text[0] == text[-1]:
                memory[text] = 2 + computeLongestPalindrome(text[1:-1])
            else:
                memory[text] = max([
                    computeLongestPalindrome(text[:-1]), 
                    computeLongestPalindrome(text[1:])
                ])
            return memory[text]

약간만 긴 string으로 두 버전을 테스트해보면 어마어마한 속도차이를 체감할 수 있을 것이다.


2017년 8월 12일 토요일

Hive에서 GROUP_CONCAT 함수 사용하기

A. 원본 테이블
지역 업종
경기도 요식업
경기도 헬스
서울 한식
서울 학원
강원도 초등학교
강원도 커피
...
제주도 제과점

B. 변환된 결과
지역 업종
경기도 요식업, 헬스, …
서울 한식, 학원, …
강원도 초등학교, 커피, …
제주도 제과점, …

A처럼 생긴 테이블을 B와 같은 형태로 바꿀 필요가 있었다. 
일종의 텍스트 피벗이라 해야 하나(?) 

원본이 800만줄이 넘는 양이라 Hive Query 안에서 처리하고 싶어 쿼리를 복잡스럽게 짜보다가(!)
뭔가 UDF라도 있을 것 같아 재미있는 아이를 발견. 

우선, MySQL에서는 GROUP_CONCAT이라는 아이가 있었는데 이렇게 생겼다. 

GROUP_CONCAT([DISTINCT] expr [,expr ...] [ORDER BY {unsigned_integer | col_name | expr} [ASC | DESC] [,col_name ...]] [SEPARATOR str_val])

MySQL기준으로 A -> B를 해보면... 이렇게 짧게 끝난다. 

mysql> select 지역, GROUP_CONCAT (업종) from table_A GROUP BY 지역; 


Hive에서는 약간 다른데 - 
CONCAT_WS, COLLECT_SET 뭐 이런 아이들을 조합해야 한다.

hive> select 지역, CONCAT_WS(',', COLLECT_SET(업종)) from table_A GROUP BY 지역



2017년 8월 11일 금요일

Mac에서의 docker troubleshooting

Mac OS에서 종종 맞닥뜨리는 docker관련 에러들에 대한 troubleshooting을 차차 업데이트 / 정리할 예정.

1. docker container를 시작할 수 없는 경우 
(Error message) docker: Cannot connect to the Docker daemon. Is the docker daemon running on this host?.
See 'docker run --help'.

커맨드 라인에서 다음 명령어 실행
> eval "$(docker-machine env default)"

1.1) 위 명령어에서 또 에러가 나는 경우
(Error message) Error checking TLS connection: Host is not running

커맨드 라인에서 다음 명령어로 TLS Cert를 재생성
> docker-machine regenerate-certs default

1.1.1) TLS Cert 생성이 에러 나는 경우: 아예 docker default machine을 재생성해주자.
> docker-machine rm default
> docker-machine create --driver virtualbox default

1.1.2) 그래도 에러가 나면...
Virtual Box를 직접 실행하고 떠 있는 아이 (Running)는 reset.
다시 1.1.1 실행.

(to be updated)

2017년 3월 25일 토요일

제로카 셰어링 8개월 운영기

* 관련 지난 포스팅
1. 제로카 셰어링 당첨, 그리고 차량 인수
2. 제로카 셰어링 한달 사용기


제로카 1기에 작년 (2016년) 8월에 당첨된 이후 지금까지의 운영기록을 돌아보며...

1. 그래서, '제로카' 운영하며 차량 관련 비용은 얼마나 냈나?
1-1. 제로카 앱에서 확인할 수 있는 청구서 내역.
위에 밝힌바와 같이 2016년 8월부터 2017년 3월 현재까지 제로카를 운영중이며 동기간 동안의 청구서가 발행되어 있는 것을 확인할 수 있다.



1-2. 상세 요금 청구서 내역
12월~2월 동안의 청구서 상세 내역만 가져와 봤다. 아래에서 확인할 수 있듯이 제법(?) 납입금 제로를 달성하고 있다.


2. 셰어링과 수익 Share는 어떻게 운영되나? 
2-1. 앱에서 셰어링을 설정하고 쏘카 고객 (쏘카에서는 이를 '쏘친'이라 부른다.)에 의한 차량 예약이 발생하면 아래와 같이 앱에 나타난다. (쏘친예약 = 1건)

2-2. 셰어링 내역들을 모아보면 이런식. 아래 각 그룹들은 내가 셰어링을 설정한 Time Slot들이며, 해당 Time Slot내에서 대여가 발생하면 아래처럼 나타나게 된다.



2-3. 한개 Time Slot을 상세히 보자. '쏘친'이 언제 빌려가고 반납했는지, 그리고 그 대여건으로 수익 Sharing이 얼마나 발생했는지 확인할 수 있다.


3. 그래서 나는 어떻게 차를 쓰고 셰어링 하고 있나?  
평일에는 도심으로 대중교통을 이용하여 출퇴근 하기 때문에, 위의 3월 실적에서도 확인할 수 있듯이 (총 셰어링시간 26일 17시간 / 1개월) 주중에는 차를 탈일이 거의 없다. 때문에 주말에 차를 이용한 뒤, 일요일 오후 7시 ~ 그 다음주 일요일 오전 10시 정도까지 셰어링을 풀로 열어두는 편이다. 
한줄 댓글, 차량 이용/반납 패턴 등에서 유추해보면, 이렇게 상당히 장기로 셰어링을 하기 때문에 인근 주민 (아파트 주차장 특성상 주민들이 편리하게 이용하고 있다고 예상하고 있다.) 들은 단지 내에 왠만하면 필요할 때 이용할 수 있는 차량이 있다는 것을 점차 인지하고 상대적으로 자주 이용하는 것으로 생각된다. 

4. 총평과 소회
차량구매를 고민하던 시점에 제로카 셰어링에 선정되어 8개월째 이용하면서 대단히 경제적으로 차량을 이용해 오고 있다. 하지만 유무형의 비용도 적잖게 소요된다. 차를 남에게 빌려줌으로써 생기는 각종 불편함과 예상치 못한 일들도 - 오주차, 사고, 실내외 오염, 주유, 등등등 - 정말 빈번하게 발생한다.  

개인적으로는 공유경제의 개념에 많이 공감하며 공유 경제 서비스들이 확산되기를 바란다.  
MIT에서 뉴욕의 데이터로 연구한 결과에 따르면 13,000대에 가까운 뉴욕의 택시는 3,000대 가량의 Riding Share Car로 완전히 대체 가능하다고 한다. (http://www.theverge.com/2017/1/2/14147286/mit-research-nyc-taxi-carpool-uber-lyft)
또 다른 연구에 따르면 미국내 차량은 하루 중 95% 정도의 시간이 주차 상태로 공간만 차지하고 있다고 한다. 

한국도 크게 다르지 않을 것이다.  더욱이 미국보다 대중교통이 잘되어 있으니.
땅덩이도 좁고, 넉넉한 천연자원하나 없으며, 전국민의 50%가량이 수도권에 밀집되어 살고 있고, 요즈음 미세먼지도 심각한 우리나라야 말로 Riding Share Car와 Car Sharing 서비스가 널리 도입되어야 한다고 생각한다. 

하지만 짧지 않은 제로카 운영 경험으로 느낀 것은 아직은 모두가 원활하게 공유 경제에 참여하기에는 일부의 의식 수준이 형편없다는 사실이다. 공유물을 이용하는 이들의 의식 수준 향상과 함께 / 또는 공유 플랫폼 상에서 참여자들을 적절히 제어하고 보상할 수 있는 보완책과 함께 이런 류의 서비스가 많이 확산되어 한정적 자원이 한정적인 공간과 시간안에서 효율적으로 공유되길 바란다. 
지구를 위해, 환경을 위해.

2017년 3월 14일 화요일

박근혜 탄핵 결정문 전문 Word2Vec Visualization w/Tensorflow


https://www.lawtimes.co.kr/Legal-News/Legal-News-View…
헌법재판소의 박근혜 탄핵 결정문 전문을 이용하여,
gensim으로 word2vec model을 만들고 Tensorflow의 embedding projector에 올려봤다.

탄핵 결정문을 읽어보다가 이정미 재판관이 낭독한 판결문과는 다가오는 느낌이 또 달라서, 
- 낭독버전과는 달리 세월호를 비롯하여 불성실한 직무 수행과 관련하여 구체적으로 짚어간다 - 
겸사겸사 word2vec과 Tensorboard를 사용해 보기로 하고 konlpy를 이용한 tokenizing과 간단한 수준의 전처리만 적용하고 돌려봤다.
전처리와 파라미터 튜닝에 시간을 더 쓰면 좀 더 깔끔하게 볼 수 있지 않았을까 싶지만 뭐 간단히만..

'머리'와 '손질', 그리고 그 아래로는 '탈출', 우측으로는 '출입문' 등등이 있는게 참 씁쓸하다.

아래는 Jupyter notebook에서 python으로 export시킨 코드. 
수행 후 tensorboard를 띄우면 됨.

# coding: utf-8

# 

# #### Gensim model building

# In[98]:

# Import required modules
import gensim, codecs
import re

# In[99]:

sentences_vocab = []
for line in codecs.open('impeachment_sentence.txt', encoding='utf-8'):
    sentences = [word+'다.' for word in line.split('다. ') if word!='\n']
    for sentence in sentences:
        sentence = re.sub('\\n다.' , '' ,sentence)
        sentence = re.sub('\\u3000.' , '' ,sentence)
        sentences_vocab.append(sentence)   

# In[101]:

from konlpy.tag import Twitter
pos_tagger = Twitter()

def tokenize(doc):
    return ['/'.join(t) for t in pos_tagger.pos(doc, norm=True, stem=True) ]

# In[102]:

tokenize('전국경제인연합회(다음부터 \'전경련\'이라 한다)가 주도하여 만든 것으로 알려져있던')

# In[103]:

# Preprocessing
sentences_vocab = []
for line in codecs.open('impeachment_sentence.txt', encoding='utf-8'):
    sentences = [word+'다.' for word in line.split('다. ') if word!='\n']
    for sentence in sentences:
        sentences_vocab.append(tokenize(sentence))

# In[104]:

sentences_vocab[41]

# In[105]:

import multiprocessing 
config = { 'min_count': 1, 
          'size': 300, 
          'sg': 1, 
          'batch_words': 10, 
          'iter': 100, 
          'workers': multiprocessing.cpu_count(), } 

model = gensim.models.Word2Vec(**config)

# In[106]:

model.build_vocab(sentences_vocab)

# In[140]:

num_w2v = len(model.wv.index2word)
print (num_w2v)

# In[141]:

import numpy as np
w2v = np.zeros((num_w2v,300))
with open("./projector/metadata.tsv", 'w+') as file_metadata:
    for i,word in enumerate(model.wv.index2word):
        w2v[i] = model[word]
        file_metadata.write(word + '\n')

# #### Tensorflow Visualization

# In[134]:

import tensorflow as tf
from tensorflow.contrib.tensorboard.plugins import projector
import numpy as np

# setup a TensorFlow session
tf.reset_default_graph()
sess = tf.InteractiveSession()

X = tf.Variable([0.0], name='embedding')
place = tf.placeholder(tf.float32, shape=[None, 300])
set_x = tf.assign(X, place, validate_shape=False)

sess.run(tf.global_variables_initializer())
sess.run(set_x, feed_dict={place: w2v})

# create a TensorFlow summary writer
summary_writer = tf.summary.FileWriter('projector', sess.graph)
config = projector.ProjectorConfig()
embedding_conf = config.embeddings.add()
embedding_conf.tensor_name = 'embedding:0'
embedding_conf.metadata_path = './projector/metadata.tsv' 
projector.visualize_embeddings(summary_writer, config)

# save the model
saver = tf.train.Saver()
saver.save(sess, './projector/model.ckpt')
sess.close()