위키피디아 코퍼스 분석
- 김경민(CDO본부 데이타마이닝팀), 2007년 1월
위키피디아 코퍼스 #
다운로드 #
- 다음의 사이트에서 영어 위키피디아 데이타베이스를 다운로드 받을 수 있습니다.
: http://download.wikimedia.org/enwiki/
참고 사이트 : http://en.wikipedia.org/wiki/Wikipedia:Database_download
MySQL에 저장 #
- gzip -dc dump.xml.gz | php maintenance/importDump.php
- 위의 작업을 위해서는 www.mediawiki.org에서 위키를 다운받아서 설치를 한 후에 maintenance폴더에 있는 파일로 실행할 수 있다.
코퍼스 통계 정보 #
- 대상 파일 : enwiki-20060502-pages-meta-current.xml
- 인코딩 : UTF-8
- 파일 사이즈 : 약 8.8 기가 (8,844,837,180 Bytes)
- 표제어 수 : 약 4,103,315개
사용 툴 #
- Python, PHP, Mysql
데이타 추출 #
추출 대상 #
- <title> 태그에 의해서 표제어 추출
- /={2, 6}/ 에 의해서 하위 제목 추출
- DATA에 의하여 표제어 추출
- #REDIRECT 관련 데이타 추출
문자열 전처리 #
- 이렇게 추출된 문자열 가운데 알파벳, 하이픈(-), 어퍼스트로피('), 앰퍼샌드(&)만으로 구성된 것을 제외한 다른 문자열들은 모두 제외한다.
- Title의 경우 스페이스(공백문자)대신 언더바(_)를 사용했으므로 언더바를 모두 스페이스로 변환해준다.
- 괄호에 의해서 묶여진 데이타는 원래 추출 문자에는 포함되지 않는 괄호를 가지고 있지만, 위키피디아에서 둥근괄호는 부가적인 설명을 주는 의미가 있으므로 따로 처리해 준다.
- 예) jaguar (car) : 브라우저상에서는 jaguar라고 보이지만, jaguar의 속성을 car로 둥근괄호를 통해서 보여준다.
- '|'에 의해서 부가적인 설명이 붙는 문자열의 경우도 특별히 분리해서 따로 각각의 의미를 살려줄 수도 있다. 다만 이렇게 처리할 경우 처리해야할 문자열의 수가 지나치게 많아지게 되는 수가 생기므로 필요에 따라서 추출의 정도를 달리할 수 있다.
- 예) anarchism|anarchist
- 예) anarchism|anarchist
추출방법 #
- 제목과 하위제목은 정규표현식을 가지고 문자열을 치환해가면서 쉽게 추출 가능
- Internal Link 추출
- Pushdown automata를 구현해서 추출가능
- 원래 위키피디아 사이트 소스에서는 ']]'에 의해서 문자열을 분리한 후 '[['을 찾아서 내부 문자열에 링크를 거는 식으로 되어 있으므로 이와 같은 방식으로 처리하는 것이 간단함.
추출한 문자열의 원형 복원 #
- 추출한 문자열의 원형을 복원하기 위해서 C로 작성된 영어형태소 분석기를 swig를 통해서 파이썬에서 사용가능하도록 포팅하였다. (이 때문에 사실 처음에 PHP가 아닌 파이썬을 사용했지만... 실제 데이타 분석에서는 형태소분석된 문자열을 사용하지 않았다)
- 파이썬에서 사용하기 위한 Wrapper를 만들기 위하여 ***.i 파일을 만든다. 여기에서는 dema.i를 만들었다.
- 마지막 getBase 함수는 간단하게 형태소 결과를 받아오는 함수이다.
- 마지막 getBase 함수는 간단하게 형태소 결과를 받아오는 함수이다.
/* File : dema.i */
%module dema
%{
/* Put headers and other declarations here */
extern int startDaumEMA (const char* pFile, const char* sFile, const char* dFile, const char* iFile);
extern void endDaumEMA();
extern int HANL_AnalyzeEnglish (FINAL_INFO* info, char* instr, int tt);
extern char* getBase (char* in);
%}
extern int startDaumEMA (const char* pFile, const char* sFile, const char* dFile, const char* iFile);
extern void endDaumEMA();
extern int HANL_AnalyzeEnglish (FINAL_INFO* info, char* instr, int tt);
extern char* getBase (char* in);
- unix> swig -python dema.i
- dema_wrap.c 라는 함수를 얻는다.
- unix> gcc -c -fpic dema.c dema_wrap.c searchtrie.c affile.c -I/usr/local/include/python2.4
- searchtrie.c는 사전검색을 위해서 affile.c는 접사 사전검색을 위해서 사용되는 파일들이다.
- object 파일들을 얻는다.
- unix> gcc -shared dema.o dema_wrap.o searchtrie.o affile.o -o _dema.so
- 다음과 같이 오브젝트 파일들을 다 모아서 _dema.so를 만들어준다. 파일명의 중복을 피하기 위해서 _(under bar)를 붙여준다.
- 다음과 같이 오브젝트 파일들을 다 모아서 _dema.so를 만들어준다. 파일명의 중복을 피하기 위해서 _(under bar)를 붙여준다.
- unix> python
- >>> import dema
- dema.i 에서 module이름을 dema라고 해 주었기 때문에 dema를 import한다.
- dema.i 에서 module이름을 dema라고 해 주었기 때문에 dema를 import한다.
- >>> dema.startDaumEMA ("data_prefix", "data_suffix", "entry.db", "infl.db")
- 사전을 로딩해 준다. 앞에서 부터 접두사, 접밈사, 규칙사전, 불규칙사전 순이다.
- 사전을 로딩해 준다. 앞에서 부터 접두사, 접밈사, 규칙사전, 불규칙사전 순이다.
- >>> dema.getBase("books")
- 결과값으로 book이 출력된다.
- 결과값으로 book이 출력된다.
- >>> dema.endDaumEMA()
- 종료된다.
- 종료된다.
데이타 분석 #
분석 목적 #
- 어휘간의 관계를 만들어, 특정 문자열에 대해서 관련된 어휘를 관련도에 따라서 보여주는 것
분석 방법 #
- 하나의 문서에서 제목과 나머지 추출 문자열들의 쌍(pair)을 만들어준다.
- 하나의 문단에서 추출된 문자열들을 가지고 쌍(pair)을 만들어준다.
- 이들 정보를 가지고 빈도를 구한다.
관련도 계산식 #
- 현재의 pair의 빈도 : a
- 각 pair의 빈도 합 : b
- 입력 쿼리에 대해 관계를 가지는 문자열 빈도 : c
- 모든 문자열의 빈도 : d
ratio = a * log((a/b)/(c/d))
SAS Code #
/* for analyzing in remote sever */
rsubmit;
/*
* Making a SAS sheet for extracted data such as titles and internal links from wikipedia by python program
*/
data wiki_result;
infile '/home/sas/work_metamoi/text_entry_tbl.txt' dlm='09'x;
length entry_id 8. entry_sub_id 8. para_id 8. raw_text $50. text_id $16. sub_text $50.;
input entry_id entry_sub_id para_id raw_text text_id sub_text;
run;
/*
* sheet split by entry_id which shows one document because of memory management
*/
data text0 text1 text2 text3 text4 text5 text6 text7 text8 text9;
set wiki_result;
if mod(entry_id,10) = 0 then output text0;
if mod(entry_id,10) = 1 then output text1;
if mod(entry_id,10) = 2 then output text2;
if mod(entry_id,10) = 3 then output text3;
if mod(entry_id,10) = 4 then output text4;
if mod(entry_id,10) = 5 then output text5;
if mod(entry_id,10) = 6 then output text6;
if mod(entry_id,10) = 7 then output text7;
if mod(entry_id,10) = 8 then output text8;
if mod(entry_id,10) = 9 then output text9;
run;
/*
* SAS MACRO Declaration
*/
%macro repeat;
%do i = 0 %to 9;
proc sql;
/* making a pair with internal links in a paragraph */
create table text_v1(compress=yes) as
select a.entry_id, a.para_id, a.sub_text as key1, b.sub_text as key2
from text&i(where=(text_id ne 'TITLE')) a, text&i(where=(text_id ne 'TITLE')) b
where a.entry_id = b.entry_id and a.para_id = b.para_id and a.sub_text ne b.sub_text;
/* making a pair with internal links and title */
create table text_v2(compress=yes) as
select a.entry_id, a.sub_text as key1, b.sub_text as key2
from (select * from text&i where text_id = 'TITLE') a, text&i b
where a.entry_id = b.entry_id and a.sub_text ne b.sub_text;quit;
/* joining the above two results together */
data all;
set text_v1(keep = key1 key2) text_v2(keep = key1 key2);
run;
/* computing the frequency of pairs */
proc sql;
create table all_&i(compress=yes) as
select key1, key2, count(*) as cnt
from all
group by 1,2;
quit;
%end;
%mend;
/* run the macro */
%repeat;
/*
* Joining the above results from MACRO together
*/
data all;
set all_0 all_1 all_2 all_3 all_4 all_5 all_6 all_7 all_8 all_9;
run;
/* computing the frequency of pairs */
proc sql;
create table all2(compress=yes) as
select key1, key2, sum(cnt) as cnt
from all
group by 1,2;
/* computing the frequency of terms */
create table zz2(compress=yes) as
select sub_text, count(*) as cnt
from wiki_result group by 1;
/* Total of terms */
proc sql;
create table total_term as
select sum(cnt) as cnt
from zz2;
/*
* computing the ratio using the above two tables, zz2, all2
*/
create table all3(compress=yes) as
select a.*, b.cnt as k1_cnt, c.cnt as k2_cnt ratio as b.cnt * log ((b.cnt*d.cnt)/(k1_cnt*k2_cnt))
from all2 a, zz2 b, zz2 c, total_term d
where a.key1 = b.sub_text and a.key2 = c.sub_text;
data a;
today = today();
today2 = put(today,yymmddn.);
call symput('today',today);
call symput('today2',today2);
run;
/*
* writing the all3 sheet on the file
*/
data _null_;
set all3;
file '/disk/15/metamoi/wiki_final.txt' dlm='09'x;
put key1 key2 cnt k1_cnt k2_cnt;
run;
/*
* writing the zz2 sheet on the file
*/
data _null_;
set zz2;
file '/disk/15/metamoi/term_tbl.txt' dlm='09'x;
length sub_text $50. cnt 8.;
put sub_text cnt;
run;
endrsubmit;
결론 #
- 이전에는 좀 더 받은 코퍼스를 확보하기 위해서 코퍼스에서 좀더 세밀하게 좀더 많은 정보를 추출하는 것이 목적이었지만, 현재는 정보의 양이 너무 많아져서 사실 정보를 좀 더 많이 추출하는 것이 중요한 것이 아니라 어떻게 좀 더 정제된 정보를 추출하느냐가 데이타 가공에서 가장 중요한 부분이라고 이번 작업을 하면서 생각하게 되었다.
- 우리가 하고 있는 검색이라는 분야에서도 어떤 면에서 많은 정보가 존재하는데 이들 가운데서 어떻게 데이타를 필터링하고 정제해서 적은 양이지만 정확한 데이타를 보여주느냐가 중요한 부분인 것 같다.
- 작업 자체는 기술적으로 크게 어려운 부분은 아니지만, 대용량 데이타를 다룰 때에 생기는 시간과 데이타베이스의 성능이 큰 부분이라는 생각이 든다.
- 위키피디아는 사실 어려 면에서 응용해 볼 수 있는 유용한 데이타라는 생각이 든다. 이를 여러면에서 활용할 수 있을 것 같다.