며칠 전에 올렸던 “내 이름 어때?”를 만들면서 썼던 여러 가지 기술적인 부분에 대해서
간단하게 정리해 봅니다. 물론 django로 만들었습니다! 이히히
Django 템플릿에서 한글 조사 처리
이름 뒤에 은/는 이/가 같은 것들을 제대로 붙이려면 아무래도 템플릿에서 처리를 해 줘야하는데,
django에서는 애플리케이션에서 직접 템플릿 태그나 필터를 정의하는 걸 매우 장려하는 분위기라서
“필터”를 따로 정의해서 처리했습니다.
템플릿에서 이렇게 쓰려고 하는 부분이 있다면:
|
<span class=“x”>이름을 뒤집으면 </span><span class=“cp”>{{</span> <span class=“nv”>rev_last_name</span><span class=“o”>|</span><span class=“nf”>attach_korean_suffix</span><span class=“s2”>:"이가"</span> <span class=“cp”>}}</span><span class=“x”></span>
<span class=“x”>되어서..</span>
|
필터 정의를 이렇게 해 줬습니다.
|
<span class=“nd”>@register</span><span class=“o”>.</span><span class=“n”>filter</span>
<span class=“k”>def</span> <span class=“nf”>attach_korean_suffix</span><span class=“p”>(</span><span class=“n”>string</span><span class=“p”>,</span> <span class=“n”>suffixes</span><span class=“p”>):</span>
<span class=“k”>if</span> <span class=“n”>hangul</span><span class=“o”>.</span><span class=“n”>ishangul</span><span class=“p”>(</span><span class=“n”>string</span><span class=“p”>[</span><span class=“o”>–</span><span class=“mf”>1</span><span class=“p”>])</span> <span class=“ow”>and</span> <span class=“n”>hangul</span><span class=“o”>.</span><span class=“n”>split</span><span class=“p”>(</span><span class=“n”>string</span><span class=“p”>[</span><span class=“o”>–</span><span class=“mf”>1</span><span class=“p”>])[</span><span class=“mf”>2</span><span class=“p”>]:</span>
<span class=“k”>return</span> <span class=“n”>suffixes</span><span class=“p”>[</span><span class=“mf”>0</span><span class=“p”>]</span>
<span class=“k”>else</span><span class=“p”>:</span>
<span class=“k”>return</span> <span class=“n”>suffixes</span><span class=“p”>[</span><span class=“mf”>1</span><span class=“p”>:]</span>
|
마지막 줄에서 1:로 굳이 잡아준 이유는 이름 뒤의 ~이 처럼 받침이 없으면 끝에 안 붙는 경우도
처리해 주려고요..
추세 해석
이름의 인기가 늘고 있는지 줄어드는지를 글자로 판단해서 표현해 주기 위해서, 간단한 계산식을
사용했습니다. 우선 원 데이터 자체는 샘플수가 적어서 노이즈가 많기 때문에 보통 많이 쓰이는
9개 윈도우 평균으로 했고, 이렇게 하면 18개 포인트가 나와서 세 부분으로 나눠서
앞 중간 뒤의 평균을 다시 구해서 3가지 값이 나왔습니다. 그래서 눈으로 딱 보면 값이 계속
증가하는지, 올라갔다 내려갔다 하는지를 볼 수 있는데요, 그냥 값으로 볼 수는 없으니
앞/중간 과 중간/뒤의 각각의 변동폭을 0에서 1사이로 정량화해서 봤습니다. 변동폭은 이름마다
절대량이 다르기 때문에 상대량으로 비교해야해서 아래와 같은 식으로 썼습니다.
보기엔 약간 쓸데없이 복잡하긴 하지만, 그냥 상대비율을 (0, 1) 사이로 넣어주는 일 밖에 안 합니다;;;
이렇게 나온 값으로 앞 뒤가 모두 (0.4, 0.6) 구간에 들어오면 “꾸준한 추세입니다.”라고 하거나,
앞-중간은 (0.0, 0.3), 중간-뒤는 (0.0, 0.5) 구간에 들어가면 앞 반쪽에서 감소세가 강하고
뒷 반쪽에서 감소세가 둔하다는 의미이므로 “확 줄어들다가 잦아드는 추세입니다.”라고 보여주는 식으로
주된 패턴들을 “대충” 느낌으로 나열하는 방법으로 코딩했습니다. 크흐;
구글 차트
이름 전체의 성별 성향이나 이름의 시대적 경향, 이름 글자의 시대적 경향을 보이는 부분에서
구글 차트를 불러서 사용했습니다. 구글 차트는 직접 URL을 코딩하는 방법은 아니고,
pygooglechart를 사용했는데요,
이게 의외로 그런대로 잘 만들어서 웬만한 기능은 불편없이 쓸 수 있게 돼 있더군요. ?
다만, 하나 기술적인 문제가 있었던 부분은 이름 글자의 시대적 경향 같은 경우에는
글자마다 실수값 18개씩(경향)이 저장돼야 하기 때문에, 이걸 그냥 저장하는 건 여러모로
번거롭고 해서 구글 차트 API에서 쓰는 0~4095 사이 인코딩하는 방법으로 썼습니다.
(base64와 거의 같은 방법입니다.) 그래서, 저장은 바로 구글 차트 API URL에 쓰면 되는
형태가 돼서 다시 불러올 때 매우 빠르게 불러올 수 있긴 한데, 문제는 한 이름 안에
이름 앞자와 뒷자의 경향을 모두 보여줘야하기 때문에 둘의 그래프 크기를 제대로 조절해 주지
않으면 각 글자의 크기가 잘못 나온다는 점이었습니다.
그래서 결국 선택한 방법은 보여줄 때 앞자 뒷자 인코딩된 값을 다시 풀어서 큰 쪽의 스케일로
맞춘 다음에 다시 인코딩하는 -.,-; 약간 노가다성 방법을 썼습니다. 역시 이런 부분은
numpy의 array의 도움을 많이 받을 수 있었습니다.
확장코드 인코딩/디코딩 부분을 따로 떼서 쓸 일이 좀 있을 것 같아서..
|
<span class=“n”>encmap</span> <span class=“o”>=</span> <span class=“s”>'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-.'</span>
<span class=“n”>decmap</span> <span class=“o”>=</span> <span class=“p”>[(</span><span class=“n”>encmap</span><span class=“o”>.</span><span class=“n”>index</span><span class=“p”>(</span><span class=“nb”>chr</span><span class=“p”>(</span><span class=“n”>c</span><span class=“p”>))</span> <span class=“k”>if</span> <span class=“nb”>chr</span><span class=“p”>(</span><span class=“n”>c</span><span class=“p”>)</span> <span class=“ow”>in</span> <span class=“n”>encmap</span> <span class=“k”>else</span> <span class=“mf”>0</span><span class=“p”>)</span> <span class=“k”>for</span> <span class=“n”>c</span> <span class=“ow”>in</span> <span class=“nb”>range</span><span class=“p”>(</span><span class=“mf”>128</span><span class=“p”>)]</span>
<span class=“k”>def</span> <span class=“nf”>decode_ggenc</span><span class=“p”>(</span><span class=“n”>encoded</span><span class=“p”>):</span>
<span class=“k”>return</span> <span class=“p”>[</span><span class=“n”>decmap</span><span class=“p”>[</span><span class=“nb”>ord</span><span class=“p”>(</span><span class=“n”>hi</span><span class=“p”>)]</span> <span class=“o”>*</span> <span class=“mf”>64</span> <span class=“o”>+</span> <span class=“n”>decmap</span><span class=“p”>[</span><span class=“nb”>ord</span><span class=“p”>(</span><span class=“n”>lo</span><span class=“p”>)]</span>
<span class=“k”>for</span> <span class=“n”>hi</span><span class=“p”>,</span> <span class=“n”>lo</span> <span class=“ow”>in</span> <span class=“nb”>zip</span><span class=“p”>(</span><span class=“n”>encoded</span><span class=“p”>[::</span><span class=“mf”>2</span><span class=“p”>],</span> <span class=“n”>encoded</span><span class=“p”>[</span><span class=“mf”>1</span><span class=“p”>::</span><span class=“mf”>2</span><span class=“p”>])]</span>
<span class=“k”>def</span> <span class=“nf”>encode_ggenc</span><span class=“p”>(</span><span class=“n”>data</span><span class=“p”>):</span>
<span class=“k”>return</span> <span class=“s”>''</span><span class=”o”>.</span><span class=”n”>join</span><span class=”p”>(</span><span class=”n”>encmap</span><span class=”p”>[</span><span class=”nb”>int</span><span class=”p”>(</span><span class=”n”>v</span><span class=”o”>//</span><span class=”mf”>64</span><span class=”p”>)]</span><span class=”o”>+</span><span class=”n”>encmap</span><span class=”p”>[</span><span class=”n”>v</span><span class=”o”>%</span><span class=”mf”>64</span><span class=”p”>]</span> <span class=”k”>for</span> <span class=”n”>v</span> <span class=”ow”>in</span> <span class=”n”>data</span><span class=”p”>)</span>
|
통계치가 적은 이름의 성별 추정
역시 통계 샘플 크기가 작아서 주요 이름들을 빼고는 제대로 된 통계치를 낼 수 없어서
주요 이름들의 성별 경향으로 학습한 걸로 예측하는 부분이 필요했습니다.
이번에는 아예 사용된 적 없는 글자까지도 어떻게 좀 해 보려고
통계치에 전혀 의존하지 않고 그냥 자소별로 분해해서 이름만 피처로 사용하기로 했습니다.
그래서 보통 SVM을
쓰는 것이 여러모로 대세이기는 하지만, 카테고리성(이산) 피처값에 매우 유리한
random forest을 썼습니다.
(물론 제가 수학을 워낙 못하는 것도 큰 요인으로;;;;)
Random forest는 아무래도 쓸 수 있는 구현이 적다는 게 큰 문제인데요.
파이썬에서 쓸 수 있는 orange를 쓰면 정말
좋겠지만, 아쉽게도 이 구현은 리그레션은 지원하지 않고요. Y.Y
R용 패키지인
party와
randomForest
중에 선택해야하는데, party를 먼저 했으나 메모리 3기가를 먹더니 죽었고 (-_-)
randomForest는 안정적으로 대략 200메가 정도 먹고 그런대로 쓸 만한 결과를 줬습니다. ?
학습 기법 측면에서는 남자 샘플이 2배 정도 되기 때문에 편향 문제가 있어서 샘플링 조절을
좀 해야했는데요, 그냥 복잡한 것 쓰지 않고 대략 0.3 밑을 반 다운 샘플링하니까 전체적으로
분포가 윗쪽하고 아랫쪽이 그런대로 맞았습니다. 중성적인 이름이 수가 훨씬 적은 것도 또한
중성적 이름 쪽에서 오차를 많이 발생시킬 수 있는 요인이 될 수 있는데, 이쪽에서 오버샘플링을
하려고 하다가 “될 거 같으면 대충해도 돼야 하는거지” 하는 교수님 말씀이 귓가를 스치며
놀이인데 대충하자 하고 -ㅇ-;; 크흐; 그래서 결국 10-fold cross validation으로
평균 피어슨 연관성이 0.97 정도 나왔습니다. (만… 역시 사람 느낌하고 좀 다른
사례가 개별적으로는 제법 많이 발견되긴 하네요;)
페이지 내용 캐시
서비스를 공개한 다음 날 점심시간이 좀 지나고 나서는 접속이 폭주해서, 실시간 계산이 상당히
있었던 구현 특성상 앞으로 어떻게 될 지 참 고민이 있었는데요; 그래서 마침 전혀 필요없겠다
싶어서 꺼놨던 django의 캐시 프레임워크를
살려서 해 봤습니다.
백엔드를 선택할 수 있는데, 역시 제일 잘 나가는 memcached를 썼습니다. 이거 소문대로 깔끔하고 잘 돌아가네요. ^_^;
Django는 다행히도 템플릿에서 일부만 특정 변수에 따라서 캐시하는 기능이 있어서
이름에 따라 바뀌는 부분, 성에 따라 바뀌는 부분을 따로 따로 캐시하도록 3조각으로
따로 캐시해서 생각보다 훨씬 간단하게 쉽게 캐시로 넣었고요, 지금은 CPU부하가 전보다
같은 요청에서 거의 1/10로 줄어들었습니다! 이히히.
다른 사소한 것들..
몇 분께서 물어보셨던 게 자료처리나 통계처리는 어떤 걸 썼느냐가 있는데, 특별히 쓴 것은 없구요, 파이썬 하나면 다 해결됩니다. -ㅇ-;
물론 numpy, matplotlib도 아주 큰 도움이 됐습니다.
collections.defaultdict를 전에는 그렇게 자주 쓰지는 않았는데, 이번에 좀 과격하게
3~4 단계 쑥쑥 defaultdict를 겹쳐서도 써 봤더니 pickle이 잘 안 되는 문제만 빼고는, 코드를 아주 많이 줄여준다는 점에서
아주 사랑스러웠습니다.
후속편으로는 이번에 들어온 로그를 한 번 분석해 보려고 하고 있습니다. ^^;Watch Full Movie Online Streaming Online and Download