본문 바로가기

Programming

집단지성 프로그래밍 3(Term Vector)

이 글은 토비 세가란 저, 윤종완 역집단지성 프로그래밍 책을 읽으며 개인적으로 공부하며 정리한 내용이다.


이전 글 목록

이전 글에서는 서로 관련된 사물들을 발견하는 방법에 대해 정리하였고, 이전의 방법을 통해 같은 영화 취향을 가진 다른 사람을 찾을 수 있었다. 이번에는 데이터 군집(Data Clustering)에 대해 설명을 한다. Data Clustering은 밀접히 관련된 사물, 사람, 아이디어들의 그룹을 찾고 시각화 하는 기법이다.

Data Clustering


군집화는 데이터 집중적인 응용(Data-Intensive Applications)에서 자주 사용되는 기법이고 간단한 예를 들면 고객이 구매한 상품을 기록하는 소매상은 일반적인 인구 통계적 정보와 이 구매 정보를 이용해서 유사한 구매 고객 그룹들을 자동으로 검출할 수 있다.


이러한 일을 하는 머신 러닝 기법에는 지도학습(Supervised Learning)비지도 학습(Unsupervised Learning)이 존재한다.

Supervised Learning은 정확한 답이 있는 학습 데이터(Learning Data)를 이용하여 학습하는 기법을 의미하며, 신경망(neural network), 결정트리(Decision Tree), SVM(Support Vector Machine), Bayesian Filtering등이 있다. 이와 같은 기법들을 사용하는 application에서는 입력과 기대 출력을 "학습(learn)"하고, 이런 기법 중의 하나를 사용한 application에서는 입력을 넣은 후에야 지금까지 학습했던 것에 기반하여 추출된 결과를 출력한다.


이번에 할 군집화는 Unsupervised Learning의 한 예이며, Neural Network와 Decision Tree와 달리 정확한 답이 있는 학습 데이터로 학습할 필요가 없다. Unsupervised Learning의 목적은 어떤 올바른 답을 찾는 것이 아닌 데이터 집합 내에서 구조를 발견하는 것이다. 이들의 예시로는 NMF(Non-negative Matrix Factorization)SOM(Self-Organizing Maps)가 있다.

블로그 데이터 수집 및 단어 벡터

Tistory 블로그에서 주제별 검색을 통해 블로그 주소들을 수집하여 군집화 하려고 한다.(기존 책에는 데이터 셋 링크가 있으나, 링크로 접속시 404 error가 출력되고, 영어가 기준인 문제점이 있었다.) 블로그 내의 단어 빈도로 군집하면, 비슷한 스타일 혹은 유사 주제에 대한 글이 자주 올라오는 블로그 그룹들을 찾을 수 있다. 이렇게 발견된 그룹들은 현재 온라인에 있는 거대한 블로그들에 대해 검색하고, 목록을 만들며, 발굴하는 데 큰 도움이 될 것이다.


feedlist는 git 내의 feedlist.txt를 다운로드 받으면 된다.


먼저 Tistory 블로그 주소들을 리스트 화 하여 개행을 구분으로 feedlist.txt에 저장한다. 그리고 파이썬의 feedparser 라이브러리를 이용해 각 블로그에서 rss 피드들을 받아온다. 그 후 BeautifulSoup 4 라이브러리를 이용해 HTML 내에서 내용만 추출하고, 단순하게 띄어쓰기를 구분으로 키워드들을 분리한다. 이를 위해 먼저 pip을 이용해 feedparser, BeautifulSoup4 라이브러리를 설치한다.

$ pip install feedparser
$ pip install beautifulsoup4

아래의 코드를 generatefeedvector.py에 추가한다.

위의 코드를 살펴보면, getwordcounts(url)은 각 url을 파라미터로 받아, feedparser 라이브러리를 이용해 summary 내용을 가져온다. 이후 받아온 내용을 getwords(html) 함수로 넣어, 구분된 키워드들을 받아와 각 단어의 개수를 계산한다.


