machine learning & deep learning

RNN(순환신경망), LSTM(장단기메모리) 실습 : 주가분석, 영화리뷰분석

갬짱 2024. 3. 10. 21:06

 
23.10.15
 

 시계열 데이터(sequence data) 

: 시간에 따라 생성되는 순서가 있는 데이터 ( ex. 영상데이터, 음성데이터, 문자열 데이터 등.. )

이전데이터가 현재데이터여 영향을 미치는 관계성을 가진다.

이는 시간축 신경망 모델(time serires model)을 이용하여 분석하고 예측한다. ( ex. RNN, LSTM, transformer )

시계열 데이터 처리 패턴와 예

 


 

RNN( Recurrent Neural Network) : 순환신경망 모델

이전 단계의 출력(hidden state : ht)을 현재 단계의 입력(input : xt)과 함께 사용하여 학습(output : yt)하는 모델

 

 

RNN모델에서 hidden layer(=메모리셀)는 이전, 현재 정보를 모두 weight sum 하여 압축하고 활성화시킨다 ( 주로 하이퍼볼릭 탄센트( tanh 이용) )

 
같은 시간단계(step)에서 한방향의 weight는 시간타이밍과 관계없이 동일 한 값으로 BP(역전파) 시에 업데이트 된다. ( = 가중치공유)

이전벡터의 크기 = Dh , 입력벡터의 크기 = d / Wx는 Dh*d 로 설계됨 -> 이전 벡터크기와 동일한 크기(Dh)로 맞추어진다.

 

이러한 RNN의 가장 큰 한계점은  장기의존성문제(The problem of Long-term Dependencies) 이다.

분석 시퀀스가 길어질수록 이전정보의 보존성이 낮아진다는 것이다.( =시퀀스 의존성이 낮아짐 )

또 시퀀스가 길어질수록 순환구조로 인한 gradient 소실 혹은 폭발, 고정윈도우크기만 기억하는 문제점이 발생할 수 있다.

 


 

LSTM( long time term memory ) : 장단기메모리 신경망

RNN의 변형아키텍처로 장기의존성문제를 해결하기 위해 셀상태 & 게이트 매커니즘을 도입했다. ( 3개의 gate와 1개의 state )

Forget gate (f) : 정보( input정보와 hidden정보의 합산연결 )중 어떤 것을 삭제할지 결정한다. ( 0~1 사이의 정도 )

 

Input gate(i) : 정보( input정보와 hidden정보의 합산연결 )중 어떤 것을 입력할지 결정한다. ( 0~1 사이의 중요도 )
 

Cell state(c) : 메모리셀의 역할로 장기의존성정보를 저장하고 전달

- 시간단계마다 update( ft, fi를 반영하여 입력, 수정, 삭제 ) : 기존정보의 유지정도(f) + 새정보의 입력정도(i)

 

Output gate(o) : 어떤정보를 다음 은닉층으로 전달할지 결정한다. ( 0~1 사이의 중요도 ) -> 전달정보 ht가 새롭게 생성된다.

 

유사한 모델로 GRU(쾌적한 순환 유닛, Gated Recurrent Unit)이 존재한다.

LSTM보다 게이트 수를 줄이면서 성능을 보장하는 모델이다.

 

LSTM과 GRU

 


 

LSTM을 이용한 삼성전자 주가분석

 

 1. 주가 데이터 불러오기 

 

pandas.read_csv : CSV (Comma-Separated Values) 파일에서 데이터를 읽어와 데이터프레임(DataFrame)을 생성

## idex_col : 데이터 프레임에서 해당 열을 인덱스(각 행을 고유하게 식별하는 역할)로 지정

# pandas를 이용해서 csv 파일을 읽어오기
import pandas as pd
df = pd.read_csv(os.path.join(root_dir, 'samsung_cospi.csv'), index_col='Date')
df.head(5) #최상단 5개출력
 

 
 

2-1. 학습준비 : 데이터 처리

 

입력데이터X와 이에 대한 정답데이터y를 생성

pandas.DataFrame.drop() : 데이터프레임에서 지정된 행 또는 열을 삭제

pandas.DataFrame.iloc[행, 열] : 데이터프레임(DataFrame)에서 행(row)과 열(column)을 정수 기반의 위치로 선택하는 pd메서드

