Jul 1, 2018 - Similar Item Name Integration

이번 글은 정보 전달보다는 고민 토로가 주된 내용이다. 팀 과제는 TV홈쇼핑 방송 매출을 예측하는 것이다. 한달/하루 매출이 아니라 각 방송 시간마다 얼마가 나올지를. 그래서 시계열 모델을 쓰지 않고 있다.

feature engineering

시계열이 아니라면 어떤 feature를 써야하는가를 심각하게 고민할 수 밖에 없다. 우리가 필요한 주요 변수는 방송에서 어떤 상품을 파느냐, 어떤 환경에서 방송을 하느냐가 아닐까 생각한다. 그런데 이런 심오한 내용을 어떤 데이터가 담아낼 수 있을까? 매우 고민이다…

similar text score

데이터에서 눈에 띄게 보이는 점은 매우 짧다는… 것이다. 어떤 상품에 대해 예측을 한다면 레코드가 10건 정도는 있기를 바라지만, 내가 보는 데이터는 평균 3건 뿐이다. classification 문제에서 class마다 데이터 3건이 있으면 어떤 분류기도 제대로 성능을 낼 수 없을 것이다.

데이터를 길게 만들기 위해 유사한 상품명을 묶어주기로 했다. [원더브라 10차, 11차, 12차]처럼 시리즈? 상품이거나 [면 블라우스/코튼 블라우스]와 같이 단어 차이만 있는 상품도 꽤 있기 때문에 이들을 한 class로 묶어주어도 무방할 것으로 보인다.

유사도의 기준은 Jaro-winkler 스코어를 사용했다. 상품 텍스트는 보통 이런 패턴이다. [브랜드] (상품 특징) (본 상품) (추가 구성) Jaro-winkler의 특징은 앞의 글자가 비슷하면 뒤쪽이 달라도 점수가 높은 경향이 있다. (그래서 검색엔진에서 첫 글자를 틀리면 오타 보정이 잘 안된다) 이 특성을 활용하기 위해 상품 텍스트에서 브랜드를 제거하여 (상품 특징) (본 상품) (추가 구성) 상품의 본질이 유사한 것끼리 묶이도록 유도했다.

text grouping by Jaro-winkler score

상품 1만 개를 $\binom{N}{2}$개의 쌍으로 묶고 Jaro-winkler 점수를 구한 다음 그룹핑을 해주는 것도 일이었다.

  1. 그룹핑 기준은 일일이 눈으로 확인하며(…) 적당히 잘 묶이는 수치를 사용했고
  2. 긴 상품명이 짧은 상품명으로 묶이도록 하여 결과적으로 군더더기 없이 간결한 상품명으로 정리되도록 했다.
  3. 이렇게 묶인 상품명이 그대로 피처로 쓰이진 않았고 파생 변수를 만드는데 중요한 역할을 했다.

회사 업무를 소스 없이 짧은 글로만 설명하기는 많이 어렵다는 걸 느끼며… 추후 소스를 짧게 덧붙여 나라도 알아볼 수 있게 만들어야겠다.

Jun 16, 2018 - Build Sales Simulator

요즘 예측 모델 + 시뮬레이터 UI를 만드는데 한창이다. 최근 몇 주간 건강이 좋지 않아 업무에 집중하지 못했는데, 이번 주는 예측 데이터 마트를 구축하느라 자잘한 이슈가 많아 두뇌가 강제로 활성화됐다. 그래서 지금 이 기록을 남겨야만 한다는 의무감이 든다.

step 1. predictive model

홈쇼핑 회사는 이런 질문이 있다. 쇼핑호스트를 바꾸면 매출이 얼마나 오를까? 이 아이템은 언제 팔아야 가장 잘 팔릴까? 프로모션을 걸면 얼마나 효과가 있을까?
이러한 질문에 답을 해줄 수 있는 예측 모형을 만드는 프로젝트를 선행했다. 몇가지 기억에 남는 점은 1. (사실 매출을 예측하려면 필요한 데이터가 포함이 됐다고 생각하지는 않지만) 회사에 있는 거의 모든 데이터를 긁어와서 해봐도 충분한 데이터를 얻기 어려웠다. 2. 데이터에서 그 어떤 패턴도 쉽게 보이지 않는다. (정말 noisy하다) 3. 아무리 좋다는 모델을 써도 효과가 없어서 흔한 boosting 알고리즘을 사용했다. 4. 그래도 현재 현업에서 as-is로 사용한 예측값보단 낫다.

step 2. web developement

웹을 통해 시뮬레이션 서비스를 제공하기에 웹 개발은 필수. 예측 모형이 python으로 짜여있어 flask로 간단한 UI를 구성했다. 사실 이 파트는 내 담당이 아니라 잘 모르겠다.

step 3. data mart!

