최종: 축약어 분리는 하지 x, .(period) 등의 부호는 따로 counting
Tokenization
축약형은 어떻게 할 것인가?
ex) I’ll → I will 로 풀어쓸 것인가?
- solution1. 풀어쓰지 않고 원본 형태로 counting. (V)
- “I am” 과 “I’m”은 동일하게 취급해도 될까?
- 일부 시각에서는 이러한 줄임말(abbreviation) 비율이 corpus의 informality의 지표가 될 수도 있다고 해석하며, 변형을 지양함.- solution2. 해체하고 따로 counting. (I’m → I am 후 각각 count)
- 대부분 그렇게 하지 않는다고 한다. 따라서 우리도 풀어쓰지 않겠다.
- 또한, SUBTLEX 논문에 의하면, lemmatation은 word-prediction 등에 유의미한 차이를 불러 일으키지 않아, 일단 굳이 하지 않아도 될 듯 하다.
- 대부분 그렇게 하지 않는다고 한다. 따라서 우리도 풀어쓰지 않겠다.
- solution2. 해체하고 따로 counting. (I’m → I am 후 각각 count)
대소문자는?
SUBTLEX 논문에 의하면, corpus 내에서 자주 등장는 고유 명사 중 사람의 이름이 많고, 이는 대부분 대문자로 적혀 있으니, 이 과대 추정을 최소화하기 위해 분석에서는 소문자만 했다. (CD)계산
- 따라서 이를 도입하려면, 전처리 단계에서 소문자 변환을 하지 말아야 한다.
- 이후 분석 단계에서 일일이 걸러내는 작업이 더 적합할 것.
- 일단, Elexicon site에서 제공하는 값은 대소문자 구분하지 않은 값임을 확인함.
- 다만 subtlex 논문에서 대소문자 구분이 중요하다고 했으니 이후에 시도
Tokenizer
기존 library(nltk, scipy)에서 제공하는 tokenizer는 원하는 대로 되지 않아 nltk.TreebankTokenizer 을 수정해서 사용.
- ex) “I’m a student. I am a professor.”
WhitespaceTokenizer.tokenize()
공백, 구두점을 기준으로 분리
- “I’m”, ‘a’, ‘student.’, ‘I’, ‘am’, ‘a’, ‘professor.‘
nltk.word_tokenize()
Penn Treebank tokenizer를 사용함.
- 구두점은 별도 토큰으로 분리
- 축약형은 분리 (I’m → I, ‘m)
- 숫자는 보존
- 이메일, URL 등은 보존
- ‘I’, “‘m”, ‘a’, ‘student’, ’.’, ‘I’, ‘am’, ‘a’, ‘professor’, ’.‘
customTokenizer
user-define tokenizer
class CustomTokenizer:
"""
다양한 설정이 가능한 커스텀 토크나이저
"""
def __init__(self,
preserve_contractions=True,
preserve_possessives=True,
preserve_numbers=True,
preserve_urls=True,
preserve_emails=True,
filter_tokens=True):
self.treebank_tokenizer = TreebankWordTokenizer()
self.preserve_contractions = preserve_contractions
self.preserve_possessives = preserve_possessives
self.preserve_numbers = preserve_numbers
self.preserve_urls = preserve_urls
self.preserve_emails = preserve_emails
self.filter_tokens = filter_tokens
# 허용할 문자 패턴 (영어 대소문자, 마침표, 콤마, 물음표, 느낌표)
self.allowed_pattern = re.compile(r"^[a-zA-Z.,'!?]+$")
# 보호할 패턴들
self.protection_patterns = []
if preserve_contractions:
self.protection_patterns.extend([
# be 동사 축약형
r"\\bI'm\\b", r"\\bI'M\\b", r"\\bi'm\\b",
r"\\b(?:he|she|it)'s\\b", r"\\b(?:HE|SHE|IT)'S\\b", r"\\b(?:he|she|it)'s\\b",
r"\\b(?:we|you|they)'re\\b", r"\\b(?:WE|YOU|THEY)'RE\\b", r"\\b(?:we|you|they)'re\\b",
# have 동사 축약형
r"\\b(?:I|you|we|they)'ve\\b", r"\\b(?:I|YOU|WE|THEY)'VE\\b", r"\\b(?:I|you|we|they)'ve\\b",
# will 축약형
r"\\b(?:I|you|he|she|it|we|they)'ll\\b", r"\\b(?:I|YOU|HE|SHE|IT|WE|THEY)'LL\\b", r"\\b(?:I|you|he|she|it|we|they)'ll\\b",
# would 축약형
r"\\b(?:I|you|he|she|it|we|they)'d\\b", r"\\b(?:I|YOU|HE|SHE|IT|WE|THEY)'D\\b", r"\\b(?:I|you|he|she|it|we|they)'d\\b",
# 부정 축약형
r"\\b(?:do|does|did|can|could|will|would|should|must|is|are|was|were|has|have|had)n't\\b",
r"\\b(?:DO|DOES|DID|CAN|COULD|WILL|WOULD|SHOULD|MUST|IS|ARE|WAS|WERE|HAS|HAVE|HAD)N'T\\b",
r"\\b(?:do|does|did|can|could|will|would|should|must|is|are|was|were|has|have|had)n't\\b",
])
if preserve_possessives:
self.protection_patterns.append(r"\\b\\w+'s\\b")
if preserve_numbers:
self.protection_patterns.append(r"\\b\\d+(?:\\.\\d+)?\\b")
if preserve_urls:
self.protection_patterns.append(r"https?://\\S+")
if preserve_emails:
self.protection_patterns.append(r"\\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\\.[A-Z|a-z]{2,}\\b")
# 정규식 컴파일
if self.protection_patterns:
self.protection_regex = re.compile('|'.join(self.protection_patterns))
else:
self.protection_regex = None
def is_allowed_token(self, token):
"""
토큰이 허용된 패턴인지 확인
"""
# 빈 토큰은 허용하지 않음
if not token:
return False
# 허용된 패턴에 맞는지 확인
return bool(self.allowed_pattern.match(token))
def tokenize(self, text):
"""
설정에 따라 텍스트를 토큰화
"""
# 특수기호 필터링
text = text.replace("//", " ")
if not self.protection_regex:
# 보호할 패턴이 없으면 Treebank Tokenizer만 사용
tokens = self.treebank_tokenizer.tokenize(text)
else:
# 1. 보호할 패턴들을 임시 토큰으로 대체
protected_text = text
protected_matches = []
for match in self.protection_regex.finditer(text):
match_text = match.group()
protected_matches.append(match_text)
protected_text = protected_text.replace(match_text, f"__PROTECTED_{len(protected_matches)-1}__")
# 2. Treebank Tokenizer로 토큰화
tokens = self.treebank_tokenizer.tokenize(protected_text)
# 3. 임시 토큰을 원래 형태로 복원
final_tokens = []
for token in tokens:
if token.startswith("__PROTECTED_") and token.endswith("__"):
# 정규식을 사용하여 인덱스 추출
match = re.search(r"__PROTECTED_(\\d+)__", token)
if match:
idx = int(match.group(1))
if idx < len(protected_matches):
final_tokens.append(protected_matches[idx])
else:
final_tokens.append(token) # 인덱스가 범위를 벗어나면 원본 토큰 유지
else:
final_tokens.append(token) # 패턴이 맞지 않으면 원본 토큰 유지
else:
final_tokens.append(token)
tokens = final_tokens
# 4. 온점을 명시적으로 분리 (토큰 중간에 있는 온점도 포함)
processed_tokens = []
for token in tokens:
# 온점이 포함된 토큰인지 확인
if '.' in token:
# 온점을 기준으로 분리
parts = token.split('.')
for i, part in enumerate(parts):
if part: # 빈 문자열이 아닌 경우만 추가
processed_tokens.append(part)
if i < len(parts) - 1: # 마지막 부분이 아니면 온점 추가
processed_tokens.append('.')
else:
processed_tokens.append(token)
# 5. 토큰 필터링 (허용되지 않은 토큰 제거)
if self.filter_tokens:
filtered_tokens = []
for token in processed_tokens:
if self.is_allowed_token(token):
filtered_tokens.append(token)
return filtered_tokens
return processed_tokens
# 사용 예시
def test_configurable_tokenizer():
# 축약형만 보존 + 토큰 필터링
tokenizer1 = CustomTokenizer(
preserve_contractions=True,
preserve_possessives=False,
preserve_numbers=False,
preserve_urls=False,
preserve_emails=False,
filter_tokens=True
)
# 축약형과 소유격 보존 + 토큰 필터링
tokenizer2 = CustomTokenizer(
preserve_contractions=True,
preserve_possessives=True,
preserve_numbers=False,
preserve_urls=False,
preserve_emails=False,
filter_tokens=True
)
# 토큰 필터링 없이
tokenizer3 = CustomTokenizer(
preserve_contractions=True,
preserve_possessives=True,
preserve_numbers=False,
preserve_urls=False,
preserve_emails=False,
filter_tokens=False
)
test_text = "I'm going to John's house at 3:30 PM. Email me at test@example.com or visit <https://example.com>. How are you?"
print("축약형만 보존 + 토큰 필터링:")
tokens1 = tokenizer1.tokenize(test_text)
print(tokens1)
print("\\n모든 패턴 보존 + 토큰 필터링:")
tokens2 = tokenizer2.tokenize(test_text)
print(tokens2)
print("\\n토큰 필터링 없이:")
tokens3 = tokenizer3.tokenize(test_text)
print(tokens3)
test_configurable_tokenizer()
```
- "I'm", 'a', 'student', '.', 'I', 'am', 'a', 'professor', '.'