NLTK¶

NLTK는 영어 텍스트 처리용 파이썬 라이브러리입니다.

설치¶

pip install nltk
import nltk
nltk.download('punkt')                      # 토큰화
nltk.download('stopwords')                  # 불용어
nltk.download('averaged_perceptron_tagger') # 품사 태깅
nltk.download('wordnet')                    # 어간 추출

주요 기능¶

  1. 토큰화 (띄어쓰기 기반으로 단어 분리)
  2. 불용어 제거 (the, is, a 같은 단어 제거)
  3. 품사 태깅 (명사, 동사 등 구분)
  4. 어간 추출 (동사/형용사를 기본형으로 변환, running → run)
In [5]:
!pip install nltk
Collecting nltk
  Downloading nltk-3.9.2-py3-none-any.whl.metadata (3.2 kB)
Requirement already satisfied: click in /usr/local/lib/python3.12/dist-packages (from nltk) (8.3.0)
Requirement already satisfied: joblib in /usr/local/lib/python3.12/dist-packages (from nltk) (1.5.2)
Requirement already satisfied: regex>=2021.8.3 in /usr/local/lib/python3.12/dist-packages (from nltk) (2025.9.18)
Requirement already satisfied: tqdm in /usr/local/lib/python3.12/dist-packages (from nltk) (4.67.1)
Downloading nltk-3.9.2-py3-none-any.whl (1.5 MB)
   ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 1.5/1.5 MB 12.0 MB/s  0:00:00
Installing collected packages: nltk
Successfully installed nltk-3.9.2
WARNING: Running pip as the 'root' user can result in broken permissions and conflicting behaviour with the system package manager, possibly rendering your system unusable. It is recommended to use a virtual environment instead: https://pip.pypa.io/warnings/venv. Use the --root-user-action option if you know what you are doing and want to suppress this warning.
In [5]:
import nltk
nltk.download('punkt')                      # 토큰화(pickle 파일 형식)
nltk.download('punkt_tab')                  # 토큰화(tabular 파일 형식)
nltk.download('stopwords')                  # 불용어
nltk.download('averaged_perceptron_tagger') # 품사 태깅
nltk.download('averaged_perceptron_tagger_eng')  # 영어 품사 태깅
nltk.download('wordnet')                    # 어간 추출
[nltk_data] Downloading package punkt to /root/nltk_data...
[nltk_data]   Package punkt is already up-to-date!
[nltk_data] Downloading package punkt_tab to /root/nltk_data...
[nltk_data]   Package punkt_tab is already up-to-date!
[nltk_data] Downloading package stopwords to /root/nltk_data...
[nltk_data]   Package stopwords is already up-to-date!
[nltk_data] Downloading package averaged_perceptron_tagger to
[nltk_data]     /root/nltk_data...
[nltk_data]   Package averaged_perceptron_tagger is already up-to-
[nltk_data]       date!
[nltk_data] Downloading package averaged_perceptron_tagger_eng to
[nltk_data]     /root/nltk_data...
[nltk_data]   Unzipping taggers/averaged_perceptron_tagger_eng.zip.
[nltk_data] Downloading package wordnet to /root/nltk_data...
[nltk_data]   Package wordnet is already up-to-date!
Out[5]:
True

1. 토큰화¶

In [1]:
import nltk
from nltk.tokenize import word_tokenize

# 예시 1: 구두점 분리
print('"I love Python!" :', word_tokenize("I love Python!"))

# 예시 2: 축약형
print('"I don\'t know" :', word_tokenize("I don't know"))

# 예시 3: 축약형 2
print('"We\'re happy" :', word_tokenize("We're happy"))

# 예시 4: 특수문자
print('"Hello, world!" :', word_tokenize("Hello, world!"))

# 예시 5: 숫자 + 기호
print('"Price: $100.50" :', word_tokenize("Price: $100.50"))

# 예시 6: 약어
print('"I live in U.S.A." :', word_tokenize("I live in U.S.A."))