X = df.drop(columns='Adj Close') #target값(Adj Close)을 제외한 입력값 -> 나머지 5개 feature
y = df.iloc[:, 4:5]              #taraget값(Adj Close)이 있는 4열을 slicing -> 1개 feature
print(X, y, sep='\n\n')
 

 

*정규화

이때 주식 데이터가 서로다른 단위의 feature로 구성되어있는 특징을 볼 수 있다.

[ high, low, open, close -> 가격값(원) / volume -> 수량(천만개) ]

 

정규화 작업(스케일링)을 통해 상이한 데이터 단위를 일치시켜줘야한다. 그렇지 않으면 단위가 큰 volume값에 민감하게 반응하게 된다..

 

sklearn.preprocessing.MinMaxScaler : 데이터를 특정 범위 내(0~1)로 조정한다 -> 0(최소값) ~ 1(최대값) 으로 매핑

=> MMS객체.fit_transform(데이터)

!pip install scikit-learn==1.2.2

from sklearn.preprocessing import MinMaxScaler

#MinMaxScaler객체 2개 생성
mm = MinMaxScaler()
ss = MinMaxScaler()

# mm을 통해 y(output)을 scaling
y_mm = mm.fit_transform(y)
y_mm

# mm을 통해 X(input)을 scaling
X_ss = ss.fit_transform(X)
X_ss
 

 

 

*데이터셋 분리

# X_train: 학습데이터 + y_train : 학습데이터 정답 ( 4800개 )
# X_test : 테스트데이터 + y_test : 테스트데이터 정답( 이후 )

X_train = X_ss[:4800, :]
X_test = X_ss[4800:, :]
y_train = y_mm[:4800, :]
y_test = y_mm[4800:, :]
 
 

데이터들을 RNN의 input, output 텐서형태( Xt : batch_size, 1 , input_size )로 변환해야한다.

  •  batch_size : 한번에 처리할 데이터의 양 (행)
  • n : (1) 각 데이터포인트의 시간단계 ( timestep ), 시계열 데이터의 길이 ( sequence_length ) => inputs, outputs               (2) 순환 레이어의 갯수( num_layers ), 총 recurrent의 횟수 => hidden, cell                                                                  => 1이라면 한 단계( 하나의 layer, recurrent, timestep )을 의미한다. 생략가능
  • input_size, hidden_size : feature의 수 (열)
RNN 입력데이터

 

xt = 하나의 timestep에서의 입력

X = bs * is 의 데이터를 n시간(번)만큼 입력받는다.

RNN 출력데이터

 

ht = 하나의 출력 ( sequential 하지 않은 출력)

h1:n = bs*hs의 데이터를 n시간(번)만큼 출력한다.

 

 

 

 

다차원배열로 얻은 데이터 -> pytorch 텐서 -> RNN 텐서 순서로 변환해준다.

torch.FloatTensor: 실수(float) 데이터를 가지는 PyTorch 텐서를 생성

#입력데이터(X) : ( bs, 1, is )의 형태로 변환 : reshape(4800, 1, 5) / reshape(619, 1, 1)
#X_train = torch.FloatTensor(X_train).reshape(X_train.shape[0], 1, X_train.shape[1])
#X_test = torch.FloatTensor(X_test).reshape(X_test.shape[0], 1, X_test.shape[1])
X_train = torch.FloatTensor(X_train).reshape(X_train.shape[0], 1, 5)
X_test = torch.FloatTensor(X_test).reshape(X_test.shape[0], 1, 5)

#정답데이터(y) : ( bs, 1, hs )의 형태로 변환 : sqential 하지 않음 -> RNN 텐서형식을 따를필요없음 / 텐서로만 변환
y_train = torch.FloatTensor(y_train)
y_test = torch.FloatTensor(y_test)
 
 

2-2. 학습준비 : 모델설계

 

다양한 하이퍼파라미터 설정 ( = 사전설정 : model training configuration)을 수행

n_epochs = 5000        #에폭수 : 전체데이터를 학습하는 횟수
learning_rate = 0.001  #loss에 따라 parameter를 업데이트하는 정도
input_size = 5    # feature 수
hidden_size = 2   # linear layer를 통과하여 1이 된다.(outputsize)
num_layers = 1    # LSTM layer의 수 = recurrent 수
num_classes = 1   # 예측값의 수 = hidden_size가 결국 도출되어야하는 값
 

 

