728x90
언어를 이해하고 분석하기
오늘은 자연어 처리 요약을 해보자.
오늘의 배움 |
|
1. 정규 표현식 (Regular Expression)
- 특정한 규칙을 가진 문자열을 찾기 위한 패턴
- 정규 표현식을 사용하면 대량의 텍스트 데이터에서 특정 패턴을 효율적으로 추출, 삭제, 대체 가능
1-1. 정규 표현식 문법 요약
기호(명령어) | 설명 | 예제 |
. | 임의의 한 글자 (개행 문자 제외) | a.b → "acb", "a1b" |
^ | 문자열의 시작 | ^abc → "abc로 시작하는" |
$ | 문자열의 끝 | abc$ → "abc로 끝나는" |
* | 0개 이상 반복 | a* → "", "a", "aaa" |
+ | 1개 이상 반복 | a+ → "a", "aaa" |
? | 0개 또는 1개 | a? → "", "a" |
{m} | 정확히 m개 반복 | a{3} → "aaa" |
{m,n} | m~n번 반복 | a{2,4} → "aa", "aaa", "aaaa" |
[] | 문자 집합 (택1) | [abc] → "a", "b", "c" |
[^] | 부정 문자 집합 | [^abc] → "a, b, c 제외" |
\d | 숫자 (0-9) | \d+ → "123", "0" |
\D | 숫자가 아닌 문자 | \D → "a", "!" |
\w | 문자 + 숫자 + 밑줄 ([a-zA-Z0-9_]) | \w+ → "word123" |
\W | \w에 해당하지 않는 문자 | \W → "!", " " |
\s | 공백 문자 (스페이스, 탭, 개행) | \s → " ", "\n", "\t" |
\S | 공백이 아닌 문자 | \S → "a", "1" |
` | ` | OR 연산자 |
() | 그룹화 (서브패턴) | (abc) → "abc" |
(?P<name>pattern) | 그룹에 이름 부여 | (?P<year>\d{4}) |
(?=pattern) | 긍정형 전방탐색 (포함 O) | \w+(?=\d) → "abc" (abc123) |
(?!pattern) | 부정형 전방탐색 (포함 X) | abc(?!123) → "abc" |
(?<=pattern) | 긍정형 후방탐색 (포함 O) | (?<=@)\w+ → "gmail" (abc@gmail.com) |
(?<!pattern) | 부정형 후방탐색 (포함 X) | (?<!@)\w+ |
re 모듈의 주요 함수
함수 | 설명 |
re.compile(pattern) | 정규 표현식을 컴파일 (재사용 시 성능 향상) |
re.match(pattern, str) | 문자열의 시작에서 패턴 일치 여부 확인 (없으면 None) |
re.search(pattern, str) | 문자열 전체에서 패턴 일치하는 첫 번째 항목 반환 |
re.findall(pattern, str) | 패턴과 일치하는 모든 항목을 리스트로 반환 |
re.finditer(pattern, str) | 패턴과 일치하는 모든 항목을 이터레이터(객체)로 반환 |
re.sub(pattern, repl, str) | 패턴과 일치하는 항목을 repl로 치환 |
re.split(pattern, str) | 패턴을 기준으로 문자열 분할 (리스트 반환) |
re 객체의 주요 메서드
re.compile()로 만든 컴파일 객체에서 사용 가능한 메서드들입니다.
메서드 | 설명 |
pattern.match(str) | 문자열 시작에서 패턴 일치 여부 확인 (None 반환 가능) |
pattern.search(str) | 문자열 전체에서 패턴 찾기 (처음 일치하는 항목 반환) |
pattern.findall(str) | 일치하는 모든 항목 리스트 반환 |
pattern.finditer(str) | 일치하는 모든 항목 이터레이터 객체로 반환 |
pattern.sub(repl, str) | 패턴과 일치하는 항목을 repl로 치환 |
pattern.split(str) | 패턴 기준으로 문자열 분할 (리스트로 반환) |
정규 표현식 예제
import re
# 1. 문자열에서 이메일 주소 추출
text = "Contact us at support@example.com or info@test.com"
emails = re.findall(r'\b[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}\b', text)
print(emails)
# 출력: ['support@example.com', 'info@test.com']
# 2. 전화번호 포맷 변환
text = "My number is 010-1234-5678"
new_text = re.sub(r'(\d{3})-(\d{4})-(\d{4})', r'(\1) \2-\3', text)
print(new_text)
# 출력: My number is (010) 1234-5678
# 3. 특정 패턴으로 문자열 분리
data = "apple, orange; banana|grape"
fruits = re.split(r'[;,\|]', data)
print(fruits)
# 출력: ['apple', ' orange', ' banana', 'grape']
1-2. 정규 표현식을 활용한 토큰화
- RegexpTokenizer: nltk의 RegexpTokenizer는 특정 정규 표현식을 기반으로 텍스트를 토큰화
from nltk.tokenize import RegexpTokenizer
# 영어와 숫자로 이루어진 단어 추출
tokenizer = RegexpTokenizer('[a-zA-Z0-9_]+')
text = "Hello, I'm learning NLP with Python 3.9!"
tokens = tokenizer.tokenize(text)
print(tokens)
# 출력: ['Hello', 'I', 'm', 'learning', 'NLP', 'with', 'Python', '3', '9']
2. 정수 인코딩 (Integer Encoding)
- 정의: 텍스트 데이터를 숫자로 변환하는 과정
- 작동 과정:
- 텍스트 데이터 수집
- 토큰화(Tokenization): 문장을 단어(토큰)로 분리
- 단어 사전(Vocabulary) 생성: 고유한 단어에 인덱스 부여
- 정수로 변환(Integer Encoding): 각 단어를 해당 정수로 매핑
- 한계:
- 순서 기반 정보 손실
- 희소성 문제 (단어 수 ↑, 희소 행렬 형태로 표현되므로 메모리 사용량 ↑)
- Out-of-Vocabulary(OOV): 새로운 단어 처리 필요.
- 해결:
- Word Embedding: 정수 인코딩된 데이터를 밀집 벡터(Dense Vector)로 변환.
- 서브워드 기반 모델: BPE, WordPiece(BERT에서 사용) 적용.
from nltk.tokenize import sent_tokenize, word_tokenize
from nltk.corpus import stopwords
# 문장 토큰화
sentences = sent_tokenize(raw_text)
# 영문 불용어 리스트
en_stopwords = stopwords.words('english')
# 단어사전
vocab = {}
# 토큰화/정제/정규화 처리 결과
preprocessed_sentences = []
for sentence in sentences:
sentence = sentence.lower() # 대소문자 정규화 (소문자 변환)
tokens = word_tokenize(sentence) # 토큰화
tokens = [token for token in tokens if token not in en_stopwords] # 불용어 제거
tokens = [token for token in tokens if len(token) > 2] # 단어 길이가 2 이하면 제거
for token in tokens:
if token not in vocab:
vocab[token] = 1
else:
vocab[token] += 1
preprocessed_sentences.append(tokens)
2-1. OOV (Out-Of-Vocabulary)
- 단어 사전에 없는 단어
- 처리 방법:
- OOV 토큰 사용: Tokenizer의 oov_token 인자로 미등록 단어를 특정 토큰으로 변환
- 미등록 단어 무시: 사전에 없는 단어를 제거
- oov_token="<OOV>"을 지정하면, 사전에 없는 단어는 <OOV>에 해당하는 정수(여기서는 1)로 인코딩됨.
- OOV 처리를 하지 않으면 미등록 단어는 무시되므로 주의가 필요함.
- => 미등록 단어는 <OOV>로 변환
from tensorflow.keras.preprocessing.text import Tokenizer
# 입력 데이터
texts = ["나는 사과를 좋아해", "나는 바나나를 좋아해"]
new_texts = ["나는 딸기를 좋아해", "사과는 맛있어"]
# OOV 처리를 위한 Tokenizer 생성
tokenizer = Tokenizer(oov_token="<OOV>")
tokenizer.fit_on_texts(texts)
# 단어 사전 확인
print(tokenizer.word_index)
# 출력: {'<OOV>': 1, '나는': 2, '좋아해': 3, '사과를': 4, '바나나를': 5}
# 새로운 데이터 정수 인코딩
encoded_new_texts = tokenizer.texts_to_sequences(new_texts)
print(encoded_new_texts)
# 출력: [[2, 1, 3], [1, 1]]
2-2. 정수 인코딩 주요 메서드 (Keras Tokenizer)
메서드 | 설명 |
Tokenizer() | 토크나이저 객체 생성 |
fit_on_texts(texts) | 텍스트 학습 및 단어 사전 생성 |
texts_to_sequences(texts) | 텍스트를 정수 시퀀스로 변환 |
sequences_to_texts(sequences) | 정수 시퀀스를 텍스트로 변환 |
word_index | 생성된 단어 사전(딕셔너리 형태) 조회 |
word_counts | 각 단어의 등장 횟수 반환 |
num_words | 상위 N개의 단어만 인코딩 |
oov_token | 미등록 단어 처리 시 사용할 OOV 토큰 지정 |
3. 패딩(padding)
- 정수 인코딩 후, 딥러닝 모델에 입력할 수 있도록 시퀀스의 길이를 맞추는 작업
- 장점:
- 일관된 입력 형식
- 병렬 연산 최적화
- 유연한 데이터 처리
from tensorflow.keras.preprocessing.sequence import pad_sequences
# 데이터 준비
texts = ["나는 사과를 좋아해", "사과와 바나나 모두 좋아해"]
tokenizer = Tokenizer()
tokenizer.fit_on_texts(texts)
sequences = tokenizer.texts_to_sequences(texts)
# 패딩 적용 (길이 맞추기)
padded_sequences = pad_sequences(sequences, padding='post')
print(padded_sequences)
# 출력: [[1 2 3 0], [4 5 6 3]]
pad_sequences 주요 옵션
옵션 | 설명 |
maxlen | 패딩 후 시퀀스의 최대 길이 설정 |
padding | 'pre' (앞에 패딩) 또는 'post' (뒤에 패딩) |
truncating | 길이 초과 시 'pre' (앞) 또는 'post' (뒤) 제거 |
3-1. 원-핫 인코딩
왜 패딩에서 원-핫 인코딩이 필요할까?
원-핫 인코딩이 필요한 이유는 주로 모델의 입력 형식 때문이다. 특정 모델에서는 정수 인코딩만 사용해도 괜찮지만, 일부 모델에서는 원-핫 인코딩이 필수이기 때문이다.
- 패딩: 시퀀스 길이를 맞춰 모델 입력 형식을 통일.
- 원-핫 인코딩: 정수 인코딩의 서열 관계 문제를 피하기 위해 필요.
- 필요 여부는 사용하는 모델 구조와 레이어에 따라 다름.
- Embedding 레이어 → 원-핫 인코딩 불필요
- Dense 레이어 → 원-핫 인코딩 필요
3-1-1. 원-핫 인코딩이 필요한 경우
- 기본 신경망(MLP, Dense Layer 사용)
- 밀집 신경망(Dense Layer)은 숫자 크기에 의미를 두기 때문에, 정수 인코딩만 사용하면 모델이 순위나 크기 정보로 잘못 이해할 수 있음.
- 따라서, 원-핫 인코딩으로 순서 정보를 없애고 각 단어를 독립적으로 표현해야 함.
from tensorflow.keras.utils import to_categorical import numpy as np # 정수 인코딩된 데이터 encoded = [1, 2, 3, 4] # 원-핫 인코딩 수행 one_hot = to_categorical(encoded, num_classes=5) print(one_hot) # 출력: # [[0. 1. 0. 0. 0.] # [0. 0. 1. 0. 0.] # [0. 0. 0. 1. 0.] # [0. 0. 0. 0. 1.]]
- 다중 클래스 분류 문제 (Multi-class Classification)
- 출력층에서 클래스를 구분해야 할 때는 원-핫 인코딩이 필수.
- 정수 값만 사용하면 모델이 특정 숫자 간의 서열 관계를 잘못 인식할 수 있기 때문.
3-1-2. 원-핫 인코딩이 필요하지 않은 경우
- 임베딩 레이어(Embedding Layer) 사용
- Keras의 Embedding 레이어는 정수 인코딩된 입력을 받아들이므로, 별도의 원-핫 인코딩이 필요 없음.
from tensorflow.keras.layers import Embedding from tensorflow.keras.models import Sequential model = Sequential() model.add(Embedding(input_dim=1000, output_dim=64, input_length=10))
- 순환 신경망(RNN, LSTM, GRU)
- 이들 모델도 Embedding 레이어를 사용하는 경우가 많아 원-핫 인코딩을 생략할 수 있음.
4. 워드 클라우드 (word cloud)
- 워드 클라우드: 자연어 처리에서 중요한 키워드를 시각화하는 도구로, 데이터 탐색과 노이즈 확인, 트렌드 파악에 유용
4-1. 코드 설명
- WordCloud() : 워드 클라우드 객체 생성
- font_path : 한글 사용 시 폰트 경로 지정 (미설정 시 한글 깨짐)
- width, height : 워드 클라우드 이미지 크기
- background_color : 배경색 (흰색 추천: 'white')
- max_words : 표시할 최대 단어 수
- colormap : 색상 팔레트 (예: 'viridis', 'plasma', 'inferno', 'magma' 등)
- generate() : 입력된 텍스트 기반으로 워드 클라우드 생성
- plt.imshow() : 이미지 출력
- plt.axis('off') : x, y축 제거
4-2. 자주 사용하는 메서드와 변수
메서드/변수 | 설명 |
generate(text) | 텍스트로부터 워드 클라우드 생성 |
to_file('path') | 워드 클라우드를 이미지로 저장 |
stopwords | 제외할 단어 목록 설정 |
max_font_size | 최대 글씨 크기 설정 |
contour_color | 외곽선 색상 지정 |
collocations | 연속된 단어 조합 사용 여부 (기본: True) |
4-3. 불용어 제거 예시
from wordcloud import WordCloud, STOPWORDS
text = """
파이썬은 데이터 분석, 머신러닝, 자연어 처리에 널리 사용되는 프로그래밍 언어입니다.
자연어 처리는 인공지능 분야에서 중요한 역할을 합니다.
"""
# 불용어 설정
custom_stopwords = set(STOPWORDS) # 기본 불용어
custom_stopwords.update(['자연어', '처리']) # 사용자 정의 불용어 추가
# 워드 클라우드 생성
wordcloud = WordCloud(
font_path='malgun.ttf',
stopwords=custom_stopwords,
background_color='white'
).generate(text)
plt.imshow(wordcloud, interpolation='bilinear')
plt.axis('off')
plt.show()
5. FastText
- 텍스트 분류 및 단어 임베딩을 위한 라이브러리.
- 희귀 단어나 다국어 데이터에서 강력한 성능을 발휘
주요 메서드
메서드 | 설명 |
train_unsupervised() | 비지도 학습으로 단어 임베딩 모델 생성 |
train_supervised() | 지도 학습으로 텍스트 분류 모델 생성 |
load_model() | 저장된 모델 로드 |
get_word_vector() | 단어의 임베딩 벡터 반환 |
get_sentence_vector() | 문장의 임베딩 벡터 반환 |
predict() | 입력 데이터의 분류 예측 |
save_model() | 학습한 모델 저장 |
주요 변수
매개변수 | 설명 |
model | 'skipgram' 또는 'cbow' 선택 |
dim | 벡터 차원 수 (기본값: 100) |
epoch | 학습 반복 수 (기본값: 5) |
minCount | 최소 등장 횟수 (기본값: 5) |
wordNgrams | 사용할 n-gram의 최대 길이 (기본값: 1) |
lr | 학습률 (기본값: 0.1) |
loss | 손실 함수 ('softmax', 'hs', 'ns' 지원) |
FastText와 다른 임베딩 비교
특징 | Word2Vec | FastText |
학습 방식 | 단어 단위 | 문자 n-gram 사용 |
신조어/오타 대응 | 불가능 | 가능 |
학습 속도 | 빠름 | 더 빠름 |
분류 지원 | 지원 안함 | 기본 지원 |
728x90
'Develop > AI' 카테고리의 다른 글
자연어 딥러닝 기초 - 텍스트 분류를 알아보자. (0) | 2025.03.02 |
---|---|
자연어 딥러닝 기초 - GRU를 알아보자. (0) | 2025.03.02 |
자연어 딥러닝 기초 - LSTM을 알아보자. (2) | 2025.02.23 |
자연어 딥러닝 기초 - RNN 알아보자. (0) | 2025.02.23 |
자연어 임베딩 이해를 알아보자. (6) | 2025.02.21 |