EXCELSIOR

Chap03-2 : Creating Custom Corpora 본문

NLP/NLTK

Chap03-2 : Creating Custom Corpora

Excelsior-JH 2017. 1. 9. 16:57

1. Creating a categorized text corpus

대량의 corpus로 이루어진 텍스트를 섹션을 나누어 카테고리화 하면 문서를 체계화 하거나, 분류(classification)하는데 유용하다. 

다음 movie review에 대한 텍스트를 긍정(Pos)와 부정(Neg)로 카테고리화 해보자

1) movie_pos.txt

: the thin red line is flawed but it provokes.

2) movie_neg.txt

: a big-budget and glossy production can not make up for a lack of spontaneity that permeates their tv show.

CategorizedPlaintextCorpusReader 클래스를 이용하여 카테고리화 한다. CategorizedPlaintextCorpusReader 클래스는 PlaintextCorpusReader와 CategorizedCorpusReader로 부터 상속받는다.

from nltk.corpus.reader import CategorizedPlaintextCorpusReader
reader = CategorizedPlaintextCorpusReader('.', r'movie_.*\.txt', cat_pattern=r'movie_(\w+)\.txt')
print(reader.categories())
print(reader.fileids(categories=['neg']))
print(reader.fileids(categories=['pos']))
#결과
['neg', 'pos']
['movie_neg.txt']
['movie_pos.txt']

'cat_pattern' 키워드는 'fileids'로 부터 카테고리 명을 추출하기 위한 정규 표현이다. 위의 예제에서는 카테고리는 'fileids'에서 'movie_' 뒤와 '.txt'앞에 위치한다. 카테고리는 반드시 괄호로 묶여있어야 한다. movie_pos.txt → movie_(\w+)\.txt

아래의 예제와 같이 'cat_pattern' 키워드 대신에 'cat_map'을 사용하여 dictionary 매핑을 통해 카테고리를 labeling 할 수 있다. 

(개인적으로 아래의 방법이 더 직관적일듯 하다.)

from nltk.corpus.reader import CategorizedPlaintextCorpusReader
reader = CategorizedPlaintextCorpusReader('.', r'movie_.*\.txt', cat_map={'movie_pos.txt' :['pos'], 'movie_neg.txt' : ['neg']})
print(reader.categories())
print(reader.fileids(categories=['neg']))
print(reader.fileids(categories=['pos']))
#결과
['neg', 'pos']
['movie_neg.txt']
['movie_pos.txt']


2. Creating a categorized chunk corpus reader

NLTK는 CategorizedPlainTextCorpusReader와 CategorizedTaggedCorpusReader클래스를 제공하지만, chunk를 카테고리화 해주는 reader는 없다. 따라서, CategorizedChunkedCorpusReader라는 클래스를 직접 만들어 본다. CategorizedChunkedCorpusReader는 CategorizedCorpusReader와 ChunkedCorpusReader를 상속받는다.  (아래의 코드는 catchunked.py와 같음)

from nltk.corpus.reader import CategorizedCorpusReader, ChunkedCorpusReader