설계한 Model의 구조 = LSTM 층 1개 + FCL층 2개

  • torch.nn.LSTM() : PyTorch에서 제공하는 LSTM모델 클래스 -> 하이퍼파라미터만 설정해주면 알아서 hidden state와 cell state를 구성하여 작동한다.
  • torch.nn.Linear() : PyTorch에서 제공하는 선형레이어를 나타내는 클래스 -> in_features(입력특성), out_features(출력특성)만 지정해주면 알아서 weight를 구성하고 학습해준다. ( 벡터화하여 변환, 값유지 )

( :: hidden size의 결과물 -> 128차원 벡터 -> class size(1차원 벡터)(prediction 값)로 변환 )

 

 

 

torch.view() : 배열(tensor)의 형상(shape)을 변경하거나 조작 ( 원본 텐서와 메모리를 공유 -> 같은 메모리 위치를 참조, 변경영향 )

torch.reshape() : 원본 텐서와 메모리를 공유하지 않고 메모리공간 새로 할당 ( 변경에 영향x)

#모델정의
class Model(nn.Module):
  def __init__(self, input_size, hidden_size, num_classes, num_layers, seq_length):
    #1. 초기화 작업
    self.input_size = input_size
    self.hidden_size = hidden_size
    self.num_classes = num_classes
    self.num_layers = num_layers
    self.seq_length = seq_length

    super(Model, self).__init__()

    #LSTM 객체 정의 : 만들어진 내부구조에 is, hs, num_layers등만 결정한다..
    self.lstm = nn.LSTM(
        input_size=input_size,
        hidden_size=hidden_size,
        num_layers=num_layers,
        batch_first=True  #(bs,1,is)형태
    )

    #FCL구성 : 2개
    #nn.Linear
    self.fc_1 = nn.Linear(hidden_size, 128) #2차원벡터 -> 128차원벡터
    self.fc = nn.Linear(128, num_classes)   #128차원벡터 -> 1차원벡터(=output=prediction)
    self.relu = nn.ReLU()

  #2. forward pass
  def forward(self,x):
    output, (hn, cn) = self.lstm(x)
    #입력값을lstm구현함수에 통과시킨다. -> 내부적으로 output, hn, cn도출
    #output.shape : [48000, 1, 2]
    #hn.shape : [1, 48000, 2]
    #cn.shape : [1, 48000, 2]
    #사실상 hn, output은 데이터형태만 다를뿐 내용은 똑같다

    hn = hn.view( -1, self.hidden_size ) #(48000,2)
    out = self.relu(hn)                  #(48000,2)
    out = self.fc_1(out)                 #(48000,128)
    out = self.relu(out) #형상유지
    out = self.fc(out)                   #(48000,1) : 48000개의 prediction값들

    return out
 

 

학습을 위한 loss함수, optimizer 구축

nn.MSELoss() : PyTorch에서 제공하는 평균 제곱 오차(Mean Squared Error, MSE) 손실 함수

torch.optim.Adam() : PyTorch에서 제공하는 Adam(Adaptive Moment Estimation) 최적화 알고리즘 : 경사 하강법으로 신경망학습

## model.parameters() : 모델에서 학습할 모든 파라미터 전달

## lr = learning_rate : 학습정도를 결정하는 학습률 전달

model= Model(input_size, hidden_size, num_classes, num_layers, seq_length=X_train.shape[1])
loss_function = nn.MSELoss()  #MSELoss함수 이용
optimizer = optim.Adam(model.parameters(), lr=learning_rate)
 

 

3. 모델학습

 

배치사이즈가 곧 전체사이즈(48000) => 전체데이터의 로테이션( epoch )마다 학습을 진행한다.

 

optimizer.zero_grad() : 모델의 그래디언트(gradient)를 초기화(0) -> 이전 반복에서의 그래디언트 값이 누적되지 않도록 함

loss.backward() : loss함수에서 도출된 결과로 gradient를 계산

optimizer.step() : 계산된 gradient를 바탕으로 가중치 업데이트 진행