# 예시 7: 이메일
print('"Email me at [email protected]" :', word_tokenize("Email me at [email protected]"))
"I love Python!" : ['I', 'love', 'Python', '!']
"I don't know" : ['I', 'do', "n't", 'know']
"We're happy" : ['We', "'re", 'happy']
"Hello, world!" : ['Hello', ',', 'world', '!']
"Price: $100.50" : ['Price', ':', '$', '100.50']
"I live in U.S.A." : ['I', 'live', 'in', 'U.S.A', '.']
"Email me at [email protected]" : ['Email', 'me', 'at', 'test', '@', 'email.com']

2. 불용어¶

  • 텍스트 분류 (스팸 필터, 감정 분석)
  • 검색 엔진 (중요 단어만 추출)
  • 키워드 추출
  • TF-IDF 계산 전
In [2]:
import nltk
from nltk.corpus import stopwords
from nltk.tokenize import word_tokenize

# 불용어 리스트
stop_words = set(stopwords.words('english'))
print(f"불용어 개수: {len(stop_words)}")
print(f"불용어 예시: {list(stop_words)[:100]}")

# 예시 1: 불용어 제거 전/후
text = "I love Python and it is amazing"
tokens = word_tokenize(text.lower())

print(f"\n원본: {tokens}")

filtered = [word for word in tokens if word not in stop_words]
print(f"불용어 제거 후: {filtered}")

# 예시 2: 문서 분류/검색할 때
text2 = "The cat is on the table"
tokens2 = word_tokenize(text2.lower())
filtered2 = [word for word in tokens2 if word not in stop_words]

print(f"\n원본: {tokens2}")
print(f"불용어 제거 후: {filtered2}")
불용어 개수: 198
불용어 예시: ["needn't", 'is', 'after', "wasn't", 'because', "it'd", 'during', 'wasn', 'no', 'with', 'be', "won't", 'so', 'above', 'don', 've', 'when', 'again', 'did', 'over', 'where', 'up', "i'm", 'y', 'once', 'have', 'can', 'under', 'such', "shouldn't", 'their', 'should', 'down', "it'll", "i've", 'mightn', 'by', 'were', 'why', "she'll", 'ourselves', 'your', 'haven', 'his', "we'd", 'was', 'will', "should've", 'as', 'needn', "we'll", "haven't", 'ours', 'some', 'weren', 'nor', 'its', 'here', 'about', "they're", "it's", "aren't", 'if', 'just', 'being', 'himself', "you'd", "isn't", "he's", 'her', 'has', 'through', 'between', 'for', 'off', 'at', 'yours', "she'd", 'same', "we're", 'mustn', "i'll", 'doing', 'this', "didn't", 'isn', 'the', "wouldn't", 'having', 'then', 'more', 'doesn', 'very', 'these', "weren't", 'didn', 'now', 'my', "mightn't", 'on']

원본: ['i', 'love', 'python', 'and', 'it', 'is', 'amazing']
불용어 제거 후: ['love', 'python', 'amazing']

원본: ['the', 'cat', 'is', 'on', 'the', 'table']
불용어 제거 후: ['cat', 'table']

3. 품사(POS, Part of Speech): 단어의 문법적 역할¶

  • NN : 명사 (Noun)

  • NNP : 고유명사 (Proper Noun)

  • VB : 동사 (Verb)

  • VBP : 현재형 동사

  • VBD : 과거형 동사

  • JJ : 형용사 (Adjective)

  • RB : 부사 (Adverb)

  • PRP : 대명사 (Pronoun)

  • DT : 관사 (Determiner, the/a)

  • IN : 전치사 (Preposition)

  • 영어 vs 한국어 품사 차이

    • 영어는 관사(a, the)와 동사 시제 변화(eat/ate/eating)가 있지만, 한국어는 조사(은/는, 을/를)와 어미(-습니다, -해요)가 있습니다.
    • 기본 품사(명사, 동사, 형용사, 부사)는 비슷하지만, 문법 구조가 달라서 품사 체계도 다릅니다.
