2024-01-05 76th Class
NLP 개요
#️⃣ 자연어란
자연어는 사람들이 일상적인 의사소통에 사용하는 언어. 인공적으로 만들어진 언어(예: 프로그래밍 언어)와 대조
자연어는 각 나라와 문화에 따라 다양한 형태로 존재 (구어+문어)
자연어 처리(Natural Language Processing, NLP)는 이러한 자연어를 컴퓨터가 이해하고 처리할 수 있도록 하는 컴퓨터 과학의 한 분야
- 말뭉치(Corpus) : 특정한 목적을 가지고 수집한 텍스트 데이터
- 문서(Document) : 문장(Sentence)들의 집합
- 문장(Sentence) : 여러 개의 토큰(단어, 형태소 등)으로 구성된 문자열. 마침표, 느낌표 등의 기호로 구분
- 어휘집합(Vocabulary) : 코퍼스에 있는 모든 문서, 문장을 토큰화한 후 중복을 제거한 토큰의 집합
#️⃣ 자연어의 분야
- 텍스트 분석과 이해: 텍스트 데이터에서 유용한 정보를 추출하고 해석. (ex 감정 분석, 주제 분류, 요약 등)
- 음성 인식: Siri나 Google Assistant와 같은 음성 비서가 음성 명령을 텍스트로 변환하고 이해
- 기계 번역: 다른 언어로 텍스트를 번역
- 챗봇과 대화형 시스템: 사용자의 질문에 응답하거나 대화를 이끌 수 있는 시스템을 구축
- 정보 검색: 사용자의 질문에 가장 관련성 높은 답변 찾기
#️⃣ 말뭉치
자연어 처리를 위해 모아놓은 구조화된 텍스트 데이터 모음
EX)
긍정적인 문장 (Positive Sentences) | 부정적인 문장 (Negative Sentences) |
---|---|
이 커피는 정말 맛있어요. | 이 음식은 너무 짜고 맛이 없어요. |
나는 오늘 기분이 아주 좋습니다. | 그 호텔은 너무 시끄럽고 불편했습니다. |
그 영화는 정말 감동적이었어요. | 오늘 날씨는 정말 우울하고 흐립니다. |
#️⃣ 데이터 전처리
- 온라인 상품 리뷰 데이터 예시
- “이 제품 정말 좋아요!!! :-) :-) :-)”
- “별로에요… 배송도 늦고 상품도 고장나서 왔네요…”
- “가격 대비 괜찮네요. ^^”
항목 | 설명 | 예시 |
---|---|---|
불필요한 문자 제거 | 특수문자나 이모티콘 제거 | “이 제품 정말 좋아요!!! :-)” → “이 제품 정말 좋아요” |
일관된 형식 유지 | 대소문자, 불필요한 구두점 표준화 | “별로에요… 배송도 늦고 상품도 고장나서 왔네요…” → “별로에요 배송도 늦고 상품도 고장나서 왔네요” |
표제어 추출(Lemmatization), 어간 추출(Stemming) | 단어의 기본 형태(표제어)를 찾기 | ‘running’, ‘ran’, 'runs’는 모두 ‘run’ |
품사 태깅(Part-of-Speech Tagging) | 텍스트 내의 각 단어에 해당하는 품사(명사, 동사, 형용사 등)를 태깅 | |
불용어 제거 | 문맥 분석이나 의미 파악에 큰 영향을 주지 않는 단어 제거 | “는”, “과”, "을"과 같은 조사나 “그리고”, "하지만"과 같은 접속사 |
토큰화 (Tokenization) | 각 문장을 단어 단위로 분리하는 과정 | "이 커피는 정말 맛있습니다"→ [“이”, “커피”, “는”, “정말”, “맛”,”있”,”습니다”] |
벡터화 (Vectorization) | 토큰화된 데이터를 숫자 벡터로 변환 | [“이”, “커피는”, “정말”, “맛있습니다”]→ [0.5, 0.3, 0.2, 0.4] |
- 한국어 전처리 특징
- 형태소 분석(Morphological Analysis): 한국어는 교착어이므로 단어를 더 작은 의미 단위인 형태소로 분리
- 조사 처리: 한국어의 조사는 문장 내에서 중요한 문법적 기능을 하지만, 때로는 NLP 모델에 혼란을 줄 수 있으므로 적절히 처리
- 복합어 및 신조어 처리: 한국어는 복합어와 신조어가 많으므로, 이를 효과적으로 처리
- 띄어쓰기 교정: 한국어의 띄어쓰기는 영어보다 복잡하며, 잘못된 띄어쓰기는 의미 해석에 큰 영향을 미칠 수 있음
데이터 전처리
#️⃣ 자연어 전처리 과정
Normalization > 형태소 분석 > Tokenizing > stopword 제거 > vectorization
#️⃣ Tokenization
문장 또는 문서를 작은 단위로 나누는 과정
모델이 단어가 가지는 텍스트의 기본 단위에 대해서 이해하기 위해서 필요한 작업
모델이 토큰단위로 문맥, 의미를 이해
토큰은 문서에서 의미를 가지는 최소 단위
English Tokenizer
기준 | NLTK | spaCy |
---|---|---|
주 사용 용도 | 교육 및 연구 | 상업적 응용 및 산업용 |
처리 속도 | 상대적으로 느림 | 빠름 |
적합한 환경 | 소규모 데이터셋, 실험적 및 학습 목적 | 대규모 데이터셋, 실시간 시스템 |
특징 | 유연성, 다양한 언어 처리 도구 제공 | 효율적인 텍스트 처리, 통합 파이프라인 제공 |
장점 | 다양한 기능, 사용자 친화적 | 고성능, 확장성, 대규모 데이터 처리에 적합 |
단점 | 처리 속도가 느림, 대규모 데이터에 적합하지 않음 | 기능이 NLTK에 비해 제한적, 학습 곡선이 가파름 |
사용 사례 | 언어 연구, 교육적 목적, 다양한 NLP 태스크 실험 | 생산 환경, 실시간 언어 처리, 상업적 애플리케이션 |
nltk
import nltk
from nltk.tokenize import word_tokenize
from nltk.tokenize import sent_tokenize
nltk.download('punkt') # Download Tokenizer.
text = "Natural language processing with Python is fun."
tokens = word_tokenize(text)
print(tokens)
text = "Life is from the inside out. When you shift on the inside, life shifts on the outside."
sent_tokenized_nltk = sent_tokenize(text)
print(sent_tokenized_nltk)
'''
['Natural', 'language', 'processing', 'with', 'Python', 'is', 'fun', '.']
['Life is from the inside out.', 'When you shift on the inside, life shifts on the outside.']
'''
spacy
import spacy
nlp = spacy.load('en_core_web_sm')
text = "Age is no guarantee of maturity."
doc = nlp(text)
spacy_tokenized_nltk = [token.text for token in doc]
print(spacy_tokenized_nltk)
text = "Life is from the inside out. When you shift on the inside, life shifts on the outside."
doc = nlp(text)
sent_tokenized_spacy = [sent.text for sent in doc.sents]
print(sent_tokenized_spacy)
'''
['Age', 'is', 'no', 'guarantee', 'of', 'maturity', '.']
['Life is from the inside out.', 'When you shift on the inside, life shifts on the outside.']
'''
Korean Tokenizer
구분 | KoNLPy | Mecab | Khaiii |
---|---|---|---|
특징 | 여러 한국어 형태소 분석기 포함 | 일본어 형태소 분석기를 한국어에 적용 | 카카오 개발, 딥러닝 기반 |
사용 목적 | 교육, 연구, 개발 | 대량 데이터 처리, 실시간 처리 | 정확도 중요한 연구 및 개발 |
장점 | 다양한 분석기 사용 가능 | 빠른 처리 속도, 높은 정확도 | 높은 정확도, 균형 잡힌 성능 |
단점 | 분석기마다 성능 차이, 일부 설치 복잡 | 설치 과정 복잡 | 설치 및 사용 복잡, 리소스 소모 큼 |
KoNLPy(Okt)
from konlpy.tag import Okt
text = '사람은 비슷한 일을 겪고도 똑같은 일을 반복한다. 사람은 쉽게 변하지 않는다'
okt = Okt()
print(okt.morphs(text)) # 형태소로 나눈다.
print(okt.morphs(text, stem = True)) # 형태소로 나눈 뒤 어간 추출
print(okt.nouns(text)) # 명사 추출
print(okt.phrases(text)) # 어절 단위로 추출
print(okt.pos(text)) # 품사 태깅
print(okt.pos(text, join = True)) # 품사 태깅 2
KoNLPy(kkma)
from konlpy.tag import Kkma
kkma = Kkma()
print(kkma.morphs(text)) # 형태소로 나눈다(토크나이징)
print(kkma.nouns(text)) # 명사 추출
print(kkma.pos(text)) # 품사 태깅
print(kkma.pos(text, join = True)) # 품사 태깅
#️⃣ Normaliztion
import nltk
from nltk.tokenize import word_tokenize
import re
import pandas as pd
from collections import Counter
import plotly.express as px
text = "Natural language processing, or NLP, is fun! Isn't it?"
original_tokens = word_tokenize(text)
print(original_tokens)
print(f'특수 문자 제거 전 토큰 개수 : {len(original_tokens)}개')
text_cleaned = re.sub('[^\w\s]', '', text)
tokens = word_tokenize(text_cleaned)
print(tokens)
print(f'특수 문자 제거 후 토큰 개수 : {len(tokens)}개')
IMDB = pd.read_csv('https://raw.githubusercontent.com/jin0choi1216/dataset/main/NLP/ENG/IMDB_Top10000.csv', index_col = 0)
def preprocess_text(text):
text = text.lower() # 소문자로
text = re.sub('[^\w\s]', '', text) # 문자, 숫자만 선택(특수문자 제거)
tokens = word_tokenize(text)
return tokens
IMDB['tokenized'] = IMDB['review'].apply(preprocess_text)
# 상위 토큰 10개 확인
tokenized_word_counts = Counter()
# 토큰화 된 각 리뷰 리스트를 카운터 객체에 업데이트
IMDB['tokenized'].apply(lambda x : tokenized_word_counts.update(x))
tokenized_word_counts.most_common(10)
# 토큰의 누적합, 순위 계산 후 분포 확인
most_common_tokens = tokenized_word_counts.most_common()
df_most_common = pd.DataFrame(most_common_tokens, columns = ['Token', 'Count'])
df_most_common['rank'] = df_most_common['Count'].rank(ascending=False) # 순위
df_most_common['cumSumCount'] = df_most_common['Count'].cumsum() # 누적합
df_most_common['cumSumCount'].plot()
# Top 100의 트리맵 확인
df_top100 = df_most_common.head(100)
fig = px.treemap(df_top100, path=['Token'], values='Count')
fig.show()
['Natural', 'language', 'processing', ',', 'or', 'NLP', ',', 'is', 'fun', '!', 'Is', "n't", 'it', '?'] 특수 문자 제거 전 토큰 개수 : 14개
['Natural', 'language', 'processing', 'or', 'NLP', 'is', 'fun', 'Isnt', 'it'] 특수 문자 제거 후 토큰 개수 : 9개
#️⃣ 어간 추출(Stemming)
단어의 어미를 제거하고 어간만 남기는 방법
단어의 다양한 형태를 하나의 형태로 통일
ex) “running”, “runs”, “ran” 등의 단어를 어간 추출을 적용하면 "run"으로 변환 됨
import nltk
nltk.download('wordnet')
from nltk.stem import PorterStemmer
stemmer = PorterStemmer()
words = 'jumping' # jumps, jumped ...
result = stemmer.stem(words)
print(result) # 'jump'
구분 | 영어 데이터 정규화 | 한국어 데이터 정규화 |
---|---|---|
대소문자 처리 | 대부분의 텍스트를 소문자로 변환 | 대소문자 구분이 없음 |
띄어쓰기 교정 | 일반적으로 띄어쓰기 오류가 적음 | 띄어쓰기 교정 중요 |
형태소 분석 | 일반적으로 사용하지 않음 | 형태소 분석을 통한 문장 분리 중요 |
어간/표제어 추출 | 어간 추출 또는 표제어 추출 사용 | 어간 추출보다 형태소 분석이 중요 |
불용어 처리 | ‘the’, ‘is’, ‘at’ 등 불용어 제거 | 조사, 어미 등의 처리가 중요 |
특수문자 처리 | 불필요한 특수문자 제거 | 불필요한 특수문자 제거 |
숫자 처리 | 숫자를 특정 토큰으로 대체 또는 제거 | 숫자를 특정 토큰으로 대체 또는 제거 |
단축어 확장 | ‘can’t’ → ‘cannot’ 등의 확장 | 해당사항 거의 없음 |
정규표현식
#️⃣ 정규식이란
특정한 패턴을 찾거나 대체, 추출하는 데 사용되는 문자열 검색 패턴
#️⃣ 찾기
regex(.)
.은 한 개의 임의의 문자를 나타냄. 예를 들어서 정규 표현식이 a.c라면, a와 c 사이에는 어떤 1개의 문자라도 올 수 있음.
akc, azc, avc, a5c, a!c와 같은 형태는 모두 a.c의 정규 표현식과 매치 됨
import re
r = re.compile("a.c")
r.search('kkk') # X
r.search('abc') # O
regex(?)
?는 ?앞의 문자가 존재할 수도 있고 존재하지 않을 수도 있는 경우
예를 들어서 정규 표현식이 ab?c라면 b는 있다고 취급할 수도 있고, 없다고 취급할 수도 있습니다.
즉, abc와 ac 모두 매치
r = re.compile("ab?c")
r.search('abc') # O
r.search('ac') # O
r.search('abbc') # X
regex(*)
*은 바로 앞의 문자가 0개 이상일 경우
앞의 문자는 존재하지 않을 수도 있으며, 또는 여러 개일 수도 있음.
정규 표현식이 ab*c라면 ac, abc, abbc, abbbc 등과 매치할 수 있으며 b의 개수는 무수히 많을 수 있음
r = re.compile("ab*c")
r.search('abc') # O
r.search('ac') # O
r.search('abbc') # O
regex(+)
+는 *와 유사합니다. 다른 점은 앞의 문자가 최소 1개 이상이어야 함
정규 표현식이 ab+c라고 한다면 ac는 매치되지 않음
하지만 abc, abbc, abbbc 등과 매치할 수 있으며 b의 개수는 무수히 많을 수 있음
r = re.compile("ab+c")
r.search('abc') # O
r.search('ac') # X
r.search('abbc') # O
regex(^)
^는 시작되는 문자열을 지정
정규표현식이 ^ab라면 문자열 ab로 시작되는 경우 매치
r = re.compile("^ab")
r.search('abc') # O
r.search('ac') # X
r.search('abbc') # O
r.search('zab') # X
regex({숫자})
문자에 해당 기호를 붙이면, 해당 문자를 숫자만큼 반복한 것
예를 들어서 정규 표현식이 ab{2}c라면 a와 c 사이에 b가 존재하면서 b가 2개인 문자열에 대해서 매치
r = re.compile("ab{2}c")
r.search('abc') # X
r.search('abbbc') # X
r.search('abbc') # O
r.search('zabbc') # O
regex({숫자1, 숫자2})
- 문자에 해당 기호를 붙이면, 해당 문자를 숫자1 이상 숫자2 이하만큼 반복합니다. 예를 들어서 정규 표현식이 ab{2,8}c라면 a와 c 사이에 b가 존재하면서 b는 2개 이상 8개 이하인 문자열에 대해서 매치합니다.
- 숫자2를 생략하면 끝까지 가능
# 숫자 1, 숫자 2
r = re.compile("ab{2,5}c")
r.search('abc') # X
r.search('abbbc') # O
r.search('abbc') # O
r.search('abbbbbbbbbbbbbbbbbbbbbc') # X
# 뒷자리 생략
r = re.compile("ab{2,}c")
r.search('abc') # X
r.search('abbbc') # O
r.search('abbc') # O
r.search('abbbbbbbbbbbbbbbbbbbbbc') # O
regex([])
[ ]안에 문자들을 넣으면 그 문자들 중 한 개의 문자와 매치라는 의미를 가집니다. 예를 들어서 정규 표현식이 [abc]라면, a 또는 b또는 c가 들어가있는 문자열과 매치됩니다. 범위를 지정하는 것도 가능합니다. [a-zA-Z]는 알파벳 전부를 의미하며, [0-9]는 숫자 전부를 의미합니다.
import re
r = re.compile("[abc]")
r.search('zzz') # X
r.search('zaz') # O
r.search('aaaaa') # O
r = re.compile("[a-z]") # 소문자 전체
r.search('zzz') # O
r.search('zaz') # O
r.search('aaaaa') # O
r.search('AAAAA') # X
r.search('111') # X
regex([^문자])
[^문자]는 ^기호 뒤에 붙은 문자들을 제외한 모든 문자를 매치하는 역할을 합니다. 예를 들어서 [^abc]라는 정규 표현식이 있다면, a 또는 b 또는 c가 들어간 문자열을 제외한 모든 문자열을 매치합니다.
r = re.compile("[^abc]")
r.search('a') # X
r.search('abc') # X
r.search('d') # O
r.search('ad') # O
r = re.compile("[^a-z]") # 소문자 전체
r.search('zzz') # X
r.search('zaz') # X
r.search('aaaaa') # X
r.search('AAAAA') # O
r.search('111') # O
re.match() vs re.search()
- search()가 정규 표현식 전체에 대해서 문자열이 매치하는지를 봅니다.
- match()는 문자열의 첫 부분부터 정규 표현식과 매치하는지를 확인합니다. 문자열 중간에 찾을 패턴이 있더라도 match 함수는 문자열의 시작에서 패턴이 일치하지 않으면 찾지 않습니다.
r = re.compile("ab.")
r.match("kkkabc") # 아무런 결과도 출력되지 않는다.
# 정규 표현식 전체에 대해서 문자열이 매치하는지 확인
r.search("kkkabc")
# 첫 부분부터 정규 표현식과 매치하는지를 확인
r.match("abckkk")
#️⃣ 데이터 나누기(split)
re.split()
# 공백 기준 분리
text = "사과 딸기 수박 메론 바나나"
re.split(" ", text)
# 줄바꿈 기준 분리
text = """사과
딸기
수박
메론
바나나"""
re.split("\\n", text)
# '+'를 기준으로 분리
text = "사과+딸기+수박+메론+바나나"
re.split("\\+", text)
#️⃣ 찾아서 출력
re.findall()
findall() 함수는 정규 표현식과 매치되는 모든 문자열들을 리스트로 리턴
단, 매치되는 문자열이 없다면 빈 리스트를 리턴
임의의 텍스트에 정규 표현식으로 숫자를 의미하는 규칙으로 findall()을 수행하면 전체 텍스트로부터 숫자만 찾아내서 리스트로 리턴
\\d : 정수
\\s : 공백
\\w : 문자
# 숫자 탐색
text = """이름 : 홍길동
전화번호 : 010 - 1234 - 5678
나이 : 30
성별 : 남"""
re.findall("\\d+", text) # d+ 숫자로 시작, 끝
#️⃣ 찾아서 대체
re.sub()
sub() 함수는 정규 표현식 패턴과 일치하는 문자열을 찾아 다른 문자열로 대체
아래와 같은 정제 작업에 많이 사용되는데, 영어 문장에 각주 등과 같은 이유로 특수 문자가 섞여있는 경우에 특수 문자를 제거하고 싶다면 알파벳 외의 문자는 공백으로 처리하는 등의 용도로 쓸 수 있음
text = "Regular expression : A regular expression, regex or regexp[1] (sometimes called a rational expression)[2][3] is, in theoretical computer science and formal language theory, a sequence of characters that define a search pattern."
preprocessed_text = re.sub('[^a-zA-Z]', ' ', text)
print(preprocessed_text)
'''
Regular expression A regular expression regex or regexp sometimes called a rational expression is in theoretical computer science and formal language theory a sequence of characters that define a search pattern
'''
#️⃣ 토큰화
NLTK에서는 정규 표현식을 사용해서 단어 토큰화를 수행하는 RegexpTokenizer를 지원
RegexpTokenizer()에서 괄호 안에 하나의 토큰으로 규정하기를 원하는 정규 표현식을 넣어서 토큰화를 수행
tokenizer2에서는 공백을 기준으로 토큰화
gaps=true는 해당 정규 표현식을 토큰으로 나누기 위한 기준으로 사용
만약 gaps=True라는 부분을 기재하지 않는다면, 토큰화의 결과는 공백들만 나오게 됨.
tokenizer2의 결과는 위의 tokenizer1의 결과와는 달리 아포스트로피나 온점을 제외하지 않고 토큰화가 수행 됨
from nltk.tokenize import RegexpTokenizer
text = "Only I can change me life, no one can do it for me. "
tokenizer1 = RegexpTokenizer("[\\w]+") # 문자 또는 숫자가 1개 이상인 경우를 의미합니다.
tokenizer2 = RegexpTokenizer("\\s+", gaps=True) # 공백을 기준으로 토큰화, gaps=true는 해당 정규 표현식을 토큰으로 나누기 위한 기준으로 사용
tokenizer3 = RegexpTokenizer("\\s+") # 정규식에 해당되는 데이터 불러오기
print(tokenizer1.tokenize(text))
print(tokenizer2.tokenize(text))
print(tokenizer3.tokenize(text))
'''
['Only', 'I', 'can', 'change', 'me', 'life', 'no', 'one', 'can', 'do', 'it', 'for', 'me'] ['Only', 'I', 'can', 'change', 'me', 'life,', 'no', 'one', 'can', 'do', 'it', 'for', 'me.'] [' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ']
'''
'Education > 새싹 TIL' 카테고리의 다른 글
새싹 AI데이터엔지니어 핀테커스 17주차 (화) - Word2Vec (1) | 2024.01.09 |
---|---|
새싹 AI데이터엔지니어 핀테커스 17주차 (월) - Vectorization & Word Embedding (2) | 2024.01.08 |
새싹 AI데이터엔지니어 핀테커스 16주차 (목) - LSTM Pytorch (1) | 2024.01.04 |
새싹 AI데이터엔지니어 핀테커스 16주차 (수) - Transfer Learning & RNN, LSTM, GRU (1) | 2024.01.03 |
새싹 AI데이터엔지니어 핀테커스 16주차 (화) - Object Detection (1) | 2024.01.02 |