for epoch in range(n_epochs):
  #에폭수(5000)만큼 전체 데이터 로테이션돌림 => 4800개(배치이자 전체데이터)의 데이터를 5000번 학습한다.

  outputs = model(X_train.to(device))
  optimizer.zero_grad()   #매 에폭마다 optimizer 초기화

  # backward pass : loss값 역시 gpu로 보낸다.
  loss = loss_function(outputs, y_train.to(device))
  loss.backward()   #gradient 계산
  optimizer.step()  #옵티마이저로 가중치 업데이트

  if epoch % 100 == 0: #에폭단위 100마다 loss값을 확인해본다.(=training loss)
    print("Epoch  :  %d,   loss  :  %.5f" % (epoch, loss.item()))
 
train data에 대한 손실률

5000번의 학습결과 -> 초기 8%였던 손실률 0.001%까지 내려가는 것을 확인할 수 있다..

 

 

 

4. 테스트 확인

 

마지막까지 학습한 모델을 가지고 테스트 추론을 시작한다.

연산으로 인해 GPU에 저장된 값을 CPU로 가져와서 시각화하고자 한다.

역정규화했던 모든 값들중 예측값(adj close)만 원래의 scale로 역정규화하여 확인한다. ( 정규화이전의 값을 담고 있는 MMS객체 이용)

 

MMS객체.inverse_transform(대상) : 사이킷런에서 데이터 전처리 후 모델의 예측값을 원래 스케일이나 형태로 복원하는 데 사용

prediction = model(X_test.to(device))                #test데이터를 가지고 예측진행
prediction = prediction.data.detach().cpu().numpy()  #gpu에 위치한 prediction값을 cpu로 가져온다. -> np배열화

#역정규화 : 예측값(adj close)을 원래의 scale로 돌려서 결과를 확인할 것이다!
prediction = mm.inverse_transform(prediction) #예측결과 역정규화
y_test = mm.inverse_transform(y_test)         #정답값 역정규화

plt.figure(figsize=(10, 6))
plt.plot(y_test, label='Actual Data')
plt.plot(prediction, label='Predicted Data')
plt.title('Time-Series Prediction')

plt.legend(loc='upper left')
plt.show()
 

 


 

LSTM을 이용한 영화리뷰 분석

 

0. TorchText 설치 & IMDB 데이터셋 다운로드 및 전처리

: Pytorch에서 자연어 처리(Natural Language Processing, NLP) 작업을 위한 데이터셋 처리작업 라이브러리

( 텍스트 데이터를 로드하고 전처리하는 데 사용 )

!pip install torchtext==0.6.0
 

 

torchtext.data.Field() : 텍스트 데이터를 어떻게 처리할지 정의하는 클래스-> 토큰화 및 전처리 방법 지정, vocabulary 지정 등..

# sequential : 시퀀스 데이터 여부 (default: True)

# use_vocab : 단어 집합(vocabulary)을 만들 것인가 (default: True)

# lower : 영어 데이터를 전부 소문자화 (default: False)

# batch_first : 미니 배치 차원을 맨 앞으로 할 것인가 (default: False)

# fix_length : 최대 허용 길이. 이 길이에 맞춰서 패딩 (길이의 일관성)

 

IMDB 데이터셋 : 텍스트 분류(text classification) 연습용 데이터셋 (영화 리뷰 데이터)

-- torchtext.datasets.IMDB = 리뷰 텍스트("text" 키) + 리뷰의 긍정(1) 또는 부정(0) 레이블("label" 키)

 

vars() : 대상을 딕셔너리 형태로 반환 (주어진 객체의 __dict__ 속성을 반환)

fields 속성 : 데이터셋의 각 열(column), 해당 열의 데이터를 어떻게 처리할지를 정의

items() : 속성을 딕셔너리 형태로 반환

from torchtext import data, datasets
import torch.nn as nn
import torch

TEXT = data.Field(sequential=True, batch_first=True, lower=True)  #문장데이터 형식담을 객체(리뷰)
LABEL = data.Field(sequential=False, batch_first=True)            #정답데이터 형식담을 객체(good/bad)

#IMDB데이터셋을 지정한 TEXT와 LABEL에 맞게 trainset, testset으로 나누어준다.
trainset, testset = datasets.IMDB.splits(TEXT, LABEL)