getwords(html)에서는, HTML 태그가 전부 포함된 문자열들을 입력으로 받고, beautifulsoup4 라이브러리의 get_text() 함수를 이용하여 태그를 전부 제거한 문자열을 받고, 각 문자열을 띄어쓰기로 구분하여 return 한다.


여담이지만, 단순 띄어쓰기를 이용해 키워드 추출을 하다보니, 결과가 제대로 나오지 않는 것 같다. 추후 시간이 된다면 예전에 사용했던 mecab 형태소 분석기를 이용해 키워드를 추출해봐야 겠다는 생각이 들었다.


위의 코드들로 함수를 만들었다면, 이제 실제 메인안에서 feedlist.txt로 부터 각각 한줄씩 rss url들을 가져와 getwordcounts(url) 함수를 실행하고, TF-DF를 계산하여 그 값을 csv 형태로 blogdata.csv에 저장한다.


코드를 추가하기 전에 이후 사용될 라이브러리인 re와 csv 라이브러리를 가장 위에 추가한다.

import re
import csv


이제 generatefeedvector.py 안에 아래 코드를 추가한다.

위의 코드를 자세히 설명하자면, 6 ~ 11 라인은 feedlist.txt로부터 각각의 url들을 불러오고, 그 값이 주석인지 아닌지 판단 후 주석이 아니면 feedlist.append(feedurl)을 통해 feedlist에 각 url을 추가한다.


15 라인은 getwordcounts(url) 함수를 이용해 어떤 블로그인지 그리고 블로그 글 내의 단어에 대한 정보를 추출한다. 이 때 TF(Term Frequency) 라는 것은 각 단어의 빈도수를 의미한다.


24 ~ 27 라인은 추출된 단어가 feedlist.txt 내의 몇개의 블로그에서 출현하는지를 계산하는 부분이다. 예를 들어 '나는', '그는', '그래서' 등과 같은 단어는 어떠한 블로그 혹은 글에서도 나올 수 있지만, 'OS X', '엘 케피탄'과 같은 키워드는 맥북 리뷰 관련 블로그에서만 출현할 것이다. 이것을 DF(Document Frequency)라 하고, 이 값이 높으면 그 단어는 중요도가 떨어진다는 의미이다.


29 ~ 33 라인은 DF를 이용해 너무 중요도가 떨어지거나 혹은 너무 드물게 나온 단어들을 제외하여 분석할 데이터를 제한하는 코드이다. 안의 0.1과 0.5와 같은 숫자는 개인에 따라 조절하여 사용하여야 한다.


35 ~ 48 라인은 각각 추출된 단어와 블로그에 대한 정보를 csv 파일 형태로 blogdata.csv 파일에 저장한다.(책 속에서는 단순히 '\t'과 '\n'을 이용해 표를 만들었지만, 개인적으로 해 본 결과 전체적으로 볼 수 없어 불편한 점이 있었다.)


$ python generatefeedvector.py

코드를 완성하고 실행을 해 보면, 아래 그림과 같은 결과를 얻을 수 있다.
generatefeedvector 결과


개인적으로 결과만을 보고 아직 어떠한 의미를 지닌지는 나도 잘 모르겠다. 조금 더 진행을하거나 혹은 키워드 추출을 단순 띄어쓰기 구분 형식이 아닌 형태소 분석기를 이용하면 조금 더 의미가 있을지도 모르겠다.


TF-IDF에 관한 글도 Vector Space Model에 간단하게 작성해 놓았다.

Tistory feedlist 만들기

처음에는 책에 나와있는 링크에서 feedlist.txt를 다운받아 사용하려 했으나, 존재하지 않는 url이라 하여 직접 만들기로 하였다. 그래서 간단하게 Tistory에서 블로그들을 직접 가져오려고 하였다.


http://www.tistory.com/category/it 와 같이 tistory 홈페이지에 들어가면 주제별로 블로그들을 모아 놓은 것을 볼 수 있고, 각각의 블로그 주소 뒤에 /rss를 붙이면 feed 주소들을 알 수 있었다.