In [7]:
import nltk
from nltk import pos_tag
from nltk.tokenize import word_tokenize

# 품사 태그 한글 설명
pos_dict = {
    'NN': '명사',
    'NNP': '고유명사',
    'NNS': '복수명사',
    'VB': '동사원형',
    'VBP': '현재동사',
    'VBD': '과거동사',
    'VBG': '현재분사',
    'JJ': '형용사',
    'RB': '부사',
    'PRP': '대명사',
    'DT': '관사',
    'IN': '전치사',
    'MD': '조동사',
    'CC': '접속사',
    '.': '마침표'
}

# 예시 1: 기본 품사 태깅
text = "I love Python programming"
tokens = word_tokenize(text)
tagged = pos_tag(tokens)

print("예시 1:")
for word, tag in tagged:
    korean = pos_dict.get(tag, tag)
    print(f'"{word}" → {tag} ({korean})')

# 예시 2: 같은 단어, 다른 품사
text2 = "They can fish. I have a can."
tokens2 = word_tokenize(text2)
tagged2 = pos_tag(tokens2)

print("\n예시 2 (같은 단어 'can'):")
for word, tag in tagged2:
    korean = pos_dict.get(tag, tag)
    print(f'"{word}" → {tag} ({korean})')

# 예시 3: 다양한 품사
text3 = "The quick brown fox jumps over the lazy dog"
tokens3 = word_tokenize(text3)
tagged3 = pos_tag(tokens3)

print("\n예시 3:")
for word, tag in tagged3:
    korean = pos_dict.get(tag, tag)
    print(f'"{word}" → {tag} ({korean})')
예시 1:
"I" → PRP (대명사)
"love" → VBP (현재동사)
"Python" → NNP (고유명사)
"programming" → NN (명사)

예시 2 (같은 단어 'can'):
"They" → PRP (대명사)
"can" → MD (조동사)
"fish" → VB (동사원형)
"." → . (마침표)
"I" → PRP (대명사)
"have" → VBP (현재동사)
"a" → DT (관사)
"can" → MD (조동사)
"." → . (마침표)

예시 3:
"The" → DT (관사)
"quick" → JJ (형용사)
"brown" → NN (명사)
"fox" → NN (명사)
"jumps" → VBZ (VBZ)
"over" → IN (전치사)
"the" → DT (관사)
"lazy" → JJ (형용사)
"dog" → NN (명사)

4. 어간¶

  • 검색 엔진: "running" 검색해도 "run" 포함 문서 찾기
  • 텍스트 분석: 같은 의미 단어를 하나로 통일 (runs, running → run)
  • 단어 개수 줄이기: Vocabulary 크기 축소
    • 영어: 어간 추출 = Stemming (단순 규칙)
    • 한국어: 형태소 분석 필요 (KoNLPy 사용)
In [8]:
import nltk
from nltk.stem import PorterStemmer
from nltk.tokenize import word_tokenize

# Stemmer 생성
stemmer = PorterStemmer()

# 예시 1: 동사 어간 추출
words1 = ["running", "runs", "run", "runner"]
print("예시 1 (동사):")
for word in words1:
    stem = stemmer.stem(word)
    print(f'"{word}" → "{stem}"')

# 예시 2: 형용사 어간 추출
words2 = ["happy", "happier", "happiest", "happiness"]
print("\n예시 2 (형용사):")
for word in words2:
    stem = stemmer.stem(word)
    print(f'"{word}" → "{stem}"')

# 예시 3: 다양한 단어
words3 = ["studies", "studying", "studied", "study"]
print("\n예시 3 (study 변형):")
for word in words3:
    stem = stemmer.stem(word)
    print(f'"{word}" → "{stem}"')

# 예시 4: 문장에서 어간 추출
text = "I am running and jumping quickly"
tokens = word_tokenize(text)
print("\n예시 4 (문장):")
for word in tokens:
    stem = stemmer.stem(word.lower())
    print(f'"{word}" → "{stem}"')