print(vars(trainset[0]))
print(trainset.fields.items())
 
{'text': ['allison', "dean's", 'performance', 'is', 'what', 'stands', 'out', 'in', 'my', 'mind', 'watching', 'this', 'film.', 'she', 'balances', 'out', 'the', 'melancholy', 'tone', 'of', 'the', 'film', 'with', 'an', 'iridescent', 'energy.', 'i', 'would', 'like', 'to', 'see', 'more', 'of', 'her.'], 
 'label': 'pos'}
dict_items([('text', <torchtext.data.field.Field object at 0x7bff1397be20>), ('label', <torchtext.data.field.Field object at 0x7bff13978c40>)])
 

 

 *단어 집합(Vocabulary) 생성 

 

Field 클래스 객체.build_vocab() : Field 클래스로 정의한 객체에 대해서 vocab(단어 사전)를 구축하는 데 사용

# min_freq = 최소 등장단어수 -> 이 이상등장하는 것들로 vocab을 생성

# max_size = 단어집합의 크기제한

# 이때 2개의 모든 단어집합에는 special token이 저장 <unk> <pad>

 

단어집합.stoi : vocab의 인덱스 = 해당 글자의 one-hot incoding된 위치를 말한다. ('<unk>' 0번째 , '<pad>' 1번째)

TEXT.build_vocab(trainset, min_freq=10, max_size=10000)
print("단어 집합(Vocabulary) 크기: ", len(TEXT.vocab))
print(TEXT.vocab.stoi)
 

 

1-1. 학습준비 - 데이터 처리

 

*데이터셋 구축

기존 train data를 한번더 나누어서 validation data를 만들고자 한다.

 

▶ validation data : 모델의 성능을 평가하고 조정하기 위해 사용 ( 학습데이터와는 별도의 것으로, 학습의 대상이 아님)

배치를 사용하여 모델을 학습하고 업데이트한 다음, 해당 배치에 대한 모델의 파라미터(가중치 및 편향)를 사용하여 검증 데이터를 평가

(= 개별 배치의 update를 평가하는 검증용 데이터 -> training loss보다 더 유의미한 validation loss를 도출 )

#trainingset을 한번더 split한다 -> train용(80%)과 validation용(20%)으로 구분하고자 한다.
trainset, validset = trainset.split(split_ratio=0.8)
 

▶ 배치화(batchify) : 학습할 때 사용되는 데이터를 작은 묶음 또는 배치로 나누는 과정, 이는 trainset에서 랜덤하게 선별(shuffle) -> 과적합 방지 / 에폭은 여러 미니배치들로 구성되어있으며 여러번 update가 발생할 수 있다

 

torchtext.data.BucketIterator.splits : 데이터셋을 미니배치로 나누고 배치를 생성, 주로 텍스트 데이터를 다룬다.

훈련 데이터, 검증 데이터, 테스트 데이터를 지정한 batch_size로 나눈다. -> 각 부분은 데이터의 크기에 따라 유사한 크기의 배치로 그룹화

next(iter(배치의 그룹)) : 하나의 배치를 추출

batch_size = 64
learning_rate = 1e-3  #0.001
n_epochs = 10         #전제 데이터 반복횟수

train_iter, val_iter, test_iter = data.BucketIterator.splits(
    (trainset, validset, testset), batch_size=batch_size, shuffle=True, repeat=False
)

mini_batch = next(iter(train_iter))
print(mini_batch)

print(mini_batch.text.shape)
print(mini_batch.text)
 
[torchtext.data.batch.Batch of size 64]
	[.text]:[torch.LongTensor of size 64x957]
	[.label]:[torch.LongTensor of size 64]

torch.Size([64, 957])
tensor([[    5,   532,    27,  ...,     1,     1,     1],
        [   94,   296,     2,  ...,     1,     1,     1],
        [    2,    20,    14,  ...,     1,     1,     1],
        ...,
        [   82,     5,   364,  ...,     1,     1,     1],
        [   30,     5,    57,  ...,     1,     1,     1],
        [  225, 19167,     0,  ...,     1,     1,     1]])
 

배치구조 : 텍스트데이터 + 이에 대한 정답데이터