class CategorizedChunkedCorpusReader(CategorizedCorpusReader, ChunkedCorpusReader):
    def __init__(self, *args, **kwargs):
        CategorizedCorpusReader.__init__(self, kwargs)
        ChunkedCorpusReader.__init__(self, *args, **kwargs)
        
    def _resolve(self, fileids, categories):
        if fileids is not None and categories is not None:
            raise ValueError('Specify fileids or categories, not both')
        if categories is not None:
            return self.fileids(categories)
        else:
            return fileids
        
    def raw(self, fileids=None, categories=None):
        return ChunkedCorpusReader.raw(self, self._resolve(fileids, categories))
    
    def words(self, fileids=None, categories=None):
        return ChunkedCorpusReader.words(self, self._resolve(fileids, categories))
    
    def sents(self, fileids=None, categories=None):
        return ChunkedCorpusReader.sents(self, self._resolve(fileids, categories))
    def paras(self, fileids=None, categories=None):
        return ChunkedCorpusReader.paras(self, self._resolve(fileids, categories))
    
    def tagged_words(self, fileids=None, categories=None):
        return ChunkedCorpusReader.tagged_words(self, self._resolve(fileids, categories))
    
    def tagged_sents(self, fileids=None, categories=None):
        return ChunkedCorpusReader.tagged_sents(self, self._resolve(fileids, categories))
    
    def tagged_paras(self, fileids=None, categories=None):
        return ChunkedCorpusReader.tagged_paras(self, self._resolve(fileids, categories))
    
    def chunked_words(self, fileids=None, categories=None):
        return ChunkedCorpusReader.chunked_words(self, self._resolve(fileids, categories))
    
    def chunked_sents(self, fileids=None, categories=None):
        return ChunkedCorpusReader.chunked_sents(self, self._resolve(fileids, categories))
    
    def chunked_paras(self, fileids=None, categories=None):
        return ChunkedCorpusReader.chunked_paras(self, self._resolve(fileids, categories))

CategorizedChunkedCorpusReader 클래스는 ChunkedCorpusReader의 모든 메소드를 fileids에 위치하는 카테고리드를 가져오기 위해 오버라이딩(Overriding)한다. fileids 는 _resolve( ) 함수 안에서 확인할 수 있다(?).  이 _resolve( ) 함수는 CategorizedCorpusReader.fileids( )를 이용하여 카테고리 리스트를 return 한다. 만약 주어진 카테고리가 없으면, _resolve( ) 함수는 fileids를 return한다.


아래의 예제는 CategorizedChunkedCorpusReader 클래스를 사용한 예제이다. 

import nltk.data from catchunked import CategorizedChunkedCorpusReader path = nltk.data.find('corpora/treebank/tagged') reader = CategorizedChunkedCorpusReader(path, r'wsj_.*\.pos', cat_pattern=r'wsj_(.*)\.pos') print(len(reader.categories())==len(reader.fileids())) print(len(reader.chunked_sents(categories=['0001']))) #결과 True 16


3. Lazy corpus loading

Corpus reader는 파일의 수, 파일의 크기에 따라 속도에 영향을 많이 받는다. NLTK는 LazyCorpusLoader 클래스를 제공하는데, LazyCorpusLoader는 스스로 corpus에 맞게 변환한다. 

LazyCorpusLoader 클래스는 2개의 인수들을 필요로 하는데, corpus의 이름과 corpus reader 클래스, 그리고 corpus reader 클래스를 초기화 하는데 필요한 다른 인수가 필요하다.  name 인수는 corpus의 root directory가 명시되어있어야 한다. 두번째 인수인 reader_cls는 CorpusReader (예, WordListCorpusReader)가 필요하다.

from nltk.corpus.util import LazyCorpusLoader
from nltk.corpus.reader.wordlist import WordListCorpusReader
reader = LazyCorpusLoader('.', WordListCorpusReader, ['wordlist.txt'])
print(isinstance(reader, LazyCorpusLoader))
print(reader.fileids())
print(isinstance(reader, LazyCorpusLoader))
print(isinstance(reader, WordListCorpusReader))
#결과
True
['wordlist.txt']
False
True

LazyCorpusLoader 클래스는 속성이나 메소드에 접근하기 전까지 아무것도 동작하지 않는다. 이러한 이유 때문에, 초기화가 매우 빠른것이다. 속성이나 메소드에 접근하는 순간

1) nltk.data.find(name) 을 호출하여 data root directory를 찾는다.

2) corpus reader와 root directory, 다른 인수를 인스턴스화 한다.

3) Corpus reader 클래스를 변환한다.

위의 예제 코드에서, reader.fileids( )를 호출하기 전까지 reader는 LazyCorpusLoader 였지만, 호출 후 reader가 WordListCorpusReader로 바뀐것을 알 수 있다.


Comments