예시 1 (동사):
"running" → "run"
"runs" → "run"
"run" → "run"
"runner" → "runner"

예시 2 (형용사):
"happy" → "happi"
"happier" → "happier"
"happiest" → "happiest"
"happiness" → "happi"

예시 3 (study 변형):
"studies" → "studi"
"studying" → "studi"
"studied" → "studi"
"study" → "studi"

예시 4 (문장):
"I" → "i"
"am" → "am"
"running" → "run"
"and" → "and"
"jumping" → "jump"
"quickly" → "quickli"

In [1]:
import setup_env
--------------------------------------------------------------------------------
=== Hardware Acceleration ===
PyTorch version: 2.9.0a0+145a3a7bda.nv25.10
Using NVIDIA GPU (CUDA)
   CUDA version: 13.0
   GPU name: NVIDIA GeForce RTX 5070 Ti
   GPU count: 1
   Total GPU memory: 15.92 GB
   Allocated memory: 0.00 GB
   Free memory: 15.92 GB
Device: cuda

=== Matplotlib Settings ===
✅ Font: NanumGothic

=== System Info ===
OS: Ubuntu 24.04.3 LTS (Noble Numbat)
    Kernel: 6.6.87.2-microsoft-standard-WSL2
Architecture: x86_64
Python: 3.12.3
Working directory: /workspace/ai-deeplearning/tutorial

=== Library Versions ===
NumPy: 2.1.0
Pandas: 3.0.0
Matplotlib: 3.10.7
Scikit-learn: 1.7.2
OpenCV: Not installed → !pip install -q opencv-python
Pillow: 12.0.0
Seaborn: 0.13.2
TensorFlow: Not installed → !pip install -q tensorflow
Transformers: 4.40.1
TorchVision: 0.24.0a0+094e7af5

=== Environment setup completed ===
--------------------------------------------------------------------------------

=== Visualizing Test Plot (Wide View) ===
No description has been provided for this image
=== GPU Usage Code Snippet ===
Device set to: cuda
----------------------------------------
# 아래 코드를 복사해서 모델과 데이터를 GPU로 보내세요:
model = YourModel().to(device)
data = data.to(device)
----------------------------------------

=== Environment setup completed ===
--------------------------------------------------------------------------------

연도 모델 핵심 아이디어 한계 인간 기준 돌파
1986 RNN 입력+이전hidden을 같이 계산해서 순차처리 긴 문장 앞부분 hidden이 희석됨 -
1997 LSTM hidden(단기기억)+cell(장기기억)으로 분리, 게이트로 뭘 기억할지 선택 여전히 순차처리, 느림 -
2013 Word2Vec 단어를 의미있는 벡터로 변환 (왕-남자+여자=여왕) 문맥 무시, 같은 단어=같은 벡터 -
2014 GloVe Word2Vec 개선, 단어 동시출현 통계 활용 여전히 정적임베딩 한계 -
2014 Seq2Seq LSTM 인코더(압축)+디코더(복원) 구조로 번역 마지막 hidden 1개만 디코더에 전달, 정보손실 -
2015 Attention 인코더의 모든 hidden을 저장 후 디코더가 중요한 hidden에 집중 RNN 기반이라 여전히 순차처리 느림 -
2016 FastText 부분단어(subword) 단위 학습, 처음보는 단어도 이해 여전히 정적임베딩 한계 -
2017 Transformer Attention만으로 순차처리 제거, 모든 단어 병렬처리 데이터/연산량 많이 필요 -
2018 BERT Transformer로 양방향 사전학습, 문맥임베딩(동적임베딩) 등장 - 독해(SQuAD) 인간 91.2% 초월
2019 RoBERTa 등 BERT 개선 - 문장분류(GLUE) 87점 초월
2020 GPT-3 초거대 Transformer, 다음단어맞추기로 사전학습, few-shot 학습 엄청난 컴퓨팅 비용 번역(BLEU) 인간번역 수준 도달
2021+ GPT-4 등 멀티모달, 초거대화 긴 문맥/상식/추론은 여전히 인간 우위 영역 존재 상식추론 ~90% 초월