텍스트데이터, 정답데이터구조 : 각 단어가 vocab번호로 배치 & 배치내의 최대크기 문장(801)에 맞추어서 pad(1)이 채워져있다.

 

1-2. 학습준비 : 모델설계

 

설계한 Model의 구조 = 임베딩층 + LSTM층(n_layers개) + FCL층 1개 + dropout

  • torch.nn.Embedding(vocab,embed ) : 자연어 처리 (NLP)에서 고유인덱스에 해당하는 고정 차원의 벡터(embedding vector)를 반환
  • torch.nn.LSTM

# input_size : 입력 특성차원

# hidden_size : 은닉(출력) 특성차원

# num_layers

# batch_first=batch_first

  • torch.nn.Linear ( :: hidden size의 결과물을 class size(2차원 벡터)로 변환 )
  • torch.nn.Dropout(p) : p의 확률만큼 dropout 진행

 

임베딩(Embedding) : voacb_size(입력텐서의 input_size)의 문자열데이터를(원핫인코딩) 지정한 embed_size만큼으로 축소(열축소)

vs를 es로 선형변환하는 것

class Model(nn.Module):
  def __init__(self, n_layers, hidden_dim, n_vocab, embed_dim, n_classes, dropout_p=0.2):
    self.n_layers = n_layers
    self.hidden_dim = hidden_dim

    super(Model, self).__init__()

    self.embed = nn.Embedding(n_vocab, embed_dim) #input size(vocab size) -> embed size로 축소
    self.lstm = nn.LSTM(embed_dim, self.hidden_dim, num_layers=self.n_layers, batch_first=True)
    #LSTM에 들어가는 것은 축소변환된 embed_dim이 된다!!
    self.out = nn.Linear(self.hidden_dim, n_classes) #도출되는 hidden size를 -> 최종 2개의 클래스
    self.dropout = nn.Dropout(dropout_p)

  def forward(self, x):             #(bs,1,vs)
    x = self.embed(x)

    output, (hn, cn) = self.lstm(x) #(bs,1,es)
    hn = hn[1]                      #마지막 시점의 hn = (bs, hs) : sequential하지 않음
    hn = self.dropout(hn)
    logit = self.out(hn)            #linear처리 -> 길이가 2인 벡터(각 pos, neg에 대한 확률값)

    return logit
 

2. 모델학습(train)

 

  • train 함수 : train_iter(train 배치그룹)을 받아 학습처리
  • evaluate 함수 : valid_iter(valid 배치그룹)을 받아 갱신한 가중치의 모델을 검증처리

각 배치에서의 손실, 정답의 합을 구함 -> 평균손실, 평균정확도를 만든다.

**이때 IMDB 데이터셋의 label 데이터(y)는 {1,2}로 구분된 상태이므로 1을 빼서 {0,1}로 만듬

 

 * 모델 학습의 기본 순서(6단계) *

(1) 모델객체.train() : 훈련모드로 전환

(2) optimizer.zero_grad() : 이전 반복의 gradient 계산을 초기화

(3) forward 연산 ( 보통 model(데이터) )

(4) loss 계산 ( 보통 loss = 사용할 loss함수(출력값, 정답) )

(5) backward 연산 = gradient의 계산 ( loss.backward() )

(6) 가중치 갱신(update) ( optimizer.step() )

 

F.cross_entropy( input, target ) : 분류 문제의 손실을 계산하는 데 사용되며, 모델의 예측과 실제 타겟 간의 차이를 측정

# reduction='sum' : eval모드에서 선택하는 옵션 / 정규화, 일관성, 학습률 조절을 위한 선택

torch.item() : PyTorch 텐서의 값을 스칼라로 변환하는 메서드 (손실함수에서 계산한 loss는 pytorch텐서형태 )

 

def train(model, optimizer, train_iter):
  model.train()                        #학습모드
  for _, mini_batch in enumerate(train_iter):
                                       #iter을 반복적으로 추출 => 인덱스, 미니배치데이터(text, label)
    #x, y = mini_batch.text.to(device), mini_batch.label.to(device)
    x, y = mini_batch.text, mini_batch.label
    y.data.sub_(1)
    #y 데이터가 1,2로 구분된 상태
    #1을 빼서 0 과 1로 변환 : 1 -> 0  2 -> 1

    optimizer.zero_grad() #optim초기화

    logit = model(x)                    #forward
    loss = F.cross_entropy(logit, y)    #cross entropy loss 함수로 loss 반환
    #cross entropy :정답(y) value가 1인 인덱스끼리의 확률차이를 비교 = loss
    loss.backward()                     #backward
    optimizer.step()                    #update