tistory blog list


이 방법으로 하나하나 수작업을 통해 링크를 수집하던 도중, 너무 귀찮아서 '한번에 수백개씩 가져올 수 없을까' 라는 생각이 들었고, 찾은 방법을 통해 한번에 feedlist.txt를 만들 수 있게 되었다.


먼저 Tistory에서 블로그 리스트들을 불러오는지 알 필요가 있었는데, Tistory에서는 처음부터 리스트를 전부 불러오는 것이 아니라, 마우스 휠을 내려 가장 아래 쪽으로 가면 추가적인 리스트를 불러오는 방식이었다. 이를 보고 jqueryajax를 이용해 가져온다는 생각을 할 수 있었고, ajax 코드가 들어있는 javascript 함수를 찾기 위해 아래 그림과 같이 먼저 개발자도구를 통해 feed url이 들어있는 태그 id를 찾게 되었다. 그림을 보면 li 태그 안에 macnews라는 블로그 주소가 들어 있고 li태그 class id는 recomm_blog임을 알 수 있다.

macnews tag


이제 Tistory 홈페이지에서 로딩하는 .js 파일을 전부 찾아, recomm_blog를 포함하는 함수를 찾는다. 찾다보면 아래와 같이 T.p.top.category.js 파일 내에서 recomm_blog라는 문자열이 사용되는 것을 볼 수 있다.

js file


이제 이 소스 파일을 분석해 보면, 딱 봐도 '아 이게 리스트 불러오는 함수구나'라는 생각을 하게 되는 함수가 존재한다.

moreCategoryViewNew: function(p, q) {
        var c = jQuery("#categoryMoreViewIco"), r = this.themeOrder, a = c.attr("data-lastpublishedval") || null, b = jQuery("#categoryName").val(), m = jQuery("#categoryPostWrap");
        var n = jQuery("#sortingThemeForm")[0].sorting;
        for (var j = 0, f = n.length; j < f; j++) {
            var d = n[j];
            var h = jQuery(d);
            if (d.checked) {
                var g = n[j].value;
                h.next().addClass("lab_choiced")
            } else {
                h.next().removeClass("lab_choiced")
            }
        }
        if (q && q.type == "change" && r == g) {
            return false
        }
        r = this.themeOrder = g;
        jQuery.cookie("TISTORY_THEME_ORDER", r, {
            domain: location.hostname,
            path: "/",
            expires: this.themeOrderCookieExpireDay
        });
        if (!p&&!a) {
            c.hide();
            return false
        }
        if (p) {
            a = 0
        }
        var k = "/category/getMoreCategoryPost.json";
        var o = this;
        T.util.ajax({
            url: k,
            timeout: 30000,
            data: {
                category: b,
                order: r,
                lastPublished: a,
                first: p
            },
            success: function(e) {
                if (!e.error) {
                    if (p) {
                        m.html("")
                    }
                    var i = jQuery(e.data.list);
                    if (r == "recent") {
                        T.p.top.daumLike.init(i)
                    }
                    m.append(i);
                    o.moreViewCount++;
                    o.moreAppend = true;
                    c.attr("data-lastpublishedval", e.data.lastPublished);
                    c.hide()
                } else {
                    alert(e.data.errorString)
                }
            },
            error: function(e) {
                alert(e.data.errorString)
            }
        })
    }


이제 Tistory 홈페이지 내에서 Jquery와 이 함수를 이용해 리스트를 불러오고, 이를 Jquery selector를 이용해 url들을 추출하면 된다. 결과는 아래 그림과 같다.

for(var i=0; i<50; i++)
    T.p.top.category.moreCategoryViewNew();

var blog_list = jQuery(".recomm_blog")
for(var i=0; i<blog_list.length; i++)
    console.log(blog_list[i].childNodes[1]['href']);


이렇게 추출한 url에서 뒤에 숫자 혹은 쓸모 없는 문자들을 떼고, /rss를 붙이면 feedlist가 완성된다.