시리즈 1 - 텍스트 분류 (AG News)¶

Step 모델 임베딩 임베딩 학습 여부 비고
0 MLP 원핫인코딩 - 기초 베이스라인
1 CNN TF-IDF - 단어 중요도 반영
2 RNN Word2Vec 고정 (freeze) 순서 처리 시작
3 LSTM GloVe 고정 (pre-trained) 장기기억 추가
4 Attention FastText 학습 중 업데이트 (fine-tune) 중요 단어 집중 + 부분단어 학습
5 Transformer 학습가능 임베딩 처음부터 학습 소형 구조, 오버핏 감수 (개념 시연)
6 BERT 문맥임베딩 (사전학습) 파인튜닝 최종 최강 모델

시리즈 2 - 생성/시퀀스 모델 (별도 튜토리얼)¶

Step 모델 태스크 임베딩 비고
0 Seq2Seq (LSTM) 번역/요약 GloVe 고정 인코더-디코더 기초
1 Seq2Seq + Attention 번역/요약 GloVe fine-tune 중요 단어 집중
2 오토인코더 문장 압축/복원 학습가능 임베딩 비지도학습
3 Transformer (풀버전) 번역 학습가능 임베딩 인코더+디코더 풀구조
4 GPT 텍스트 생성 문맥임베딩 (사전학습) 디코더만, 파인튜닝

AG News Dataset¶

개요¶

AG News는 뉴스 기사 분류를 위한 대표적인 텍스트 분류 데이터셋입니다. 학술 검색 엔진인 AG's corpus of news articles에서 수집된 뉴스 기사들로 구성되어 있습니다.

데이터셋 구성¶

  • 훈련 데이터: 120,000개
  • 테스트 데이터: 7,600개
  • 총 데이터: 127,600개

카테고리 (4개 클래스)¶

  1. World (세계 뉴스), 2. Sports (스포츠), 3. Business (비즈니스/경제), 4. Sci/Tech (과학/기술)
In [2]:
import os

# 디렉토리 생성
os.makedirs('./data/ag_news', exist_ok=True)

# 이미 다운받은게 있으면 스킵
base_url = "https://raw.githubusercontent.com/mhjabreel/CharCnn_Keras/master/data/ag_news_csv"

for filename in ['train.csv', 'test.csv']:
    filepath = f'./data/ag_news/{filename}'
    if os.path.exists(filepath):
        print(f"✅ {filename} 이미 존재 → 스킵")
    else:
        print(f"⬇️ {filename} 다운로드 중...")
        os.system(f'wget -P ./data/ag_news {base_url}/{filename}')
        print(f"✅ {filename} 다운로드 완료")
✅ train.csv 이미 존재 → 스킵
✅ test.csv 이미 존재 → 스킵
In [3]:
import pandas as pd

df_train = pd.read_csv('./data/ag_news/train.csv', header=None)
df_test  = pd.read_csv('./data/ag_news/test.csv',  header=None)

print("컬럼 수:", df_train.shape)
print("\n샘플 데이터:")
print(df_train.head(3))
print("\n결측값:", df_train.isnull().sum().tolist())
print("레이블 분포:", df_train[0].value_counts().to_dict())
컬럼 수: (120000, 3)

샘플 데이터:
   0                                                    1  \
0  3    Wall St. Bears Claw Back Into the Black (Reuters)   
1  3  Carlyle Looks Toward Commercial Aerospace (Reuters)   
2  3      Oil and Economy Cloud Stocks' Outlook (Reuters)   

                                                                                                                                                                                                                        2  
0                                                                                                                          Reuters - Short-sellers, Wall Street's dwindling\band of ultra-cynics, are seeing green again.  
1  Reuters - Private investment firm Carlyle Group,\which has a reputation for making well-timed and occasionally\controversial plays in the defense industry, has quietly placed\its bets on another part of the market.  
2                                Reuters - Soaring crude prices plus worries\about the economy and the outlook for earnings are expected to\hang over the stock market next week during the depth of the\summer doldrums.  