실시간 시뮬레이션 서비스를 위해 매일 데이터와 모델을 갱신하고 test 데이터를 만들어줘야한다. 이 부분이 참 말로는 쉽지만 실제 액션은 절대 그렇지가 않다는 걸 뼈저리게 느꼈다. (그래서 소제목에 느낌표를 붙였다!)

  1. test 셋을 만드는 건 train보다 어렵다. train 셋은 과거의 값이니 주어진 데이터를 그대로 사용하면 된다. 반면 test 셋은 미래의 데이터이니 사내 데이터의 대부분을 차지하는 집계치를 사용할 수 없고 네이버트렌드처럼 시계열 데이터를 가져다 쓸 수 없다.
  2. 자동화 코드를 만드는 것도 데이터 수작업 못지 않게 일이다. 먼저 DB에서 데이터를 가져오는 etl 과정, python에서 돌아가는 복잡한 데이터 전처리 과정의 예외 처리가 까다로웠다. (전처리 과정이 많다보니 기상천외한 에러도 있었다.) DB/python/외부API 3군데를 돌아다니며 데이터가 쌓이다보니 로깅 작업도 꼼꼼히 하고 나서야 실제 로직을 원활히 테스트할 수 있었고 이후로 1~2주 정도 수정하는 기간이 필요했다.
  3. 친숙하지 않은 DB와 Linux. SQLD 취득, 웹개발 경험도 실무에서 큰 도움은 되지 못했다. 언제나 그렇듯 개발 과정은 구글링 시간이 절반 정도 되는 것 같다. python 콘솔에서 보이는 에러를 잡기 위해 무엇을 해야하는지 구글링을 하며 mysql 설정, 테이블 설정, crontab 설정등등 DB와 server os를 반드시 만져야 했다. 시스템을 구성한다는 건 개발자로서의 능력도 요구하기에 녹록하지가 않았다.

May 20, 2018 - Python simple etl using pandas

파이썬 쓰임새가 점차 넓어지다.

실전 프로젝트에서 사용하면서 배워나가고 있는 만큼 깊이는 부족하지만 무엇을 하던 쉽게 해낼 수 있다는 것만큼은 확연히 느꼈다.

최근 데이터분석용 데이터마트를 구축하며 기존 oracle 데이터허브에서 분석서버의 mariaDB로 옮기는 일이 있었다. 최근 파이썬을 많이 쓰다보니 자연히 파이썬으로 짜게 됐는데 너무 간단히 소스를 짤 수 있어서 당황스러웠다…

docplex 라이브러리에서도 pandas 덕을 많이 봤는데 이번에도 역시 pandas가 큰 일을 했다. 먼저 oracle에 붙어줄 cx_oracle과 mysql용 sqlalchemy가 필요하다. sqlalchemy는 mongodb용 mongoose와 유사하게 클래스 형태로 데이터를 조작할 수 있는 기능이 있지만 다소 번거로워 db connect 기능만 사용했다.

engine = create_engine("mysql+pymysql://~~~~", encoding='utf-8')
connection = engine.connect()

db connect 객체 생성 후, sql 쿼리를 파일로 읽고 pandas로 정말 쉽게 끝낼 수 있다.

sql_file = open('./query.sql', 'r')
query = sql_file.read()
sql_file.close()

df = pd.read_sql(query, con=oracle_connection, params={'date_to_extract': Date})
df.to_sql(name='TABLE', con=mysql_connection, if_exists='append', index=False)

로깅 모듈

혹시 발생할지 모르는 DB에러를 잡기 위해 로그 저장 기능을 만들기는 다소 어려웠다. 여러 방법이 있겠지만 가장 보기에 깔끔하고 기능적으로도 괜찮은 건 이 정도가 아닐까 싶다. 다만 사전에 log설정 파일을 작성해야한다.

except exc.SQLAlchemyError as exec:
    logging.config.fileConfig('./log/log.config.ini')
    logger = logging.getLogger()
    logger.debug(exec)
[loggers]
keys=root

[handlers]
keys=stream_handler, file_handler

[formatters]
keys=formatter

[logger_root]
level=DEBUG
handlers=stream_handler, file_handler

[handler_stream_handler]
class=StreamHandler
formatter=formatter
args=(sys.stderr,)

[handler_file_handler]
class=handlers.RotatingFileHandler
args=('./log/etl.log','a',10 * 1024 * 1024, 10)
formatter=formatter

[formatter_formatter]
format=%(asctime)-12s %(name)-4s [%(module)s:%(lineno)-4s]  %(message)s
datefmt='%Y-%m-%d %I:%M:%S %p'

pandas의 기능에 감사하게 되고 점점 개발자스러워지는 걸 느끼게 됐다. 다음엔 네이버 트렌드도 활용하고 hadoop에 저장된 웹행동 데이터도 긁어올 예정이니 점점 이런 작업에 익숙해지겠지. 그러면 또 정리해서 올리자.

그리고 아직은 글 하나 올리는데 30분씩 걸리지만, 점차 빨라지겠지.