def evaluate(model, val_iter):          #update작업을 하지 않기에 optimizer 필요X
  model.eval()                          #평가모드
  corrects, total_loss = 0, 0           #맞힌갯수, 전체 loss
  for mini_batch in val_iter:
    #x, y = mini_batch.text.to(device), mini_batch.label.to(device)
    x, y = mini_batch.text, mini_batch.label
    y.data.sub_(1)

    logit = model(x)
    loss = F.cross_entropy(logit, y, reduction='sum')
    #배치에서 수행되는 loss를 모두 더함 / 배치마다의 손실을 합산한 전체 손실
    # reduction='sum'-> 정규화, 일관성, 학습률 조절을 위한 선택
    
    total_loss += loss.item()
    #item() : PyTorch 텐서의 값을 스칼라로 변환하는 메서드 (텐서 형태의 손실 값을 숫자로 변환)

    corrects += (logit.max(1)[1].view(y.size()).data == y.data).sum()
                 #높은 확률로 예측한 것                 정답
  
  # 평균손실 : 전체loss/미니배치수(iter가 돌아간 횟수)
  avg_loss = total_loss / len(val_iter.dataset)
  # 평균정확도 : 전체 맞춘수/미니배치수(iter가 돌아간 횟수)
  avg_accuracy = 100.0 * corrects / len(val_iter.dataset)

  return avg_loss, avg_accuracy
 

model에 하이퍼파라미터 값을 설정

optimizer에 model의 모든 파라미터를 학습대상으로 설정

 

epoch(1) : train( 내부배치들에서 update ) -> 모델 evaluate

이때 에폭별 모델마다 loss값을 비교해서 최적의 모델을 save한다.

 

model.state_dict(): model 객체의 상태사전(state dictionary)을 반환 (가중치(weight)와 편향(bias) 등의 매개변수의 내용)

torch.save(): PyTorch의 저장 함수, 첫번째 객체를 두번째 경로에 저장

#모델 설정 : lstm layer 2개 / vocab_size를 embed사이즈(400)로 변환 / 256의 hiddensize를 2로 변환 / drop =0.5
model = Model(2, 256, vocab_size, 400, n_classes, 0.5)
# optimizer 설정
optimizer =torch. optim.Adam(model.parameters(), lr=learning_rate)

best_val_loss = None #최소 손실값
for e in range(1, n_epochs+1):
  train(model, optimizer, train_iter)                 #한번의 에폭 train -> 내부 배치들만큼 update
  val_loss, val_accuracy = evaluate(model, val_iter)  #한번의 train이후 validate하여 평균손실, 평균정확도 구함

  #에폭에 해당하는 평균손실, 평균정확도를 구함
  print("Epoch:  %d  |  Val loss:  %.2f  |  Val accuracy:  %.2f" % (e, val_loss, val_accuracy))

  #bvl은 가장 작은 값으로 지속적으로 update
  if not best_val_loss or val_loss < best_val_loss:
    torch.save(model.state_dict(), './txtclassification.pt')  #모델의 현재상태를 가져와서 해당위치에 저장하고
    best_val_loss = val_loss                                  #bvl을 현재 loss로 갱신


print(best_val_loss) #모델의 최종적인 loss값
 

 
epoch이 반복될수록 loss는 작아지고 accuracy는 증가한다..!!!!

 

 

3. 모델검증(test)

 

최적의 모델을 load해온다 -> train_iter을 가지고 검증한다 ( evaluate 함수 재사용 : 배치별로 loss, accuracy를 구해 평균을 계산 )

model.load_state_dict(torch.load('./txtclassification.pt'))
test_loss, test_acc = evaluate(model, test_iter)

print("테스트 오차: %5.2f  |  테스트 정확도: %5.2f" % (test_loss, test_acc))
 
테스트 오차:  0.58  |  테스트 정확도: 76.29
 

테스트 배치들로 검증해본 결과가 된다!