결측값: [0, 0, 0]
레이블 분포: {3: 30000, 4: 30000, 2: 30000, 1: 30000}
In [4]:
import pandas as pd
import re
import matplotlib.pyplot as plt
import seaborn as sns
from sklearn.model_selection import train_test_split

df_train = pd.read_csv('./data/ag_news/train.csv', header=None, names=['label', 'title', 'body'])
df_test  = pd.read_csv('./data/ag_news/test.csv',  header=None, names=['label', 'title', 'body'])

def preprocess(title, body):
    text = str(title) + ' ' + str(body)
    text = text.lower()
    text = re.sub(r'[^a-z0-9\s]', ' ', text)
    text = re.sub(r'\s+', ' ', text).strip()
    return text

df_train['text'] = df_train.apply(lambda r: preprocess(r['title'], r['body']), axis=1)
df_test['text']  = df_test.apply(lambda r: preprocess(r['title'], r['body']), axis=1)
df_train['label'] = df_train['label'] - 1
df_test['label']  = df_test['label'] - 1

df_tr, df_val = train_test_split(df_train, test_size=0.2, random_state=42, stratify=df_train['label'])

label_names = ['World', 'Sports', 'Business', 'Sci/Tech']
print(f"훈련셋:   {len(df_tr):,}개")
print(f"검증셋:   {len(df_val):,}개")
print(f"테스트셋: {len(df_test):,}개")

# ── 1. 데이터셋 크기 비교 ──
fig, axes = plt.subplots(1, 3, figsize=(15, 4))

splits = {'Train': df_tr, 'Val': df_val, 'Test': df_test}
colors = ['#4C72B0', '#DD8452', '#55A868']
for ax, (name, df), color in zip(axes, splits.items(), colors):
    counts = df['label'].value_counts().sort_index()
    ax.bar([label_names[i] for i in counts.index], counts.values, color=color, edgecolor='white')
    ax.set_title(f'{name} ({len(df):,}개)')
    ax.set_ylabel('개수')
    for i, v in enumerate(counts.values):
        ax.text(i, v + 100, str(v), ha='center', fontweight='bold', fontsize=9)

plt.suptitle('데이터셋 분할 및 레이블 분포', fontsize=13, fontweight='bold')
plt.tight_layout()
plt.show()

# ── 2. 텍스트 길이 분포 ──
for df, name in [(df_tr, 'Train'), (df_val, 'Val'), (df_test, 'Test')]:
    df['text_len'] = df['text'].apply(lambda x: len(x.split()))

fig, axes = plt.subplots(1, 3, figsize=(15, 4))
for ax, (name, df), color in zip(axes, splits.items(), colors):
    ax.hist(df['text_len'], bins=50, color=color, edgecolor='white')
    ax.axvline(df['text_len'].mean(), color='red', linestyle='--', label=f'평균: {df["text_len"].mean():.0f}')
    ax.set_title(f'{name} 텍스트 길이 분포')
    ax.set_xlabel('단어 수')
    ax.set_ylabel('빈도')
    ax.legend()

plt.suptitle('텍스트 길이 분포', fontsize=13, fontweight='bold')
plt.tight_layout()
plt.show()

# ── 3. 데이터셋 비율 파이차트 ──
fig, ax = plt.subplots(figsize=(6, 6))
sizes = [len(df_tr), len(df_val), len(df_test)]
labels = [f'Train\n{len(df_tr):,}개', f'Val\n{len(df_val):,}개', f'Test\n{len(df_test):,}개']
ax.pie(sizes, labels=labels, colors=colors, autopct='%1.1f%%',
       startangle=90, wedgeprops=dict(edgecolor='white', linewidth=2))
ax.set_title('데이터셋 분할 비율', fontsize=13, fontweight='bold')
plt.show()
훈련셋:   96,000개
검증셋:   24,000개
테스트셋: 7,600개
No description has been provided for this image
No description has been provided for this image
No description has been provided for this image