tokens = []
for token in doc:
tokens.append(token.text)
import pandas as pd
import numpy as np
import seaborn as sns
import matplotlib.pyplot as plt
import re
import torch
import torch.nn as nn
import torch.optim as optim
from torch.utils.data import DataLoader, Dataset
from sklearn.model_selection import train_test_split
from sklearn.preprocessing import LabelEncoder
from torchtext.data.utils import get_tokenizer
from torchtext.vocab import build_vocab_from_iterator
from torch.nn.utils.rnn import pad_sequence
#데이터 전처리
def preprocess_text(content):
if isinstance(content, float):
return ''
content = content.lower()
content = re.sub(r'[^/w/s]', '', content)
content = re.sub(r'\d+', '', content)
content = content.strip()
return content or '<pad>'
data[['userName', 'content']] = data[['userName', 'content']].apply(lambda col: col.apply(preprocess_text))
def preprocess_score(score):
return int(score) - 1
class ReviewDataset(Dataset):
def __init__(self, content, score, preprocess_text, preprocess_score):
self.content = content.to_numpy()
self.score = score.to_numpy()
self.preprocess_text = preprocess_text
self.preprocess_score = preprocess_score
def __len__(self):
return len(self.content)
def __getitem__(self, idx):
content = self.preprocess_text(self.content[idx])
score = self.preprocess_score(self.score[idx])
#tokenized_content = self.preprocess_text(content).split()
indexed_content = torch.tensor([vocab[token] for token in content.split()])
return indexed_content, torch.tensor(score)
train_content, test_content, train_score, test_score = train_test_split(
data['content'], data['score'], test_size=0.2, random_state=42)
batch_size = 64
train_dataset = ReviewDataset(train_content, train_score, preprocess_text, preprocess_score)
train_dataload = DataLoader(train_dataset, batch_size = batch_size, shuffle = True)
test_dataset = ReviewDataset(test_content, test_score, preprocess_text, preprocess_score)
test_dataload = DataLoader(test_dataset, batch_size = batch_size, shuffle = True)
def content_text(content):
for text in content:
yield text.split()
vocab = build_vocab_from_iterator(content_text(data['content']), specials=['<unk>'])
vocab.set_default_index(vocab['<unk>'])
class LSTMModel(nn.Module):
def __init__(self, vocab_size, embed_dim, hidden_dim, output_dim):
super(LSTMModel, self).__init__()
self.embedding = nn.EmbeddingBag(vocab_size, embed_dim, sparse = True)
self.lstm = nn.LSTM(embed_dim, hidden_dim, batch_first = True)
self.fc = nn.Linear(hidden_dim, output_dim)
def forward(self, content):
embedded = self.embedding(content)
output, (hidden, cell) = self.lstm(embedded.unsqueeze(1))
return self.fc(hidden[-1])
Vocab_size = len(vocab)
Embed_dim = 64
Hidden_dim = 128
Output_dim = len(set(data['score']))
model = LSTMModel(Vocab_size, Embed_dim, Hidden_dim, Output_dim)
optimizer = optim.SGD(model.parameters(), lr = 0.01)
criterion = nn.CrossEntropyLoss()
def predict_review(model, review):
processed_review = preprocess_text(review)
tensor_review = torch.tensor([vocab[token] for token in processed_review.split()]).unsqueeze(0)
with torch.no_grad():
output = model(tensor_review)
predicted_score = torch.argmax(output, dim = 1).item() + 1
return predicted_score
new_review = "This app is terrible"
predicted_score = predict_review(model, new_review)
print(f'Predicted Score: {predicted_score}')
제가 만든 전체 코드입니다.
솔직히 새로운 개념들하고 서로간의 관계를 확실히 이해하기 위해 전체적으로 복습을 해보겠습니다.
import pandas as pd # 데이터 처리를 위한 라이브러리
import numpy as np # 수치 계산을 위한 라이브러리
import seaborn as sns # 데이터 시각화
import matplotlib.pyplot as plt # 그래프 생성
import re # 정규표현식 처리
import torch # 딥러닝 프레임워크
import torch.nn as nn # 신경망 구성요소
import torch.optim as optim # 최적화 알고리즘
from torch.utils.data import DataLoader, Dataset # 데이터 로딩 도구
from sklearn.model_selection import train_test_split # 데이터 분할
from sklearn.preprocessing import LabelEncoder # 레이블 인코딩
from torchtext.data.utils import get_tokenizer # 텍스트 토큰화
from torchtext.vocab import build_vocab_from_iterator # 단어 사전 생성
from torch.nn.utils.rnn import pad_sequence # 시퀀스 패딩
1. import pandas as pd
역할: pandas는 데이터 조작 및 분석을 위한 라이브러리입니다. 표 형식의 데이터(예: 데이터프레임)를 쉽게 다룰 수 있도록 다양한 기능을 제공합니다. 데이터 읽기, 쓰기, 필터링, 정렬, 집계 등의 작업을 쉽게 할 수 있습니다.
2. import numpy as np
역할: numpy는 고성능 수치 계산을 위한 라이브러리입니다. 다차원 배열 객체인 ndarray를 제공하며, 수학적 연산, 배열 조작, 선형 대수 등 다양한 수치 계산을 효율적으로 수행할 수 있습니다. 주로 데이터 분석과 과학 계산에 사용됩니다.
3. import seaborn as sns
역할: seaborn은 데이터 시각화를 위한 라이브러리로, matplotlib 기반 위에 구축되었습니다. 통계적 데이터 시각화에 중점을 두고 있으며, 고급 시각화를 손쉽게 만들 수 있는 함수들을 제공합니다. 다양한 스타일과 색상 테마를 제공하여 시각적으로 매력적인 그래프를 생성할 수 있습니다.
4. import matplotlib.pyplot as plt
역할: matplotlib의 pyplot 모듈은 2D 그래프를 그리는 데 사용됩니다. 그래프의 다양한 종류(선 그래프, 막대 그래프, 히스토그램 등)를 생성할 수 있으며, 세부 설정을 통해 그래프의 외관을 조정할 수 있습니다. 데이터 시각화에서 가장 널리 사용되는 라이브러리 중 하나입니다.
5. import re
역할: re는 정규 표현식을 처리하기 위한 모듈입니다. 문자열 검색, 매칭, 치환 등을 효율적으로 수행할 수 있게 해줍니다. 텍스트 전처리에서 특정 패턴의 문자열을 찾거나 수정하는 데 자주 사용됩니다.
6. import torch
역할: torch는 PyTorch라는 딥러닝 프레임워크의 기본 모듈입니다. 텐서(Tensor) 연산, 자동 미분, GPU 가속 등을 지원하여 신경망을 구성하고 훈련하는 데 필요한 기능을 제공합니다. 머신러닝 및 딥러닝 프로젝트에서 널리 사용됩니다.
7. import torch.nn as nn
역할: torch.nn 모듈은 신경망을 구성하는 다양한 계층(레이어)과 함수들을 제공합니다. 선형 레이어, 컨볼루션 레이어, 활성화 함수 등 신경망의 기본 구성 요소를 쉽게 만들 수 있도록 도와줍니다. 모델 정의와 구축에 필수적인 요소입니다.
8. import torch.optim as optim
역할: torch.optim 모듈은 딥러닝 모델의 학습을 위한 최적화 알고리즘을 제공합니다. SGD(확률적 경사 하강법), Adam, RMSprop 등의 다양한 최적화 기법을 사용할 수 있으며, 모델 파라미터 업데이트를 관리하는 데 필요한 기능을 갖추고 있습니다.
9. from torch.utils.data import DataLoader, Dataset
역할:
Dataset: 데이터셋을 정의하는 클래스입니다. 데이터를 어떻게 불러올지, 얼마나 많은 데이터가 있는지 등을 정의합니다.
DataLoader: 데이터를 배치 단위로 불러오는 도구입니다. 학습 시 데이터 샘플을 랜덤으로 섞거나, 여러 스레드로 데이터를 로드하는 등의 기능을 제공합니다.
10. from sklearn.model_selection import train_test_split
역할: sklearn의 train_test_split 함수는 주어진 데이터를 학습용과 테스트용으로 나누는 데 사용됩니다. 모델을 훈련할 데이터와 평가할 데이터를 분리하여 모델의 성능을 검증하는 데 유용합니다.
11. from sklearn.preprocessing import LabelEncoder
역할: LabelEncoder는 범주형 데이터를 숫자로 변환하는 도구입니다. 머신러닝 모델은 일반적으로 숫자 데이터만 처리할 수 있기 때문에, 텍스트나 범주형 변수를 숫자로 변환할 때 사용됩니다.
12. from torchtext.data.utils import get_tokenizer
역할: get_tokenizer는 텍스트 데이터를 토큰화하는 데 사용되는 도구입니다. 문장을 단어로 나누어 모델 입력으로 사용할 수 있는 형식으로 변환하는 데 유용합니다.
13. from torchtext.vocab import build_vocab_from_iterator
역할: build_vocab_from_iterator는 주어진 텍스트 데이터를 기반으로 단어 사전을 생성하는 함수입니다. 각 단어에 고유한 인덱스를 부여하여 모델이 입력 데이터를 처리할 수 있도록 돕습니다.
14. from torch.nn.utils.rnn import pad_sequence
역할: pad_sequence는 가변 길이의 시퀀스(예: 문장)를 동일한 길이로 패딩하는 데 사용됩니다. LSTM과 같은 순환 신경망 모델은 입력의 길이가 같아야 하므로, 짧은 시퀀스에 패딩을 추가하여 길이를 맞춥니다.
중요 개념들
먼저 깊게 다루기전에, 이 코드들에서 알아야 할 기본 정보들을 알아보겠습니다.(참고로 1부터 끝까지 전부 설명하는 바입니다)
1.데이터 전처리 (Data Preprocessing)
데이터 전처리는 머신러닝 모델에 입력하기 전에 데이터를 정제하고 변환하는 과정입니다. 이 단계에서는 다음과 같은 작업이 포함됩니다:
결측치 처리: 결측값을 제거하거나 대체하는 방법. 예를 들어, 평균값으로 대체하거나 삭제합니다.
형식 변환: 날짜 형식 변환, 문자열 인코딩 등.
정규화 및 표준화: 특성의 범위를 조정하여 모델의 학습을 돕습니다.
결측치 처리
1. 결측치 처리 -> 결측치 처리 방식에는 결측값 제거와 대체가 있습니다.
결측값 제거 ->
dropna(): 결측값이 있는 행이나 열을 제거합니다.
import pandas as pd # 예제 데이터 df = pd.DataFrame({ 'A': [1, 2, None, 4], 'B': [None, 2, 3, None], 'C': [1, None, 3, 4] }) # 결측값이 있는 행 제거 df_drop_row = df.dropna() # 결측값이 있는 열 제거 df_drop_column = df.dropna(axis=1)
결측값 대체 ->
fillna(): 결측값을 특정 값이나 계산된 값(평균, 중앙값 등)으로 대체합니다.
df_fill_mean = df.fillna(df.mean()) # 특정 값으로 대체 df_fill_zero = df.fillna(0)
2. 형식 변환 -> 형식 변환은 주로 날짜 형식 변환과 문자열 인코딩으로 나뉩니다.
pd.to_datetime(): 날짜 형식 변환을 돕는 함수입니다.
.dt: 날짜에서 특정 속성(연도, 월 등)을 추출하는 속성입니다.
# 날짜 형식으로 변환 df['date'] = pd.to_datetime(df['date']) # 날짜에서 연도 추출 df['year'] = df['date'].dt.year
문자열 인코딩
여기서 문자열 인코딩이란?
문자열 인코딩은 문자(텍스트)를 컴퓨터가 이해할 수 있는 이진수 형태로 변환하는 과정입니다. 컴퓨터는 0과 1로 이루어진 이진수만 이해할 수 있기 때문에, 우리가 사용하는 글자나 문장을 숫자로 바꿔주는 인코딩이 필요합니다.
pd.get_dummies(): 문자열 카테고리를 수치로 변환(원-핫 인코딩).
LabelEncoder(): 레이블 인코딩.from sklearn.preprocessing import LabelEncoder # 원-핫 인코딩 df_encoded = pd.get_dummies(df, columns=['category_column']) # 레이블 인코딩 le = LabelEncoder() df['category_column'] = le.fit_transform(df['category_column'])
여기서 fit.transform과 transform에 차이를 한번 더 알아보겠습니다.
fit_transform과 transform의 차이는 머신 러닝에서 데이터 전처리 과정을 수행할 때 중요한 개념입니다. fit이 포함된 함수와 포함되지 않은 함수는 데이터에 대한 학습과 적용 여부에서 차이를 보입니다.
fit ->
fit은 모델이 데이터를 학습하는 과정입니다. 예를 들어, 데이터 스케일링을 할 때 fit 메서드는 데이터의 평균과 표준편차를 계산하거나, 최솟값과 최댓값을 알아내서 어떻게 변환해야 할지 기준을 세워요. fit은 주로 훈련 데이터(X_train)에 사용됩니다.
transform ->
transform은 학습된 기준에 따라 데이터를 변환하는 과정입니다. transform은 새로운 데이터에 fit에서 학습한 기준을 적용하는 것인데요. 예를 들어, 테스트 데이터(X_test)에 적용할 때는 fit을 사용하지 않고 transform만 사용합니다. 이미 학습된 평균과 표준편차 기준에 따라 데이터를 스케일링하는 방식이죠.
fit_transform ->
fit_transform은 fit과 transform을 한 번에 수행하는 메서드예요. 주로 훈련 데이터에 사용되며, 데이터를 학습하면서 동시에 변환을 적용할 수 있게 합니다. 예를 들어, 훈련 데이터를 스케일링할 때 fit_transform을 사용하면 평균과 표준편차를 계산한 후 바로 그 기준에 맞춰 변환된 데이터를 반환합니다.
from sklearn.preprocessing import StandardScaler scaler = StandardScaler() # 1. 훈련 데이터에 `fit_transform`을 사용하여 스케일링 X_train_scaled = scaler.fit_transform(X_train) # 2. 테스트 데이터에 `transform`만 사용하여 훈련 데이터의 기준으로 스케일링 X_test_scaled = scaler.transform(X_test)
X_train_scaled = scaler.fit_transform(X_train): 훈련 데이터를 기준으로 평균과 표준편차를 계산(fit)하고, 이 기준을 적용해 데이터를 변환(transform)합니다.
X_test_scaled = scaler.transform(X_test): 테스트 데이터에 fit을 다시 하지 않고, 이미 계산된 평균과 표준편차로 변환합니다.
3. 정규화 및 표준화 -> 정규화와 표준화는 모델이 학습하기 좋은 데이터 스케일로 조정하는 방법입니다.
정규화 (Normalization) ->
MinMaxScaler(): 최소값과 최대값을 기준으로 데이터를 0~1 범위로 변환합니다.
from sklearn.preprocessing import MinMaxScaler # 정규화 scaler = MinMaxScaler() df_normalized = scaler.fit_transform(df)
표준화 (Standardization) ->
StandardScaler(): 평균을 0, 표준편차를 1로 설정하여 데이터를 변환합니다.from sklearn.preprocessing import StandardScaler # 표준화 scaler = StandardScaler() df_standardized = scaler.fit_transform(df)
텍스트 토큰화
텍스트 토큰화 (Tokenization)
텍스트를 더 작은 단위인 토큰으로 나누는 과정입니다. 토큰은 일반적으로 단어, 문장, 또는 하위 단위(서브워드)가 될 수 있습니다.
종류:
단어 기반 토큰화: 문장을 단어로 나눕니다. 예를 들어, "I love banana"을 ["I", "love", "banana"]으로 분리합니다.
서브워드 토큰화: 단어를 더 작은 단위로 나누어, 자주 나타나는 부분 단어를 사용합니다. 이는 OOV(Out Of Vocabulary) 문제를 줄이는 데 도움이 됩니다.
단어 기반 토큰화 (Word-based Tokenization)
단어 기반 토큰화는 문장을 단어 단위로 나누는 방식으로, 가장 일반적이고 직관적인 토큰화 방법입니다.
nltk.word_tokenize(): nltk 라이브러리에서 제공하는 기본적인 단어 단위 토큰화 함수입니다.
spacy.tokenizer: spaCy에서 제공하는 토큰화 객체로, 단어 단위뿐 아니라 다양한 토큰화 옵션을 제공합니다.
NLTK (Natural Language Toolkit) 사용 ->
NLTK는 자연어 처리에 널리 쓰이는 라이브러리로, 텍스트를 토큰화, 품사 태깅, 파싱, 개체명 인식(NER) 등의 작업을 수행할 수 있습니다.
import nltk from nltk.tokenize import word_tokenize sentence = "I love banana" tokens = word_tokenize(sentence) print(tokens) # ['I', 'love', 'banana']
spaCy 사용 ->
spaCy는 빠르고 효율적인 자연어 처리 라이브러리로, 토큰화 외에도 품사 태깅, 의존 구문 분석, 개체명 인식 등 고급 NLP 기능을 제공합니다. en_core_web_sm은 영어로 학습된 모델 중 작은 사이즈의 모델입니다.import spacy nlp = spacy.load("en_core_web_sm") doc = nlp(sentence) tokens_spacy = [token.text for token in doc] print(tokens_spacy) # ['I', 'love', 'machine', 'learning']
여기서 spacy.load란, spaCy에서 특정 언어 모델(예: en_core_web_sm)을 불러와서 NLP 작업을 할 수 있는 환경을 준비하는 함수입니다. 그리고 로드한 모델을 기반으로 NLP 파이프라인을 생성합니다. 이 파이프라인은 텍스트를 분석할 때 필요한 여러 처리 단계(토큰화, 품사 태깅, 구문 분석 등)를 포함하며, 텍스트가 입력되면 이 파이프라인을 통해 차례로 각 단계를 거쳐 분석 결과를 반환합니다.
[token.text for token in doc] -> 예제에서 사용한 for 문은 리스트 컴프리헨션(list comprehension)이라는 파이썬의 기능을 활용한 것입니다.
이 코드는 doc이라는 객체의 각 token을 순회하면서 token.text 값을 가져와 새로운 리스트 tokens에 추가하는 방식입니다. 이를 일반적인 for 문으로 작성하면 다음과 같은 코드가 됩니다.tokens = [] for token in doc: tokens.append(token.text)
리스트 컴프리헨션의 개념:
리스트 컴프리헨션의 일반적인 형식 기본 형식: [표현식 for 아이템 in 반복 가능한 객체] 조건문을 사용할 수도 있습니다: [표현식 for 아이템 in 반복 가능한 객체 if 조건문] 예시로, 리스트에서 짝수만 추출하고 싶을 때: numbers = [1, 2, 3, 4, 5] even_numbers = [num for num in numbers if num % 2 == 0] # [2, 4]
표현식: 새 리스트에 추가할 항목 또는 항목을 변형한 값을 의미합니다.
for 항목 in 반복가능객체: 반복할 대상입니다. 예를 들어 리스트, 문자열, 튜플, 혹은 다른 이터러블 객체를 넣을 수 있습니다.
if 조건 (선택적): 조건을 만족할 때만 항목이 리스트에 포함되도록 필터링하는 역할을 합니다.
결국 종합적으로 결론을 내리면,
doc = nlp("I love machine learning") ->
"I love machine learning"이라는 문장을 spacy 모델인 nlp로 분석하여 doc 객체에 저장합니다.
spacy의 nlp 모델은 이 문장을 분석하면서 자동으로 단어별로 토큰화합니다. 즉, "I", "love", "machine", "learning"과 같은 단어들이 각각 token 객체로 doc에 들어가게 됩니다.
tokens_spacy = [token.text for token in doc] ->
for token in doc는 doc 안에 있는 각각의 token 객체를 하나씩 token 변수에 넣습니다.
token.text는 token 객체가 담고 있는 원래 텍스트(단어)를 문자열로 반환합니다.
리스트 컴프리헨션 [token.text for token in doc]을 통해 doc의 각 token에서 .text를 가져와 문자열 리스트로 만듭니다.
결과적으로, tokens_spacy는 ["I", "love", "machine", "learning"]과 같이 각 단어가 문자열로 저장된 리스트가 됩니다.
잠깐, 여기서 en_core_web_sm이 뭔지 좀 더 살펴봅시다.
en_core_web_sm는 spaCy의 영어 자연어 처리(NLP) 모델 중 하나로, 작은 사이즈(Small)를 가진 영어 모델입니다. 이 모델은 텍스트 토큰화, 품사 태깅, 구문 분석, 개체명 인식(NER) 등의 기본적인 NLP 작업을 수행할 수 있도록 학습되어 있습니다.
en: 모델이 영어(en) 데이터를 기반으로 학습되었음을 나타냅니다.
core: "핵심(core)" 모델이라는 뜻으로, 기본적인 NLP 작업에 필요한 요소들을 포함하고 있는 모델임을 나타냅니다.
web: 주로 웹 텍스트를 기반으로 학습된 데이터라는 의미예요. 이 모델은 뉴스, 블로그 글과 같은 웹 데이터로 학습되어 일상 언어를 처리하는 데 적합합니다.
sm: "small"의 약자로, 작은 사이즈의 모델임을 뜻해요. sm 모델은 상대적으로 가벼워서 빠르게 실행할 수 있지만, 정확도는 중간 사이즈(md), 큰 사이즈(lg) 모델에 비해 낮을 수 있습니다.
모델 크기에 따른 종류:
sm (small): 가장 작은 사이즈로, 빠르게 실행되지만 정확도는 상대적으로 낮아요. 시스템 자원을 덜 사용해 빠르게 처리할 때 유리합니다.
md (medium): 중간 크기의 모델로, 정확도와 성능의 균형을 고려할 때 사용됩니다.
lg (large): 가장 큰 모델로, 정확도는 높지만 처리 시간이 오래 걸릴 수 있어요. 고사양 시스템이나 정확도가 중요한 작업에 적합합니다.
즉, en_core_web_sm모델은 자연어 처리(NLP) 작업을 위해 텍스트 데이터를 분석할 수 있는 다양한 정보와 알고리즘을 포함하고 있습니다.
nlp는 이 모델을 로드한 객체로, 텍스트를 분석하기 위해 사용됩니다.
만약 en_core_web_sm과 sentence가 만나면, spacy 라이브러리에서 자연어 처리를 위한 여러 가지 작업이 수행됩니다. 좀 더 구체적으로 살펴보면:
1. 모델 로딩
nlp = spacy.load("en_core_web_sm"):
en_core_web_sm은 spacy에서 제공하는 사전 훈련된 영어 모델입니다. 이 모델은 자연어 처리(NLP) 작업을 위해 텍스트 데이터를 분석할 수 있는 다양한 정보와 알고리즘을 포함하고 있습니다.
nlp는 이 모델을 로드한 객체로, 텍스트를 분석하기 위해 사용됩니다.
2. 텍스트 분석
doc = nlp(sentence):
sentence는 입력된 문자열로, 예를 들어 "I love banana"입니다.
이때, nlp 모델이 sentence를 입력받아 여러 가지 작업을 수행합니다.
3. 수행되는 주요 작업
토큰화(Tokenization):
문장을 단어와 구두점으로 나누어 각 단어를 token 객체로 생성합니다.
예를 들어, "I love banana"은 ['I', 'love', 'banana']으로 나뉘게 됩니다.
품사 태깅(Part-of-Speech Tagging):
각 토큰에 대해 품사 정보를 추가합니다. 예를 들어, "I"는 대명사(pronoun), "love"는 동사(verb)로 태깅됩니다.
문장 경계 인식(Sentence Boundary Detection):
입력된 문장에서 문장이 어디서 시작하고 끝나는지를 인식합니다. 이는 문장이 여러 개 포함된 경우 유용합니다.
의존 구문 분석(Dependency Parsing):
각 단어의 문법적 관계를 분석하여 문장 구조를 이해합니다. 어떤 단어가 주어, 동사, 목적어 역할을 하는지를 파악합니다.
명명된 개체 인식(Named Entity Recognition):
특정 개체(예: 사람, 장소, 조직 등)를 식별합니다. "banana"이 어떤 카테고리에 속하는지를 이해하는 데 도움을 줍니다.
4. 결과
최종적으로 doc 객체는 이러한 분석 결과를 포함하며, doc 내에 각 token에 대한 정보가 포함됩니다. 사용자는 이 doc 객체를 통해 각 단어의 텍스트, 품사, 문법적 관계 등 다양한 정보를 쉽게 접근할 수 있습니다.
그렇기 때문에 nlp가 sentence를 받아 함수처럼 작용할 수 있었던 이유입니다.
여기서 조금 더 심화된 코드를 보겠습니다.
from nltk.tokenize import word_tokenize from nltk.corpus import stopwords from nltk.stem import PorterStemmer tokens = word_tokenize("Natural language processing is powerful.") stop_words = set(stopwords.words('english')) filtered_tokens = [w for w in tokens if w.lower() not in stop_words] # 불용어 제거 stemmer = PorterStemmer() stemmed_tokens = [stemmer.stem(token) for token in filtered_tokens] # 어근 추출
여기서 알아야할 개념들을 보겠습니다.
1. PorterStemmer -> PorterStemmer는 NLTK에서 제공하는 어근 추출(stemming) 도구입니다. 어근 추출이란, 단어의 접미사나 어미를 제거하여 단어의 기본 형태인 어근(root)으로 변환하는 과정을 말합니다 이 과정은 텍스트에서 단어의 변형을 줄여, 단어의 의미를 유지하면서 다양한 변형 형태를 동일한 형태로 통합할 때 유용합니다.
예를 들어, running, runner, runs는 모두 run이라는 어근으로 변환될 수 있습니다. 이를 통해 텍스트 데이터에서 단어의 일관성을 높이고, 분석할 단어의 수를 줄일 수 있습니다. (사실 PorterStemmer는 어근 추출을 위한 클래스로, 이 클래스 안에는 어근 추출 작업을 실제로 수행하는 stem 메서드가 정의되어 있습니다. 그래서 stemmer.stem()으로 쓰는 것입니다)
따라서, stemmer.stem(token)으로 호출해야 stem 메서드가 token에 대해 어근 추출 작업을 수행하고, 그 결과를 반환합니다.
그리고 set입니다
set은 파이썬의 내장 자료형 중 하나로, 중복되지 않는(unordered) 고유한 값들의 집합을 표현합니다. 수학에서의 집합(Set) 개념과 유사하며, 다음과 같은 특성을 가집니다:
중복 제거: set에 저장된 요소는 중복될 수 없습니다. 동일한 값을 여러 번 추가해도 한 번만 저장됩니다.
순서 없음: set은 요소의 순서를 유지하지 않습니다. 즉, 인덱스를 사용하여 요소에 접근할 수 없습니다.
가변성: set은 가변(mutable) 자료형으로, 요소를 추가하거나 제거할 수 있습니다.
이 부분에서 set은 불용어(stopwords)를 저장하는 데 사용되며, set 자료형을 사용하면 특정 단어가 불용어에 포함되어 있는지 빠르게 확인할 수 있습니다.
불용어와 set의 역할
불용어(stop words): 불용어는 텍스트 분석에서 일반적으로 의미가 없다고 판단되는 단어들을 말합니다. 예를 들어, is, the, and 같은 단어들은 문장에서 중요한 정보를 전달하지 않으므로 분석에 불필요한 경우가 많습니다. 이 코드에서 stopwords.words('english')는 영어 불용어 목록을 가져옵니다.
set의 역할: 불용어 목록을 set으로 변환하면, w.lower() not in stop_words와 같은 조건문에서 불용어 포함 여부를 더 빠르게 확인할 수 있어 성능이 향상됩니다. set은 각 요소가 유일하며, 특정 요소의 포함 여부를 확인하는 데 있어서 리스트보다 훨씬 빠릅니다.
즉, w.lower()로 변환된 단어가 stop_words 집합에 존재하지 않을 때(not in), 즉 불용어가 아닐 때만 filtered_tokens 리스트에 추가됩니다.
출력:
['natur', 'languag', 'process', 'power', '.']
어휘 (Vocabulary)
어휘란 데이터셋에서 사용되는 모든 고유한 단어의 집합으로, 각 단어는 고유한 인덱스를 갖습니다.
어휘는 종종 주어진 텍스트에서 단어의 빈도를 기반으로 구축됩니다. 예를 들어, 특정 빈도 이하의 단어는 무시하거나 <unk>로 표시할 수 있습니다.
어휘를 통해, 모델이 이해할 수 있는 형식으로 데이터를 변환하기 위해 어휘를 사용합니다. 예를 들어, "I love AI"라는 문장은 어휘를 통해 [1, 2, 3] 같은 인덱스 배열로 변환될 수 있습니다.
from collections import Counter tokens = ["I", "love", "AI", "AI", "is", "fun"] counter = Counter(tokens) vocab = {word: i for i, (word, count) in enumerate(counter.items()) if count > 1} # 빈도 기반 필터링
이 코드는 Python의 collections 모듈에 있는 Counter 클래스를 사용하여 단어 목록에서 중복된 단어의 빈도를 계산하고, 빈도가 2회 이상인 단어만을 선택하여 어휘 사전(vocab)을 생성하는 코드입니다.
여기서 Counter는 tokens의 각 단어가 등장한 횟수를 세어주는 딕셔너리 형태의 객체를 생성합니다. Counter를 사용하면 각 단어의 빈도가 저장된 객체를 얻을 수 있습니다. 위 코드에서 counter의 결과는 다음과 같습니다:counter = Counter(tokens) -> Counter({'I': 1, 'love': 1, 'AI': 2, 'is': 1, 'fun': 1})
이 결과를 보면, AI는 두 번 등장하고 나머지 단어는 한 번씩 등장한 것을 확인할 수 있습니다.
vocab = {word: i for i, (word, count) in enumerate(counter.items()) if count > 1}
이 단계는 빈도가 2회 이상인 단어를 선택해 어휘 사전(vocab)을 생성하는 부분입니다. 이 어휘 사전은 특정 빈도 이상 등장한 단어를 고유 번호와 매핑한 딕셔너리로 생성됩니다.
여기서 .items()는 파이썬의 딕셔너리 메서드로, 딕셔너리의 키와 값을 쌍으로 묶어 반환하는 메서드입니다. .items()는 딕셔너리의 각 키-값 쌍을 튜플 형태로 묶어서 dict_items 객체로 반환하며, 이를 통해 딕셔너리의 키와 값을 동시에 반복할 수 있습니다.
그렇기에 이미 딕셔너리 형태의 객체를 가지고 있는 counter에서 enumerate을 통해 한번 더 index를 뽑아내기 때문에, i, (word, count)의 형태가 나올 수 있는겁니다. 즉, .item()을 통해 키와 값을 쌍으로 묶어 두개를 반환함과 동시에, 그것을 (word, count)으로 넣어줘야 하는데, enumerate을 통해 한번 더 두개로 갈라져야 하는데, 그것을 위해 ()을 쓰는 것입니다.
저렇게 쓰면 count가 1개 이상인 AI가 출력되게 됩니다print(vocab) {'AI': 2}
여기서 enumerate에 조금 더 간단히 알아보겠습니다.
enumerate는 반복문을 돌면서 각 요소에 대한 인덱스(순서)를 함께 제공하는 파이썬 내장 함수입니다. 리스트, 튜플, 문자열 등 순회할 수 있는(iterable) 객체에서 각 요소와 해당 인덱스 번호를 한 쌍으로 반환합니다.
fruits = ['apple', 'banana', 'cherry'] for index, fruit in enumerate(fruits): print(index, fruit)
즉, 이렇게 index와 그에 맞는 값을 한 쌍으로 반환하게 해줍니다. 즉, index와 요소를 두개 반환하기 때문에, 받는 값도 두개(index, fruit)로 지정할 수 있습니다.0 apple 1 banana 2 cherry
신경망(Neural Network)super(NeuralNetwork, self).__init__()
먼저 기본적인 예시부터 보겠습니다.
import torch.nn as nn class NeuralNetwork(nn.Module): def __init__(self): super(NeuralNetwork, self).__init__() self.fc1 = nn.Linear(784, 128) # 은닉층 self.fc2 = nn.Linear(128, 10) # 출력층 def forward(self, x): x = torch.relu(self.fc1(x)) # 은닉층에 ReLU 활성화 함수 적용 x = self.fc2(x) # 출력층 return x
하나하나 차근차근 보겠습니다.
import torch.nn as nn
PyTorch의 nn 모듈을 가져옵니다. 이 모듈은 신경망을 구축하는 데 필요한 다양한 클래스와 함수(예: 레이어, 손실 함수 등)를 포함하고 있습니다.
class NeuralNetwork(nn.Module):
NeuralNetwork라는 새로운 클래스를 정의합니다. 이 클래스는 신경망의 구조를 정의하며, nn.Module을 상속받습니다.
nn.Module은 PyTorch에서 모든 신경망의 기본 클래스입니다. 이를 상속받음으로써 신경망 클래스가 PyTorch의 기능을 사용할 수 있게 됩니다.def __init__(self): super(NeuralNetwork, self).__init__()
__init__ 메서드는 클래스의 인스턴스가 생성될 때 호출되는 메서드입니다. 여기서 신경망의 구조를 정의합니다.
super(NeuralNetwork, self).__init__()
부모 클래스인 nn.Module의 초기화 메서드를 호출하여 PyTorch의 내부 기능을 준비합니다.
이는 신경망이 제대로 작동하도록 설정하는 데 필수적입니다.self.fc1 = nn.Linear(784, 128) # 은닉층
선형 변환을 적용하는 은닉층을 정의합니다.
입력 노드 수: 784 (예: 28x28 크기의 이미지에서 각 픽셀이 입력으로 사용됨).
은닉 뉴런 수: 128. 이 층에서 학습된 표현이 다음 층으로 전달됩니다.self.fc2 = nn.Linear(128, 10) # 출력층
출력층을 정의합니다.
입력 노드 수: 128 (은닉층의 출력).
출력 노드 수: 10 (예: 0부터 9까지의 숫자 분류). 이 출력은 클래스 확률로 해석됩니다.def forward(self, x):
이 메서드는 신경망의 순전파 과정을 정의합니다. 입력 데이터가 네트워크를 통해 어떻게 흐르는지를 나타냅니다.
x는 입력 데이터입니다.
x = torch.relu(self.fc1(x)) # 은닉층에 ReLU 활성화 함수 적용
self.fc1(x) -> 입력 x가 첫 번째 선형 계층인 fc1에 전달되어 가중치와 편향을 통해 변환됩니다.
torch.relu(...) ->
ReLU 활성화 함수를 적용합니다.
ReLU는 음수 값을 0으로 만들고 양수 값은 그대로 반환합니다. 이는 신경망에 비선형성을 추가하여 복잡한 패턴을 학습할 수 있게 합니다.
x = self.fc2(x) # 출력층
은닉층의 출력 x가 두 번째 선형 계층인 fc2에 전달되어 최종 출력을 생성합니다.
return x
최종 출력 x를 반환합니다. 이 출력은 이후의 손실 함수와 비교되어 모델의 성능을 평가하고, 학습 과정에서 가중치가 업데이트됩니다.
여기서 중요 흐름을 설명하겠습니다.
먼저,
self.fc1에서의 입력값은 x의 입력값과 같습니다. 이를 통해 self.fc1의 작동 방식을 명확하게 이해할 수 있습니다.
x는 forward 메서드의 매개변수로, 모델이 예측을 수행하기 위해 외부에서 전달된 입력 데이터입니다. 예를 들어, 28x28 이미지가 784 차원의 벡터로 변환되어 들어올 수 있습니다.
x = self.fc1(x)
이 줄에서 self.fc1(x)는 x를 입력으로 받아 은닉층의 선형 변환을 수행합니다.
self.fc1은 nn.Linear(784, 128)로 정의되어 있기 때문에, 입력 데이터 x의 차원(784)과 self.fc1의 입력 차원(784)이 일치해야 합니다.
이 과정에서 x는 선형 변환을 거쳐 128 차원으로 변환됩니다.
즉, 입력값이 x와 같다는 것은, self.fc1이 x를 그대로 받아들이고 변환하는 과정을 의미합니다. 따라서, x의 차원(784)과 self.fc1의 입력 차원(784)이 일치해야 합니다.
입력값이 x와 같다는 것은, self.fc1이 x를 그대로 받아들이고 변환하는 과정을 의미합니다. 따라서, x의 차원(784)과 self.fc1의 입력 차원(784)이 일치해야 합니다. 만약 x의 차원이 784가 아니라면, 에러가 발생합니다.
즉, self.fc1 = nn.Linear(784, 128)이 부분에서 저 784는 28x28 크기의 이미지에서 각 픽셀이 입력으로 사용된것이고, x의 784는 모델에 전달되는 데이터입니다. 그러니까 저 784를 그대로 self.fc1(x)의 x로 들어가게 됩니다. 그리고 torch.relu란 활성화 함수를 전달했기 때문에, 비선형성을 추가합니다.
더 쉽게 설명하면, self.fc1(784, 128)에서 784를 x에도 똑같은 값이 들어가고, 그 값을 Relu 함수를 통해 내가 설정한 128개의 값으로 나오도록 유도하는 것이라고 생각하면 편합니다.
여기서 마지막 fc2에는 ReLU를 적용하지 않는 이유로는, 출력층에서의 최종 결과가 각 클래스의 점수 또는 로짓(logits)을 표현해야 하기 때문입니다. 이 값들은 모델이 각 클래스에 대해 얼마나 신뢰하는지를 나타냅니다.
즉, 128개의 입력 채널을 실제로 값 클래스들로 분류하기 위함인것입니다.(그냥 답을 꺼내 예측하기 위함이라고 생각하시면 편합니다.)
여기서 ReLU는 값을 출력하는 것이 아닌, ReLU를 적용함으로써 비선형성을 추가한다는 뜻입니다. ReLU는 입력이 0 이하일 경우 출력을 0으로 만들고, 0보다 크면 그 값을 그대로 반환하므로, 만약 출력층에서 ReLU를 사용하면 0 이하의 점수가 0으로 바뀌어버리므로, 실제로 각 클래스에 대한 확률을 제대로 표현할 수 없게 됩니다.
LSTM(Long Short-Term Memory)
LSTM (Long Short-Term Memory) 네트워크는 순환 신경망(RNN)의 한 종류로, 시퀀스 데이터(시간 순서가 있는 데이터)를 처리하기 위해 설계되었습니다. LSTM은 특히 장기 의존성(long-term dependencies) 문제를 해결하는 데 강력한 성능을 발휘합니다.
LSTM은 기본 RNN의 단점을 보완하기 위해 다음과 같은 구성 요소를 포함합니다:
셀 상태(Cell State): LSTM의 핵심 부분으로, 시퀀스의 정보를 장기간 유지할 수 있도록 설계되었습니다. 셀 상태는 LSTM 내부에서 데이터의 흐름을 조절합니다.
은닉 상태(Hidden State): 현재 시점의 정보를 저장하는 역할을 하며, 다음 시점의 계산에 사용됩니다. 이는 출력으로도 사용됩니다.
LSTM 게이트 구조 LSTM의 중요한 특성은 바로 게이트(gate)입니다. 게이트는 정보를 선택적으로 통과시키고, 정보를 제어하는 역할을 합니다. 주요 게이트는 다음과 같습니다:
입력 게이트 (Input Gate): 어떤 새로운 정보가 셀 상태에 추가될지를 결정합니다. 활성화 함수(주로 시그모이드)를 통해 0과 1 사이의 값으로 계산되며, 이 값에 따라 새로운 정보를 셀 상태에 추가할지 여부를 조절합니다.
망각 게이트 (Forget Gate): 현재 셀 상태에서 어떤 정보를 제거할지를 결정합니다. 활성화 함수(주로 시그모이드)를 사용하여 0과 1 사이의 값을 생성하며, 0에 가까울수록 해당 정보를 잊어버리게 됩니다.
출력 게이트 (Output Gate): 현재 셀 상태에서 출력할 정보를 결정합니다. 활성화 함수(주로 시그모이드)와 함께 셀 상태를 tanh 함수에 통과시켜 최종 출력을 생성합니다.
구조는 대략:
self.lstm = nn.LSTM(input_size=input_dim, hidden_size=hidden_dim, num_layers=1, batch_first=True)
주요 매개변수로는
input_size: 각 타임 스텝에서 입력되는 특성의 수입니다. 예를 들어, 텍스트 데이터에서 각 단어를 100차원의 벡터로 표현했다면 input_size=100으로 설정합니다.
hidden_size: 은닉 상태의 크기이며, 모델이 학습할 표현의 차원입니다. LSTM의 학습 능력과 모델 복잡도를 조정합니다.
num_layers: LSTM 레이어의 개수입니다. 기본값은 1이지만 더 깊은 네트워크를 원하면 레이어를 쌓을 수 있습니다.
batch_first: 입력 텐서의 형태를 결정하는 매개변수로, True이면 입력 크기를 (batch, seq_len, input_size)로 설정하여 배치가 가장 먼저 오게 합니다.
주요 출력값
output: 모든 타임 스텝의 은닉 상태를 포함하는 텐서입니다.
(hn, cn): 마지막 타임 스텝의 은닉 상태(hn)와 셀 상태(cn)로, 각각의 형태는 (num_layers, batch, hidden_size)입니다.
코드를 통해 조금 더 자세히 보겠습니다.
import torch.nn as nn class LSTMNetwork(nn.Module): def __init__(self, input_dim, hidden_dim, output_dim): super(LSTMNetwork, self).__init__() self.lstm = nn.LSTM(input_dim, hidden_dim, batch_first=True) self.fc = nn.Linear(hidden_dim, output_dim)
class LSTMNetwork(nn.Module)::
LSTMNetwork라는 이름의 클래스를 정의하며, nn.Module을 상속받습니다. 이는 PyTorch의 모든 신경망 모듈의 기본 클래스입니다.
def __init__(self, input_dim, hidden_dim, output_dim):
초기화 메서드로, LSTM 네트워크의 구조를 정의합니다.
input_dim: 입력 특성의 차원 수 (예: 시퀀스의 각 타임 스텝의 특징 수).
hidden_dim: LSTM의 은닉 상태 차원 수.
output_dim: 출력 특성의 차원 수 (예: 분류 문제에서 클래스 수).
self.lstm = nn.LSTM(input_dim, hidden_dim, batch_first=True):
LSTM 층을 초기화합니다. batch_first=True는 입력 텐서의 첫 번째 차원이 배치 크기임을 나타냅니다. 즉, 입력 데이터는 (batch_size, seq_length, input_dim) 형식입니다.
self.fc = nn.Linear(hidden_dim, output_dim):
LSTM 층의 출력 값을 받아 최종 출력을 생성하는 선형(fully connected) 층입니다.
def forward(self, x): out, (hn, cn) = self.lstm(x) # 은닉 상태와 셀 상태를 반환 out = self.fc(out[:, -1, :]) # 마지막 시점의 출력 return out
def forward(self, x):
순전파(Forward pass)를 수행하는 메서드입니다. 입력 텐서 x를 받아 LSTM 네트워크의 출력을 계산합니다.
out, (hn, cn) = self.lstm(x):
LSTM 네트워크에 입력을 전달합니다. 여기서 out은 LSTM의 모든 타임 스텝에서의 출력이고, (hn, cn)은 각각 마지막 타임 스텝의 은닉 상태와 셀 상태입니다.
LSTM의 출력 out의 형태는 (batch_size, seq_length, hidden_dim)입니다.
여기서 말하는 타임 스텝이란,시퀀스 데이터에서 각 시점(시간이나 순서)에 해당하는 단위입니다. 예를 들어, 문장의 각 단어, 동영상의 각 프레임, 또는 시간에 따른 센서 데이터의 측정 값들이 각각의 타임 스텝이 될 수 있습니다. 모든 타임 스텝이라는 표현은 시퀀스 데이터의 처음부터 끝까지의 모든 시점을 의미합니다.
예를 들어, "I love chocolate banana"이라는 문장이 있다면, 각 단어가 한 타임 스텝이 됩니다:
타임 스텝 1: "I"
타임 스텝 2: "love"
타임 스텝 3: "chocolate"
타임 스텝 4: "banana"
LSTM에 문장을 입력할 때, 각 타임 스텝마다 해당 단어에 대한 정보를 처리합니다. 각 단어는 모델이 학습할 데이터로 들어가며, LSTM은 이 데이터를 입력 순서에 따라 처리하여 이전 정보들을 반영합니다.
out = self.fc(out[:, -1, :]):
LSTM의 마지막 타임 스텝의 출력을 선형 층에 전달합니다.
참고로 out[:, -1, :]에서 콜론(:)이 위치에 따라 의미를 가지며, 특정 차원을 그대로 유지하겠다는 의미로 사용됩니다.
out[:, -1, :]는 out 텐서의 일부만 선택하는 인덱싱 방식입니다.
(첫 번째): 배치 차원에서 모든 배치를 선택합니다.
-1 (두 번째): 시퀀스 차원에서 마지막 타임 스텝을 선택합니다.
(세 번째): 마지막 차원에서 모든 값을 선택합니다.
out[:, -1, :]는 모든 배치에 대해 마지막 시점의 출력만 선택하는 것을 의미합니다.
이 최종 출력은 원하는 형태(예: 클래스의 확률)로 변환됩니다.
return out:
최종 출력 값을 반환합니다.
추가적으로, LSTM의 각 차원 (input_dim, hidden_dim, output_dim)은 모델이 해결하려는 문제와 데이터의 구조에 따라 결정됩니다.
1. input_dim (입력 특성의 차원 수)
input_dim은 시퀀스의 각 타임 스텝에 포함된 특성(feature)의 수를 의미합니다. LSTM에 전달되는 입력의 차원을 설정하며, 한 시점의 입력 데이터가 몇 개의 값을 가지는지를 정의합니다.
크기 기준: input_dim은 주로 데이터의 형식과 특성 수에 따라 결정됩니다.
예를 들어, 텍스트 데이터에서 단어 임베딩(embedding)을 사용할 경우, 임베딩 차원 수가 input_dim이 됩니다.
시계열 데이터(예: 날씨 데이터)에서는 각 타임 스텝의 특성 개수(온도, 습도 등)가 input_dim이 됩니다.
2. hidden_dim (은닉 상태 차원 수)
hidden_dim은 LSTM의 은닉 상태(hidden state)와 셀 상태(cell state)의 차원 수를 결정합니다. 이는 모델이 각 시점에서 학습할 수 있는 표현의 크기를 조절하여, 모델의 용량과 학습 능력에 큰 영향을 미칩니다.
크기 기준: hidden_dim은 모델의 성능과 복잡도 간의 균형에 따라 설정됩니다.
크기가 작으면 모델이 복잡한 패턴을 충분히 학습하기 어려울 수 있습니다.
크기가 너무 크면 과적합(overfitting)될 위험이 있으며, 학습 속도가 느려질 수 있습니다.
보통은 경험적 실험이나 하이퍼파라미터 튜닝을 통해 적절한 hidden_dim 값을 찾습니다.
3. output_dim (출력 특성의 차원 수)
output_dim은 모델이 최종적으로 예측하는 값의 차원 수를 의미하며, 주로 문제의 유형에 따라 설정됩니다.
hidden_dim과 output_dim은 데이터와 문제의 유형에 맞춰 설정하지만, 어느 정도는 사용자 정의에 따라 조정할 수 있습니다.
예시로 보겠습니다:
예시로 이해하기
텍스트 분류 모델:
텍스트를 단어 임베딩으로 변환해 시퀀스로 입력하는 경우:
input_dim: 임베딩 차원 (예: input_dim = 300). -> 단어 임베딩 벡터의 차원이 300이기 때문입니다.
hidden_dim: 데이터와 모델의 복잡성에 맞게 설정 (예: hidden_dim = 128).
output_dim: 분류해야 하는 클래스 수 (예: 감정 분석에서 긍정/부정 분류라면 output_dim = 2).
주가 예측 모델:
과거 60일의 주가 데이터를 이용해 다음 날의 주가를 예측하는 경우:
input_dim: 각 타임 스텝의 입력 특성 (예: 종가, 거래량이라면 input_dim = 2).
hidden_dim: 학습할 수 있는 표현의 크기에 따라 설정 (예: hidden_dim = 64).
output_dim: 예측하고자 하는 값의 수 (예: 주가 하나를 예측한다면 output_dim = 1).
이번에는 실습 코드로 한번 보겠습니다.
과거 며칠간의 주가 데이터를 보고 다음 날의 주가를 예측하기 위해 LSTM을 사용하는 경우입니다.
주어진 데이터는 하루에 3개의 특성(종가, 최고가, 최저가)이 있고, 예측에 사용할 과거 일수는 30일입니다.
import torch # 예제 데이터 생성: 100개의 데이터 샘플, 30일 동안의 #각 샘플에 대해 종가, 최고가, 최저가가 포함된 3개의 특성 data = torch.randn(100, 30, 3) # (샘플 수, 타임 스텝 수, 특성 수) target = torch.randn(100, 1) # 100개의 샘플에 대한 다음 날 종가 (1개 값) 예측
샘플 수: 100개
타임 스텝 수: 30일 (과거 30일 데이터를 이용해 예측)
특성 수: 3개 (종가, 최고가, 최저가)
이제 input_dim, hidden_dim, output_dim을 설정할 차례입니다.
1. input_dim 결정
각 타임 스텝에서 입력되는 데이터의 특성 수입니다.
input_dim = 3 # 특성 3개: 종가, 최고가, 최저가
2. hidden_dim 결정
모델이 학습할 표현의 크기를 설정합니다. 데이터가 복잡할수록 더 많은 뉴런이 필요할 수 있지만, 과적합을 피하기 위해 너무 큰 값을 피해야 합니다. 여기서는 실험적으로 64로 설정해 보겠습니다.
hidden_dim = 64 # 은닉 상태의 차원을 64로 설정
3. output_dim 결정
출력 차원은 예측할 값의 수를 나타냅니다. 여기서는 다음 날 종가 하나만 예측하므로 output_dim = 1입니다.output_dim = 1 # 예측할 종가 1개
import torch.nn as nn class LSTMNetwork(nn.Module): def __init__(self, input_dim, hidden_dim, output_dim): super(LSTMNetwork, self).__init__() # LSTM 레이어 생성 self.lstm = nn.LSTM(input_dim, hidden_dim, batch_first=True) # 완전 연결층 생성 self.fc = nn.Linear(hidden_dim, output_dim) def forward(self, x): # LSTM 레이어: LSTM 출력과 마지막 은닉, 셀 상태(hn, cn)를 반환 out, (hn, cn) = self.lstm(x) # 마지막 타임 스텝의 출력에 대해 완전 연결층 적용 out = self.fc(out[:, -1, :]) return out # 모델 생성 model = LSTMNetwork(input_dim=input_dim, hidden_dim=hidden_dim, output_dim=output_dim)
이렇게 기본적으로 작동하는 방식으로 data를 받아 출력 예측값을 반환하는 법을 알아보겠습니다.# 예제 데이터 사용 output = model(data) print(output.shape) # torch.Size([100, 1]) - 샘플 100개의 예측 값
그런데 data는 data = torch.randn(100, 30, 3) # (샘플 수, 타임 스텝 수, 특성 수)이렇게 3개의 특성을 가지는데,
target은 2개의 특성 밖에 없습니다. 그런데 어떻게 예측을 하는 것일까요?
이것은 바로 타임 스텝의 개념이 가장 중요합니다.
타임 스텝은 LSTM 모델에서 예측하는 기준이 됩니다.LSTM(Long Short-Term Memory) 네트워크는 시계열 데이터를 처리하는 데매우 유용한 구조로, 과거의 정보(즉, 이전 타임 스텝의 데이터)를 기억하고 이를 통해 현재의 상태를 업데이트합니다. 이 과정에서 타임 스텝은 매우 중요한 역할을 합니다.
타임 스텝의 역할
과거 정보 활용:
각 타임 스텝은 특정 시점에서의 데이터를 나타내며, LSTM은 이러한 데이터를 사용하여 과거의 정보를 학습합니다. 예를 들어, 30일의 종가, 최고가, 최저가 데이터를 통해 다음 날의 종가를 예측합니다.
상태 업데이트:
LSTM은 각 타임 스텝에서 입력을 받아 은닉 상태(hidden state)와 셀 상태(cell state)를 업데이트합니다. 이러한 상태들은 모델이 과거의 정보를 기억하고 잊는 데 도움을 줍니다.
예측:
마지막 타임 스텝의 은닉 상태는 다음 날의 종가를 예측하는 데 사용됩니다. 즉, 모델은 30일의 데이터를 기반으로 마지막 날(30일째)의 정보를 사용하여 다음 날의 종가(31일째)를 예측합니다.
즉, 30 타임스텝을 기준으로 학습을 하고, 그 학습된 결과를 바탕으로 target의 결과를 내는 것이기 때문에, 두개의 값을 넣는것입니다.
배치 처리 (Batch Processing)
데이터가 클 경우 메모리 부담을 줄이기 위해 전체 데이터셋을 나누어 학습합니다. 훈련 속도가 향상되고, 일반화 성능을 높이는 데 도움이 됩니다.
from torch.utils.data import DataLoader data_loader = DataLoader(dataset, batch_size=32, shuffle=True)
그냥, 데이터를 한번에 얼마나 학습시킬지 정하는 부분입니다.
배치 처리: 데이터를 작은 그룹으로 나누어 모델을 훈련하는 방식.
미니배치 학습: 배치 처리의 한 형태로, 데이터를 더 작은 배치로 나누어 훈련하여 메모리 효율성, 훈련 속도, 일반화 성능을 높이는 방법.
최적화 알고리즘 (Optimization Algorithm)
최적화 알고리즘은 손실 함수를 최소화하기 위해 모델의 가중치와 편향을 업데이트하는 방법을 정의합니다. 목표는 모델이 훈련 데이터에 잘 맞도록 학습하는 것입니다.
그리고 오차가 계산된 후, 즉 손실이 존재할 때마다 역전파(backpropagation) 과정이 끝난 뒤에 사용됩니다. 최적화 알고리즘은 역전파 과정에서 계산된 기울기(gradient)를 사용해 모델의 매개변수를 업데이트합니다.
1. 경사하강법 (Gradient Descent)
구조:
파라미터 (Weights): 모델의 가중치, 예를 들어 weights.
손실 함수 (Loss Function): 모델의 예측과 실제값 간의 차이를 계산하는 함수. 예: MSE, Cross-Entropy 등.
그래디언트 (Gradient): 손실 함수의 기울기로, 각 파라미터에 대해 손실의 변화율을 나타냅니다.
학습률 (Learning Rate):파라미터 업데이트의 크기를 결정하는 값.(0.01, 0.001, 0.0001)
# 1. SGD (Stochastic Gradient Descent, 확률적 경사 하강법) optimizer_sgd = optim.SGD(model.parameters(), lr=0.01)
2. Adam (Adaptive Moment Estimation)
구조:
가중치 (Weights): 모델의 가중치.
1차 모멘트 (m): 그래디언트의 평균을 추정하는 변수.
2차 모멘트 (v): 그래디언트의 분산을 추정하는 변수.
시간 스텝 (t): 업데이트 횟수를 세는 변수.
편향 보정: 초기 단계에서의 모멘트 추정의 편향을 보정하는 과정.
학습률 (Learning Rate): 각 파라미터의 업데이트 크기를 조절.
베타스(betas): 베타스 Adam과 AdamW 최적화 알고리즘에서 사용하는 하이퍼파라미터로, 모멘텀과 적응적 학습률 조정에 영향을 미칩니다. 이 두 값은 기울기 추적을 위해 계산되는 지수 가중 이동 평균(EMA)에 쓰입니다. 기본으로 (0.9, 0.999)로 쓰입니다.
# 2. Adam (Adaptive Moment Estimation) optimizer_adam = optim.Adam(model.parameters(), lr=0.01, betas = (0.9, 0.999))
beta1 (0.9): 기울기 평균을 계산하는 모멘텀의 영향을 조절합니다. 0.9라는 값은 최근 기울기 값이 과거 기울기 값보다 90% 더 중요하게 고려되도록 합니다. beta2 (0.999): 기울기 제곱의 평균을 계산하는 적응적 학습률의 영향을 조절합니다. 0.999라는 값은 과거 기울기의 변동성을 감지하여 학습률을 조절합니다.
3. RMSProp (Root Mean Square Propagation)
보통 RNN, LSTM 같은 순환 신경망(RNN)에서 안정적인 학습이 필요할 때 씁니다.
특징으로는 최근 기울기의 제곱을 평균내어 학습률을 조정하므로, 변동이 심한 기울기를 다룰 때 특히 유리합니다.
구조:
가중치 (Weights): 모델의 가중치.
2차 모멘트 (v): 그래디언트의 제곱 평균을 저장하는 변수.
학습률 (Learning Rate): 파라미터 업데이트의 크기.
# 3. RMSProp (Root Mean Square Propagation) optimizer_rmsprop = optim.RMSprop(model.parameters(), lr=0.01)
4. Stochastic Gradient Descent (SGD)
보통 큰 데이터셋에서 빠르게 학습해야 할 때 씁니다.
특징으로는 한 번의 업데이트에 하나의 데이터 포인트(또는 작은 배치)만을 사용하기 때문에 계산이 빠르지만, 기울기의 변동이 심해서 수렴이 불안정할 수 있습니다.
구조:
가중치 (Weights): 모델의 가중치.
손실 함수 (Loss Function): 모델의 예측과 실제값 간의 차이를 계산.
그래디언트 (Gradient): 손실 함수의 기울기.
학습률 (Learning Rate): 파라미터 업데이트의 크기.
모멘텀 (Momentum): 이전 업데이트의 가속도를 저장하는 변수.
여기서 모멘텀은 SGD에서 변동을 줄이고 더 빠르게 수렴하게 하고 싶을 때 씁니다.
특징으로는 이전 기울기 업데이트를 참고하여 새로운 업데이트에 모멘텀(속도)을 더합니다. 이를 통해 수렴 속도가 빨라지고, 변동이 심한 기울기에 더 안정적으로 대응할 수 있습니다.
# 4. SGD (Stochastic Gradient Descent, 확률적 경사 하강법) optimizer_sgd = optim.SGD(model.parameters(), lr=0.01, momentum = 0.9)
그전에, .parameter()에 대해 알아봅시다.
이 메서드는 PyTorch 모델에서 학습 가능한 매개변수를 가져와 옵티마이저에 전달하기 위한 역할을 합니다.
model.parameters()가 가져오는 것: 모델의 학습 가능한 매개변수:
학습 가능한 매개변수는 모델이 예측을 최적화하기 위해 학습을 통해 조정할 수 있는 값들입니다. 일반적으로 가중치 (weights)와 바이어스 (biases)를 의미합니다.
PyTorch에서 모델을 정의할 때, torch.nn.Module을 상속받아 각 레이어를 생성하게 되면, 이 레이어에는 자동으로 학습 가능한 매개변수가 포함됩니다.
import torch.nn as nn class LinearRegressionModel(nn.Module): def __init__(self): super(LinearRegressionModel, self).__init__() self.linear = nn.Linear(1, 1) # 입력과 출력이 모두 1차원인 선형 레이어 생성 def forward(self, x): return self.linear(x)
위 코드에서 self.linear는 nn.Linear(1, 1)로 정의된 선형 레이어입니다. 이 레이어는 내부적으로 가중치와 바이어스를 자동으로 생성하고 self.linear에 속한 학습 가능한 매개변수로 저장합니다.
model.parameters()가 하는 일: 모델의 학습 가능한 매개변수를 반환:
model.parameters()는 이 모델 객체에 포함된 모든 학습 가능한 매개변수를 가져와 반환합니다.
반환되는 것은 가중치와 바이어스를 포함한 텐서의 이터레이터입니다. 즉, 모델에 있는 모든 레이어의 가중치와 바이어스가 담긴 텐서를 순차적으로 접근할 수 있게 됩니다.
model.parameters()의 목적:
이 메서드를 사용하는 주된 목적은 옵티마이저에 학습 가능한 매개변수를 전달하기 위함입니다. 옵티마이저는 이 매개변수들을 기반으로 손실 함수에 대한 기울기를 계산하고, 이를 통해 매개변수를 업데이트하여 모델의 예측 성능을 향상시킵니다. 예를 들어, 옵티마이저를 다음과 같이 정의할 때 사용됩니다:
import torch.optim as optim model = LinearRegressionModel() optimizer = optim.SGD(model.parameters(), lr=0.01)
여기서 model.parameters()를 옵티마이저에 전달함으로써, 옵티마이저는 모델의 모든 가중치와 바이어스를 업데이트할 준비를 합니다.
model.parameters()의 내부 동작 방식
1. model.parameters()는 모델에 포함된 모든 레이어를 검색하고, 각 레이어에 있는 학습 가능한 매개변수를 찾아 반환합니다.
2. 각 레이어의 매개변수는 torch.nn.Parameter라는 특별한 텐서 타입으로, requires_grad=True 속성이 설정되어 있어 기울기(gradient)를 계산하고 학습할 수 있는 상태로 되어 있습니다.
3. 옵티마이저는 model.parameters()로 얻은 매개변수에 대해 기울기 업데이트를 수행하게 됩니다.
이렇게 model.parameters()를 통해 학습 가능한 매개변수를 모아서 옵티마이저로 전달하는 방식으로, PyTorch는 모델의 학습 과정을 단순하고 유연하게 관리할 수 있습니다.
손실 함수(Loss Function)
손실 함수(Loss Function)는 모델의 예측이 실제 값과 얼마나 다른지 측정하는 지표로, 딥러닝 모델 학습의 핵심 역할을 합니다. 손실 함수 값이 작을수록 모델이 잘 예측하고 있다는 의미입니다. 손실 함수는 모델이 잘못된 예측을 할 때 패널티를 부여하여 가중치를 업데이트할 수 있도록 합니다.
그리고 모델이 예측한 값을 평가할 때마다 사용됩니다. 일반적으로 모델의 순전파(forward pass)가 끝난 후에 손실 함수를 사용하여 예측의 오차를 계산합니다.
손실 함수의 역할
예측 오차 측정: 손실 함수는 모델의 예측이 실제 값과 얼마나 차이가 있는지 수치로 표현합니다.
패널티 부여: 오차가 크면 패널티도 커지기 때문에 모델은 더 많은 가중치 업데이트를 하게 됩니다.
목표 방향 설정: 최적화 과정에서 손실 함수는 모델이 어떤 방향으로 학습을 진행해야 할지 알려주는 역할을 합니다.
대표적인 손실 함수들:
MSE (Mean Squared Error): 회귀 문제에서 주로 사용되며, 예측과 실제 값의 차이를 제곱한 후 평균을 구합니다.
criterion = nn.MSELoss() # 회귀 문제에서 사용 loss = criterion(predictions, labels) # 예측값과 실제값으로 손실 계산
CrossEntropyLoss: 분류 문제에서 사용되며, 모델이 예측한 확률 분포와 실제 클래스 분포 간의 차이를 측정합니다.criterion = nn.CrossEntropyLoss() # 분류 문제에서 자주 사용 loss = criterion(predictions, labels) # 모델의 예측값과 실제 라벨로 손실 계산
텐서(Tensor)
텐서는 다차원 배열(multi-dimensional array)로, 스칼라(0D), 벡터(1D), 행렬(2D), 그리고 그 이상의 차원(3D, 4D 등)을 가질 수 있습니다. PyTorch에서는 torch.Tensor 클래스를 사용하여 텐서를 생성하고 조작합니다.
언제, 어디서, 어떻게, 왜 쓰이나?
텐서는 데이터 전처리, 모델 입력, 출력, 손실 계산, 그리고 그래디언트 계산 등의 모든 과정에서 사용됩니다. 주로 딥러닝 라이브러리(예: PyTorch, TensorFlow)에서 주로 사용됩니다. 신경망의 입력 및 출력, 매개변수의 저장 및 업데이트 등 다양한 곳에서 활용됩니다.
텐서는 복잡한 데이터 구조를 효율적으로 다루고, GPU를 통한 병렬 처리를 가능하게 해줍니다. 따라서 대량의 데이터를 처리하고 수치 연산을 최적화하는 데 필수적입니다.'
텐서 생성
import torch # 1D 텐서 생성 vector = torch.tensor([1, 2, 3]) # 벡터 print(vector) # 2D 텐서 생성 (행렬) matrix = torch.tensor([[1, 2], [3, 4]]) # 행렬 print(matrix) # 랜덤한 3D 텐서 생성 random_tensor = torch.rand(2, 3, 4) # 2x3x4 텐서 print(random_tensor)
텐서 연산:
# 텐서의 덧셈 a = torch.tensor([1, 2, 3]) b = torch.tensor([4, 5, 6]) result = a + b # 텐서 덧셈 print(result) # 텐서의 곱셈 matrix_a = torch.tensor([[1, 2], [3, 4]]) matrix_b = torch.tensor([[5, 6], [7, 8]]) matrix_result = torch.matmul(matrix_a, matrix_b) # 행렬 곱셈 print(matrix_result)
텐서 속성:# 텐서의 속성 tensor = torch.tensor([[1, 2], [3, 4]]) print(tensor.shape) # 텐서의 차원 출력 (2x2) print(tensor.size()) # 텐서의 크기 출력 (2x2) print(tensor.dtype) # 데이터 타입 출력 (예: torch.int64)
텐서와 딥러닝 모델:import torch.nn as nn # 간단한 신경망 모델 정의 class SimpleModel(nn.Module): def __init__(self): super(SimpleModel, self).__init__() self.fc = nn.Linear(2, 2) # 입력 2, 출력 2 def forward(self, x): return self.fc(x) # 모델 생성 및 텐서로 예측 model = SimpleModel() input_tensor = torch.tensor([[1.0, 2.0]]) output_tensor = model(input_tensor) # 텐서를 모델에 통과시킴 print(output_tensor)
어떻게 보면 값들을 그냥 파이썬에서 쓰는 것 처럼 단순히 숫자 배열처럼 보이지만, 이 tensor 객체는 일반적인 리스트나 배열과는 여러 면에서 다릅니다.
1. 데이터 타입 및 메모리 효율
고정된 데이터 타입: torch.Tensor는 메모리에서 연속적인 공간에 저장되며, 모든 요소가 동일한 데이터 타입을 가집니다. 이는 메모리 효율성을 높이고 연산 속도를 향상시킵니다.
다양한 데이터 타입 지원: torch.Tensor는 여러 데이터 타입(예: torch.float32, torch.int64 등)을 지원하며, 필요에 따라 쉽게 변환할 수 있습니다.
2. 다차원 지원
다양한 차원: torch.Tensor는 스칼라(0D), 벡터(1D), 행렬(2D), 그리고 그 이상의 차원(3D, 4D 등)을 가질 수 있어 복잡한 데이터 구조를 표현하는 데 적합합니다. 일반적인 파이썬 리스트나 NumPy 배열은 이러한 다양한 차원을 자연스럽게 지원하지 않습니다.
3. GPU 가속
GPU 연산: torch.Tensor는 GPU에서 실행될 수 있으며, 대량의 데이터를 병렬 처리하여 연산 속도를 크게 높입니다. 일반적인 파이썬 리스트는 CPU에서만 실행됩니다.
4. 수학적 연산 및 자동 미분
벡터화 연산: torch.Tensor는 텐서 간의 연산(덧셈, 곱셈 등)을 효율적으로 수행할 수 있도록 설계되었습니다. 예를 들어, 텐서 간의 덧셈은 내부적으로 최적화되어 빠르게 실행됩니다.
자동 미분 지원: torch.Tensor는 requires_grad=True로 설정하면, 해당 텐서에 대한 모든 연산을 추적하여 자동으로 기울기를 계산할 수 있습니다. 이는 딥러닝에서 매우 중요한 기능입니다.
5. 다양한 내장 함수
함수와 메서드: torch.Tensor는 수학적 연산, 통계적 계산, 차원 조작 등을 위한 다양한 내장 함수를 제공합니다. 이는 사용자에게 더 많은 기능을 제공하고 복잡한 계산을 단순화합니다.
전이 학습 (Transfer Learning)
한 도메인에서 학습한 모델의 지식을 다른 도메인에서 활용하는 기법입니다. 특히, 데이터가 부족한 경우 효과적입니다.
보통, 데이터가 적거나 불균형한 경우, 사전 학습된 모델의 지식을 활용하여 성능을 개선할 수 있으며, 큰 모델을 처음부터 학습하는 데 시간이 걸리는 경우, 전이 학습을 통해 빠르게 모델을 구축할 수 있습니다.
그리고 각 모델에 따라 쓰이는 곳도 다릅니다.
컴퓨터 비전: 이미지 분류, 객체 탐지, 스타일 전이 등에서 사용됩니다.
자연어 처리: 텍스트 분류, 감정 분석, 질문 응답 시스템 등에서 활용됩니다.
게다가 사전 학습된 모델을 활용해 유명한 네트워크(예: VGG, ResNet, BERT 등)를 사용하여 모델을 불러오고, 최상위 레이어를 교체하여 새로운 작업에 맞게 조정합니다.
파인튜닝란 개념도 있는데, 새로운 데이터셋에 맞게 일부 레이어의 가중치를 조정하거나 모든 레이어를 미세 조정하여 최적화합니다.
전이 학습의 주요 단계
사전 학습된 모델 선택: 대규모 데이터셋(예: ImageNet)에서 학습된 모델을 선택합니다.
모델 수정: 마지막 레이어를 새로운 작업에 맞게 수정합니다. (예: 출력 클래스 수 변경)
모델 학습: 수정된 모델을 새로운 데이터셋으로 학습합니다.
평가 및 미세 조정: 모델 성능을 평가하고 필요에 따라 추가 조정을 합니다.
예를 들어, ResNet을 쓸 때에는,
추가로import torchvision.models as models import torch.nn as nn import torch.optim as optim #1. 사전 학습된 모델 로드 #ResNet18 모델 불러오기 (사전 학습된 가중치 사용) model = models.resnet18(pretrained=True) #2. 모델 수정 #마지막 레이어 수정 (예: 10개의 클래스 분류) num_classes = 10 model.fc = nn.Linear(model.fc.in_features, num_classes) # fc 레이어 수정 #3. 학습 준비 #옵티마이저 설정 optimizer = optim.Adam(model.parameters(), lr=0.001) # 손실 함수 설정 (다중 클래스 분류용) criterion = nn.CrossEntropyLoss() #4. 모델 학습 #예시로 주어진 데이터로 모델을 학습하는 루프 for epoch in range(num_epochs): model.train() # 학습 모드로 설정 optimizer.zero_grad() # 기울기 초기화 outputs = model(inputs) # 모델의 예측 loss = criterion(outputs, labels) # 손실 계산 loss.backward() # 기울기 계산 optimizer.step() # 가중치 업데이트
# VGG16 모델 불러오기 (사전 학습된 가중치 사용) model = models.vgg16(pretrained=True) # BERT 토크나이저 및 모델 불러오기 (사전 학습된 가중치 사용) tokenizer = BertTokenizer.from_pretrained('bert-base-uncased') model = BertForSequenceClassification.from_pretrained('bert-base-uncased', num_labels=10)
데이터 증강 (Data Augmentation)
데이터셋을 인위적으로 늘려 모델의 일반화 능력을 향상시키는 기법입니다. 특히 이미지나 텍스트 데이터에 유용합니다.
텍스트 데이터 증강 방법:
동의어 치환: 특정 단어를 동의어로 바꿔 새로운 문장을 생성합니다.
무작위 삭제: 일부 단어를 무작위로 삭제하여 문장을 변형합니다.
하이퍼파라미터 튜닝 (Hyperparameter Tuning)
모델의 성능을 극대화하기 위해 학습률, 배치 크기, 은닉층의 수 등 다양한 하이퍼파라미터를 조정하는 과정입니다. 방법:
Grid Search: 하이퍼파라미터의 모든 조합을 시도하여 최적의 조합을 찾습니다.
Random Search: 하이퍼파라미터 공간에서 임의로 조합을 선택하여 탐색합니다.
Bayesian Optimization: 이전 시도에서의 결과를 바탕으로 더 효율적으로 최적의 하이퍼파라미터를 찾습니다.
내일부터 코드 복습 들어가겠습니다.