1๏ธโฃ ์๊ณ์ด ๋ฌธ์
๐น ์๊ณ์ด ๋ถ์์ด๋
โช ์๊ฐ์ ๋ฐ๋ผ ๋ณํ๋ ๋ฐ์ดํฐ๋ฅผ ์ฌ์ฉํด ์ถ์ด๋ฅผ ๋ถ์ํ๋ ๊ฒ์ผ๋ก ์ฃผ๊ฐ/ํ์จ๋ณ๋, ๊ธฐ์จ/์ต๋๋ณํ ๋ฑ์ด ๋ํ์ ์ธ ์๊ณ์ด ๋ถ์์ ํด๋นํ๋ค.
โช ์ถ์ธํ์ , ํฅํ์ ๋ง ์์ธก์ ์๊ณ์ด ๋ถ์์ ์ฌ์ฉํ๋ค.
๐น ์๊ณ์ด ํํ
โช ๋ฐ์ดํฐ ๋ณ๋ ์ ํ์ ๋ฐ๋ผ ๊ตฌ๋ถํ ์ ์๋ค.
๋ถ๊ท์น๋ณ๋ | ์์ธก ๋ถ๊ฐ๋ฅํ๊ณ ์ฐ์ฐ์ ์ผ๋ก ๋ฐ์ํ๋ ๋ณ๋. ์ ์, ํ์, ์ง์ง, ํ์ ๋ฑ |
์ถ์ธ๋ณ๋ | GDP, ์ธ๊ตฌ์ฆ๊ฐ์จ ๋ฑ ์ฅ๊ธฐ์ ์ธ ๋ณํ ์ถ์ธ๋ฅผ ์๋ฏธํ๋ค. ์ฅ๊ธฐ๊ฐ์ ๊ฑธ์ณ ์ง์์ ์ผ๋ก ์ฆ๊ฐ, ๊ฐ์ํ๊ฑฐ๋ ์ผ์ ์ํ๋ฅผ ์ ์งํ๋ ค๋ ์ฑ๊ฒฉ์ ๋๋ค. |
์ํ๋ณ๋ | 2~3๋ ์ ๋์ ์ผ์ ํ ๊ธฐ๊ฐ์ ์ฃผ๊ธฐ๋ก ์ํ์ ์ผ๋ก ๋ํ๋๋ ๋ณ๋ |
๊ณ์ ๋ณ๋ | ๊ณ์ ์ ์ธ ์ํฅ๊ณผ ์ฌํ์ ๊ด์ต์ ๋ฐ๋ผ 1๋ ์ฃผ๊ธฐ๋ก ๋ฐ์ํ๋ ๊ฒ์ ์๋ฏธ |
๐น ์๊ณ์ด ๋ฐ์ดํฐ
โช ๊ท์น์ ์๊ณ์ด vs ๋ถ๊ท์น์ ์๊ณ์ด
• ๊ท์น์ ์๊ณ์ด : ํธ๋ ๋์ ๋ถ์ฐ์ด ๋ถ๋ณํ๋ ๋ฐ์ดํฐ
• ๋ถ๊ท์น์ ์๊ณ์ด : ํธ๋ ๋ ํน์ ๋ถ์ฐ์ด ๋ณํํ๋ ์๊ณ์ด ๋ฐ์ดํฐ
โธ ์๊ณ์ด์ ์ ๋ถ์ํ๋ค๋ ๊ฒ์ ๋ถ๊ท์น์ฑ์ ๊ฐ๋ ์๊ณ์ด ๋ฐ์ดํฐ์ ํน์ ํ ๊ธฐ๋ฒ์ด๋ ๋ชจ๋ธ์ ์ ์ฉํ์ฌ ๊ท์น์ ์ธ ํจํด์ ์ฐพ๊ฑฐ๋ ์์ธกํ๋ ๊ฒ์ ์๋ฏธํ๋ค.
โช AR,MA,ARMA,ARIMA,๋ฅ๋ฌ๋ ๊ธฐ๋ฒ ๋ฑ์ด ์ฌ์ฉ๋๋ค.
2๏ธโฃ AR, MA, ARMA, ARIMA
โช ์๊ณ์ด ๋ถ์์ ์ผ๋ฐ์ ์ธ ๋จธ์ ๋ฌ๋์์ "์๊ฐ" ์ ๋ ๋ฆฝ๋ณ์๋ก ์ฌ์ฉํ๋ค๋ ํน์ง์ด ์๋ค.
๐น AR ๋ชจ๋ธ
• Auto Regressive ์๊ธฐํ๊ท ๋ชจ๋ธ
• ์ด์ ๊ด์ธก๊ฐ์ด ์ดํ ๊ด์ธก๊ฐ์ ์ํฅ์ ์ค๋ค๋ ์์ด๋์ด์์ ์์ํ ๊ฒ์ผ๋ก ์ด์ ๋ฐ์ดํฐ์ '์ํ' ์์ ํ์ฌ ๋ฐ์ดํฐ์ ์ํ๋ฅผ ์ถ๋ก ํ๋ค.
• โ ๋ฒ ๋ถ๋ถ : ์๊ณ์ด ๋ฐ์ดํฐ์ ํ์ฌ์์
• โก ๋ฒ ๋ถ๋ถ : ๊ณผ๊ฑฐ๊ฐ ํ์ฌ์ ๋ฏธ์น๋ ์ํฅ์ ๋ํ๋ด๋ ๋ชจ์์ ์๊ณ์ด ๋ฐ์ดํฐ์ ๊ณผ๊ฑฐ ์์ ์ ๊ณฑํ ๊ฒ
• โข ๋ฒ ๋ถ๋ถ : ๋ฐฑ์์ก์, ์๊ณ์ด ๋ถ์์์์ ์ค์ฐจํญ
๐น MA ๋ชจ๋ธ
• Moving average ์ด๋ํ๊ท ๋ชจ๋ธ
• ํธ๋ ๋ (ํ๊ท or ์๊ณ์ด ๊ทธ๋ํ์์ y ๊ฐ) ๊ฐ ๋ณํํ๋ ์ํฉ์ ์ ํฉํ ํ๊ท๋ชจ๋ธ
• window ๋ผ๋ ๊ฐ๋ ์ ์ฌ์ฉํ์ฌ ๊ทธ ํฌ๊ธฐ๋งํผ ์ด๋ํ๋ค ํ์ฌ ์ด๋ํ๊ท ๋ชจ๋ธ์ด๋ผ ๋ถ๋ฅธ๋ค.
• โก ๋ฒ ๋ถ๋ถ : ๋งค๊ฐ๋ณ์ θ ์ ๊ณผ๊ฑฐ ์์ ์ ์ค์ฐจ๋ฅผ ๊ณฑํ ๊ฒ์ผ๋ก, ์ด์ ๋ฐ์ดํฐ์ ์ค์ฐจ์์ ํ์ฌ ๋ฐ์ดํฐ์ ์ํ๋ฅผ ์ถ๋ก ํ๋ค.
๐น ARMA ๋ชจ๋ธ
• ์๊ธฐํ๊ท ์ด๋ํ๊ท ๋ชจ๋ธ, ์ฃผ๋ก ์ฐ๊ตฌ๊ธฐ๊ด์์ ์ฌ์ฉํ๋ค.
• AR, MA ๋ ๊ฐ์ง ๊ด์ ์์ ๊ณผ๊ฑฐ์ ๋ฐ์ดํฐ๋ฅผ ์ฌ์ฉํ๋ค.
๐น ARIMA ๋ชจ๋ธ
• ์๊ธฐ ํ๊ท ๋์ (integrated) ์ด๋ ํ๊ท ๋ชจ๋ธ : AR๊ณผ MA ๋ฅผ ๋ชจ๋ ๊ณ ๋ คํ๋ ๋ชจํ์ธ๋ฐ, ARMA ์ ๋ฌ๋ฆฌ ๊ณผ๊ฑฐ ๋ฐ์ดํฐ์ ์ ํ์ ๊ด๊ณ ๋ฟ ์๋๋ผ ์ถ์ธ๊น์ง ๊ณ ๋ คํ ๋ชจ๋ธ์ด๋ค.
from statsmodels.tsa.arima_model import ARIMA
• ARIMA(data, order = (p,d,q))
โธp : ์๊ธฐํ๊ท์ฐจ์
โธd : ์ฐจ๋ถ์ฐจ์
โธq : ์ด๋ํ๊ท ์ฐจ์
โธmodel.fit() : ํ๋ จ
โธmodel.forecast() : ์์ธก
3๏ธโฃ RNN
๐น Recurrent Neural Network
• ์ด์ ์๋์ธต์ด ํ์ฌ ์๋์ธต์ ์ ๋ ฅ์ด ๋๋ฉด์ ๋ฐ๋ณต๋๋ ์ํ ๊ตฌ์กฐ๋ฅผ ๊ฐ๋๋ค.
• ์ด์ ์ ์ ๋ณด๋ฅผ ๊ธฐ์ตํ๊ณ ์๊ธฐ ๋๋ฌธ์ ์ต์ข ์ ์ผ๋ก ๋จ๊ฒจ์ง ๊ธฐ์ต์ ๋ชจ๋ ์ ๋ ฅ ์ ์ฒด๋ฅผ ์์ฝํ ์ ๋ณด๊ฐ ๋๋ค.
• ์์ฑ์ธ์, ๋จ์ด์ ์๋ฏธํ๋จ ๋ฐ ๋ํ ๋ฑ์ ์์ฐ์ด์ฒ๋ฆฌ์ ํ์ฉ๋๊ฑฐ๋ ์๊ธ์จ, ์ผ์๋ฐ์ดํฐ ๋ฑ์ ์๊ณ์ด ๋ฐ์ดํฐ ์ฒ๋ฆฌ์ ํ์ฉ๋๋ค.
๐น ๋ค์ํ ์ ํ์ RNN
• ์ผ๋๋ค : ์ ๋ ฅ์ด ํ๋๊ณ ์ถ๋ ฅ์ด ๋ค์์ธ ๊ตฌ์กฐ์ด๋ค. ์ด๋ฏธ์ง๋ฅผ ์ ๋ ฅํด, ์ด๋ฏธ์ง์ ๋ํ ์ค๋ช ์ ๋ฌธ์ฅ์ผ๋ก ์ถ๋ ฅํ๋ ์ด๋ฏธ์ง ์บก์ ์ด ๋ํ์ ์ธ ์ฌ๋ก์ด๋ค.
• ๋ค๋์ผ : ์ ๋ ฅ์ด ๋ค์๊ณ ์ถ๋ ฅ์ด ํ๋์ธ ๊ตฌ์กฐ๋ก, ๋ฌธ์ฅ์ ์ ๋ ฅํด ๊ธ/๋ถ์ ์ ์ถ๋ ฅํ๋ ๊ฐ์ฑ๋ถ์๊ธฐ์์ ์ฌ์ฉ๋๋ ๊ตฌ์กฐ์ด๋ค.
# ๋ค๋์ผ ๋ชจ๋ธ
self.em = nn.Embedding(len(TEXT.vocab.stoi), embedding_dim) # ์๋ฒ ๋ฉ ์ฒ๋ฆฌ
self.rnn = nn.RNNCell(input_dim, hidden_size) # RNN ์ ์ฉ
self.fc1 = nn.Linear(hidden_size, 256) # ์์ ์ฐ๊ฒฐ์ธต
self.fc2 = nn.Linear(256,3) # ์ถ๋ ฅ์ธต
• ๋ค๋๋ค : ์ ๋ ฅ๊ณผ ์ถ๋ ฅ์ด ๋ค์์ธ ๊ตฌ์กฐ, ์๋๋ฒ์ญ๊ธฐ๊ฐ ๋ํ์ ์ธ ์ฌ๋ก์ด๋ค. ํ์ดํ ์น์์๋ ์๋์ ํ์ค ์ฝ๋๋ก ๊ฐ๋จํ๊ฒ ๊ตฌํ์ด ๊ฐ๋ฅํ๋, ํ์ดํ ์น์์๋ seq2seq ๊ตฌ์กฐ๋ฅผ ์ฌ์ฉํ๋ ๋ฐฉ์์ผ๋ก ๊ตฌํ๋๋ค.
keras.layers.SimpleRNN(100, return_sequences = True, name='RNN')
โช ํ ์ํ๋ก์ฐ์์๋ return_sequences =True ์ต์ ์ผ๋ก ์ํ์ค๋ฅผ ๋ฆฌํดํ ์ ์๋๋ก ํ๋ค
โธ ํ์ดํ ์น๋ก ๊ตฌํ
# ๋ค๋๋ค ๋ชจ๋ธ
Seq2Seq(
(encoder) : Encoder(
(embedding) : Embedding(7855,256)
(rnn) : LSTM(256, 512, num_layers=2, dropout=0.5)
(dropout) : Dropout(p=0.5, inplace=False)
)
(decoder) : Decoder(
(embedding) : Embedding(5893,256)
(rnn) : LSTM(256, 512, num_layers=2, dropout=0.5)
(fc_out) : Linear(in_features=512, out_features=5893, bias = True)
(dropout) : Dropout(p=0.5, inplace=False)
)
)
• ๋๊ธฐํ ๋ค๋๋ค : ์ ๋ ฅ๊ณผ ์ถ๋ ฅ์ด ๋ค์์ธ ๊ตฌ์กฐ๋ก, ํ๋ ์ ์์ค์ ๋น๋์ค ๋ถ๋ฅ๊ฐ ๋ํ์ ์ธ ์ฌ๋ก์ด๋ค.
๐น RNN ๊ณ์ธต๊ณผ ์
• Cell ์ : ์ค์ง ํ๋์ ๋จ๊ณ time step ๋ง ์ฒ๋ฆฌํ๋ ๋จ์๋ฅผ ์๋ฏธํ๋ค. ์ค์ ๊ณ์ฐ์์ ์ฌ์ฉ๋๋ RNN ๊ณ์ธต์ ๊ตฌ์ฑ์์๋ก ๋จ์ผ ์ ๋ ฅ๊ณผ ๊ณผ๊ฑฐ ์ํ๋ฅผ ๊ฐ์ ธ์ ์ถ๋ ฅ๊ณผ ์๋ก์ด ์ํ๋ฅผ ์์ฑํ๋ค.
โช ์ ์ ํ
nn.RNNCell | Simple RNN ๊ณ์ธต์ ๋์๋๋ RNN ์ |
nn.GRUCell | GRU ๊ณ์ธต์ ๋์๋๋ GRU ์ |
nn.LSTMCell | LSTM ๊ณ์ธต์ ๋์๋๋ LSTM ์ |
• Layer ๊ณ์ธต : ์ ์ ๋ํํด ๋์ผํ ์ ์ ์ฌ๋ฌ ๋จ๊ณ์ ์ ์ฉํ๋ค.
๐ ํ์ดํ ์น์์๋ ๋ ์ด์ด์ ์ ์ ๋ถ๋ฆฌํด์ ๊ทํํ๋ ๊ฒ์ด ๊ฐ๋ฅํ๋ค.
4๏ธโฃ RNN ๊ตฌ์กฐ
๐น ๊ตฌ์กฐ
• x(t-1) ์์ h(t-1) ์ ์ป๊ณ , ๋ค์ ๋จ๊ณ์์ h(t-1) ๊ณผ x(t) ๋ฅผ ์ฌ์ฉํด ๊ณผ๊ฑฐ ์ ๋ณด์ ํ์ฌ ์ ๋ณด๋ฅผ ๋ชจ๋ ๋ฐ์ํ๋ค.
• ๊ฐ์ค์น
โช Wxh : ์ ๋ ฅ์ธต์์ ์๋์ธต์ผ๋ก ์ ๋ฌ๋๋ ๊ฐ์ค์น
โช Whh : t ์์ ์ ์๋์ธต์์ t+1 ์์ ์ ์๋์ธต์ผ๋ก ์ ๋ฌ๋๋ ๊ฐ์ค์น
โช Why : ์๋์ธต์์ ์ถ๋ ฅ์ธต์ผ๋ก ์ ๋ฌ๋๋ ๊ฐ์ค์น
โธ ๋ชจ๋ ์์ ์ ๊ฐ์ค์น ๊ฐ์ ๋์ผํ๊ฒ ์ ์ฉ๋๋ค.
๐น t ์์ ์ RNN ๊ณ์ฐ
โ ์๋์ธต ๊ณ์ฐ
โช ํ์ฌ ์ ๋ ฅ๊ฐ๊ณผ ์ด์ ์์ ์ ์๋์ธต์ ๊ฐ์ค์น ๊ณ์ฐํ ํ, ํ์ดํผ๋ณผ๋ฆญ ํ์ ํธ ํ์ฑํ ํจ์๋ฅผ ์ฌ์ฉํด ํ์ฌ ์์ ์ ์๋์ธต์ ๊ณ์ฐํ๋ค.
โก ์ถ๋ ฅ์ธต
โช ์ถ๋ ฅ์ธต ๊ฐ์ค์น์ ํ์ฌ ์๋์ธต์ ๊ณฑํ์ฌ ์ํํธ๋งฅ์ค ํจ์๋ฅผ ์ ์ฉํ๋ค.
โข ์๋ฐฉํฅ ํ์ต ๋ฐ ์ค์ฐจ E
โช ์ฌ์ธต ์ ๊ฒฝ๋ง์์ ์ผ๋ฐ์ ์ธ feedforward ์ ๋ฐฉํฅ ํ์ต๊ณผ ๋ฌ๋ฆฌ ๊ฐ ๋จ๊ณ t ๋ง๋ค ์ค์ฐจ๋ฅผ ์ธก์ ํ๋ค.
โฃ ์ญ์ ํ
โช RNN ์์๋ BPTT (backpropagation through time) ๋ฅผ ์ด์ฉํ์ฌ ๋ชจ๋ ๋จ๊ณ๋ง๋ค ์ฒ์๋ถํฐ ๋๊น์ง ์ญ์ ํํ๋ค.
โช ๊ฐ ๋จ๊ณ๋ง๋ค ์ค์ฐจ๋ฅผ ์ธก์ ํ๊ณ ์ด์ ๋จ๊ณ๋ก ์ ๋ฌ๋๋ ๊ฒ์ ์๋ฏธํ๋๋ฐ, โข ๊ณผ์ ์์ ๊ตฌํ ์ค์ฐจ๋ฅผ ์ด์ฉํด ๊ฐ์ค์น์ bias ๋ฅผ ์ ๋ฐ์ดํธ ํ๋ค.
โช ๊ธฐ์ธ๊ธฐ ์๋ฉธ๋ฌธ์ : ์ค์ฐจ๊ฐ ๋ฉ๋ฆฌ ์ ํ๋ ๋ ๊ณ์ฐ๋์ด ๋ง์์ง๊ณ ์ ํ๋๋ ์์ด ์ ์ฐจ ์ ์ด์ง๋ ๋ฌธ์ ์
๐น RNN ์ ๊ตฌํ : IMDB ์ํ๋ฆฌ๋ทฐ ๊ธ๋ถ์ ์์
โ ๋ฐ์ดํฐ ์ค๋น
(1) ๋ผ์ด๋ธ๋ฌ๋ฆฌ ๊ฐ์ ธ์ค๊ธฐ
pip install torchtext==0.10.1 # ๋ฐํ์ ๋ค์์์
import torch
import torchtext
import numpy as np
import torch.nn as nn
import torch.nn.functional as F
import time
โธ torchtext : NLP ๋ถ์ผ์์ ์ฌ์ฉํ๋ ๋ฐ์ดํฐ๋ก๋๋ก, ํ์ผ ๊ฐ์ ธ์ค๊ธฐ, ํ ํฐํ, ๋จ์ด ์งํฉ ์์ฑ, ์ธ์ฝ๋ฉ, ๋จ์ด๋ฒกํฐ ์์ฑ ๋ฑ์ ์์ ์ ์ง์ํ๋ค.
โธ ์ฉ์ด์ ๋ฆฌ
โ ํ ํฐํ : ํ ์คํธ๋ฅผ ๋ฌธ์ฅ์ด๋ ๋จ์ด๋ก ๋ถ๋ฆฌํ๋ ๊ฒ
โ ๋จ์ด์งํฉ vocabulary : ์ค๋ณต์ ์ ๊ฑฐํ ํ ์คํธ์ ์ด ๋จ์ด์ ์งํฉ
โ ์ธ์ฝ๋ฉ : ์ฌ๋์ ์ธ์ด์ธ ๋ฌธ์๋ฅผ ์ปดํจํฐ์ ์ธ์ด์ธ ์ซ์๋ก ๋ฐ๊พธ๋ ์์
โ ๋จ์ด๋ฒกํฐ : ๋จ์ด์ ์๋ฏธ๋ฅผ ๋ํ๋ด๋ ์ซ์ ๋ฒกํฐ
(2) ๋ฐ์ดํฐ ์ ์ฒ๋ฆฌ
from torchtext.legacy.data import Field
start = time.time()
TEXT = torchtext.legacy.data.Field(lower = True, fix_length = 200, batch_first=False)
LABEL = torchtext.legacy.data.Field(sequential = False)
โธ torchtext.legacy.data.Field
โ lower = True : ๋๋ฌธ์๋ฅผ ๋ชจ๋ ์๋ฌธ์๋ก ๋ณ๊ฒฝ
โ fix_length = 200 : ๊ณ ์ ๋ ๊ธธ์ด์ ๋ฐ์ดํฐ๋ฅผ ์ป์ ์ ์๋ค. ์ฌ๊ธฐ์ฒ๋ผ 200์ผ๋ก ๊ณ ์ ํ๋ค๋ฉด ๋ฐ์ดํฐ์ ๊ธธ์ด๋ฅผ 200์ผ๋ก ๋ง์ถ๋ ๊ฒ์ด๊ณ , ๋ง์ฝ 200๋ณด๋ค ์งง์ ๊ธธ์ด๋ผ๋ฉด ํจ๋ฉ ์์ ์ ํตํด ์ด์ ๋ง์ถ์ด ์ค๋ค.
โ batch_first = True : ์ ๊ฒฝ๋ง์ ์ ๋ ฅ๋๋ ํ ์์ ์ฒซ๋ฒ์งธ ์ฐจ์์ ๊ฐ์ด ๋ฐฐ์นํฌ๊ธฐ๊ฐ ๋ ์ ์๋๋ก ํ๋ค. ์๋ ๋ชจ๋ธ์ ๋คํธ์ํฌ๋ก ์ ๋ ฅ๋๋ ๋ฐ์ดํฐ๋ (seq_len, batch_size, hidden_size) ํํ์ธ๋ฐ, ์ด ์ต์ ์ True ๋ก ์ค์ ํ๊ฒ ๋๋ฉด (batch_size, seq_len, hidden_size) ํํ๋ก ๋ณ๊ฒฝ๋๋ค.
โ ํ์ดํ ์น๋ ๊ฐ ๊ณ์ธต๋ณ ๋ฐ์ดํฐ ํํ๋ฅผ ๋ง์ถ๋ ๊ฒ์์ ์์ํ์ฌ ๋๋ ์ ๋๋ก ๋งค์ฐ ์ค์ํ๊ธฐ ๋๋ฌธ์ ์ ๋ ฅ์ธต, ์๋์ธต ๋ฐ์ดํฐ๋ค์ ๋ํด ๊ฐ ์ซ์๊ฐ ์๋ฏธํ๋ ๊ฒ์ ์ดํดํด์ผ ํ๋ค.
โ sequential = False : ๋ฐ์ดํฐ์ ์์๊ฐ ์๋์ง ๋ํ๋ด๋ ๊ฒ์ผ๋ก ๊ธฐ๋ณธ๊ฐ์ True ์ด๋ค. ์์ ์ ๋ ์ด๋ธ์ ๊ธ๋ถ์ ๊ฐ๋ง ๊ฐ์ง๋ฏ๋ก False ๋ก ์ค์ ํ๋ค.
(3) ๋ฐ์ดํฐ์ ์ค๋น
from torchtext.legacy import datasets
train_data, test_data = datasets.IMDB.splits(TEXT, LABEL)
โธ splits : ์ ์ฒด ๋ฐ์ดํฐ์ ์ TEXT ์ LABEL ๋ก ๋ถํ ํ์ฌ, TEXT ๋ ํ๋ จ์ฉ๋๋ก LABEL ์ ํ ์คํธ ์ฉ๋๋ก ์ฌ์ฉํ๋ค.
โธ ํ๋ จ ๋ฐ์ดํฐ๋ text ์ label ์ ๊ฐ์ง๋ ์ฌ์ ํ์์ผ๋ก ๊ตฌ์ฑ๋์ด ์๋ค. { 'text' : ['A','B', ..] , 'label' : 'pos' }
โก ๋ฐ์ดํฐ ์ ์ฒ๋ฆฌ
(1) ํ ์คํธ ์ ์ฒ๋ฆฌ
# ๋ฐ์ดํฐ ์ ์ฒ๋ฆฌ
import string
for example in train_data.examples : # ๋ฐ์ดํฐ์
๋ด์ฉ ํ์ธํ๊ธฐ : examples
text = [x.lower() for x in vars(example)['text']] # ์๋ฌธ์๋ก ๋ณ๊ฒฝ
text = [x.replace("<br","") for x in text] # "<br" ์ "" ๊ณต๋ฐฑ์ผ๋ก ๋ณ๊ฒฝ
text = [''.join(c for c in s if c not in string.punctuation) for s in text] # ๊ตฌ๋์ ์ ๊ฑฐ
text = [s for s in text if s] # ๊ณต๋ฐฑ์ ๊ฑฐ
vars(example)['text'] = text
โธ ๋ถํ์ํ ๋ฌธ์ ์ ๊ฑฐ, ๊ณต๋ฐฑ์ฒ๋ฆฌ ๋ฑ์ด ํฌํจ๋๋ค.
(2) ํ๋ จ๊ณผ ๊ฒ์ฆ ๋ฐ์ดํฐ์ ๋ถ๋ฆฌ
import random
train_data , valid_data = train_data.split(random_state = random.seed(0), split_ratio = 0.8)
โธ random_state : ๋ฐ์ดํฐ ๋ถํ ์ ๋ฐ์ดํฐ๊ฐ ์์๋ก ์์ธ ์ํ์์ ๋ถํ ๋๋ค. seed ๊ฐ์ ์ฌ์ฉํ๋ฉด ๋์ผํ ์ฝ๋๋ฅผ ์ฌ๋ฌ๋ฒ ์ํํด๋ ๋์ผํ ๊ฐ์ ๋ฐ์ดํฐ๋ฅผ ๋ฐํํ๋ค.
(3) ๋จ์ด์งํฉ ๋ง๋ค๊ธฐ : build.vocab()
#๋จ์ด์งํฉ ๋ง๋ค๊ธฐ
TEXT.build_vocab(train_data, max_size = 10000, min_freq = 10, vectors=None)
LABEL.build_vocab(train_data)
โธ ๋จ์ด์งํฉ : IMDB ๋ฐ์ดํฐ์ ์ ํฌํจ๋ ๋จ์ด๋ค์ ์ด์ฉํด ํ๋์ ๋์ ๋๋ฆฌ์ ๊ฐ์ ์งํฉ์ ๋ง๋๋ ๊ฒ์ผ๋ก ๋จ์ด๋ค์ ์ค๋ณต์ ์ ๊ฑฐ๋ ์ํ์์ ์งํ๋๋ค.
โธ max_size : ๋จ์ด ์งํฉ์ ํฌ๊ธฐ๋ก ๋จ์ด ์งํฉ์ ํฌํจ๋๋ ์ดํ ์
โธ min_freq : ํน์ ๋จ์ด์ ์ต์ ๋ฑ์ฅ ํ์๋ก, ํ๋ จ ๋ฐ์ดํฐ์ ์์ ํน์ ๋จ์ด๊ฐ ์ต์ 10๋ฒ ๋ฑ์ฅํ ๊ฒ๋ง ๋จ์ด์งํฉ์ ํฌํจํ๊ฒ ๋ค๋ ์๋ฏธ์ด๋ค.
โธ vectors : ์๋ฒ ๋ฉ ๋ฒกํฐ๋ฅผ ์ง์ ํ ์ ์๋ค. Word2Vec, Glove ๋ฑ์ด ์์ผ๋ฉฐ ํ์ดํ ์น์์๋ nn.embedding() ์ ํตํด ๋๋คํ ์ซ์๊ฐ์ผ๋ก ๋ณํํ์ฌ ๊ฐ์ค์น๋ฅผ ํ์ตํ๋ ๋ฐฉ๋ฒ์ ์ ๊ณตํ๋ค.
print('TEXT ๋จ์ด์ฌ์ ์ ํ ํฐ ๊ฐ์(์ค๋ณต์์):', len(TEXT.vocab))
# 10002
print('LABEL ๋จ์ด์ฌ์ ์ ํ ํฐ ๊ฐ์(์ค๋ณต์์):', len(LABEL.vocab))
# 3 : ์๋ ๊ธ์ ๊ณผ ๋ถ์ 2๊ฐ๊ฐ ์ถ๋ ฅ๋์ด์ผ ํ๋๋ฐ ํ๋ฒ ํ์ธํด๋ณผ ํ์๊ฐ ์์
(4) ๋จ์ด ์งํฉ ํ์ธ
# ํ
์คํธ ๋ฐ์ดํฐ์
์ ๋จ์ด์งํฉ ํ์ธ
print(LABEL.vocab.stoi)
defaultdict(<bound method Vocab._default_unk_index of <torchtext.legacy.vocab.Vocab object at 0x7f134796de10>>, {'<unk>': 0, 'pos': 1, 'neg': 2})
→ <unk> : ์ฌ์ ์ ์๋ ๋จ์ด๋ฅผ ์๋ฏธ
(5) ๋ฐ์ดํฐ์ ๋ฉ๋ชจ๋ฆฌ๋ก ๊ฐ์ ธ์ค๊ธฐ
# ๋ฐ์ดํฐ์
๋ฉ๋ชจ๋ฆฌ๋ก ๊ฐ์ ธ์ค๊ธฐ
BATCH_SIZE = 64
device = torch.device('cuda:0' if torch.cude.is_available() else 'cpu')
embedding_dim = 100 # ๊ฐ ๋จ์ด๋ฅผ 100์ฐจ์์ผ๋ก ์กฐ์ (์๋ฒ ๋ฉ ๊ณ์ธต์ ํต๊ณผํ ํ ๊ฐ ๋ฒกํฐ์ ํฌ๊ธฐ)
hidden_size = 300
train_iterator, valid_iterator, test_iterator = torchtext.legacy.data.BucketIterator.splits(
(train_data, valid_data, test_data),
batch_size = BATCH_SIZE,
device = device
)
โธ hidden size : ์๋์ธต์ ์ ๋(๋ด๋ฐ) ๊ฐ์๋ฅผ ์ ํ๋ค. ์ผ๋ฐ์ ์ผ๋ก ๋น์ ํ ๋ฌธ์ ๋ฅผ ์ข ๋ ํ์ตํ ์ ์๋๋ก, ์๋์ธต์ ์ ๋ ๊ฐ์๋ฅผ ๋๋ฆฌ๊ธฐ๋ณด๋จ, ๊ณ์ธต ์์ฒด์ ๊ฐ์๋ฅผ ๋๋ฆฌ๋ ๊ฒ์ด ์ฑ๋ฅ์ ๋ ์ข๋ค. ์ต์ ์ ์๋์ธต ๊ฐ์์ ์ ๋ ๊ฐ์๋ฅผ ์ฐพ๋ ๊ฒ์ ๋งค์ฐ ์ด๋ ค์ด ์ผ์ด๊ธฐ ๋๋ฌธ์ ๊ณผ์ ํฉ์ด ๋ฐ์ํ์ง ์๋๋ก, ์ค์ ํ์ํ ๊ฐ์๋ณด๋ค ๋ ๋ง์ ์ธต๊ณผ ์ ๋์ ๊ตฌ์ฑํด ๊ฐ์๋ฅผ ์กฐ์ ํด ๋๊ฐ๋ ๋ฐฉ์์ ์ฌ์ฉํ๋ค.
โธBucketIterator : dataloader ์ ๋น์ทํ๊ฒ ๋ฐฐ์น ํฌ๊ธฐ ๋จ์๋ก ๊ฐ์ ์ฐจ๋ก๋ก ๊บผ๋ด์ด ๋ฉ๋ชจ๋ฆฌ๋ก ๊ฐ์ ธ์ค๊ณ ์ถ์ ๋ ์ฌ์ฉํ๋ค. ๋น์ทํ ๊ธธ์ด์ ๋ฐ์ดํฐ๋ฅผ ํ ๋ฐฐ์น์ ํ ๋นํ์ฌ ํจ๋ฉ์ ์ต์ํ ์์ผ์ค๋ค.
โข ์๋ ์๋ฒ ๋ฉ ๋ฐ RNN ์ ์ ์
โ ์์ ๋จ์ด์งํฉ ์์ฑ ๊ณผ์ ์์ vectors=none ์ผ๋ก ์ค์ ํ์์ผ๋ฏ๋ก ์๋ฒ ๋ฉ์ด ์งํ๋์ง ์์๊ธฐ ๋๋ฌธ์, nn.Embedding() ์ ์ด์ฉํด ์๋ฒ ๋ฉ ์ฒ๋ฆฌ๋ฅผ ์์ผ์ค๋ค.
class RNNCell_Encoder(nn.Module) :
def __init__(self, input_dim, hidden_size) :
super(RNNCell_Encoder, self).__init__()
self.rnn = nn.RNNCell(input_dim, hidden_size) # RNN ์
๊ตฌํ
def forward(self, inputs) : # inputs ๋ ์
๋ ฅ ์ํ์ค๋ก (์ํ์ค ๊ธธ์ด, ๋ฐฐ์น, ์๋ฒ ๋ฉ) ํํ๋ฅผ ๊ฐ์ง
bz = inputs.shape[1] # ๋ฐฐ์น๋ฅผ ๊ฐ์ ธ์จ๋ค.
ht = torch.zeros((bz, hidden_size)).to(device) # ๋ฐฐ์น์ ์๋์ธต ๋ด๋ฐ์ ํฌ๊ธฐ๋ฅผ 0์ผ๋ก ์ด๊ธฐํ
for word in inputs :
ht = self.rnn(word, ht) # ์ฌ๊ท์ ์ผ๋ก ๋ฐ์ํ๋ ์ํ ๊ฐ ์ฒ๋ฆฌ
return ht
โธ nn.RNNCell
- input_dim : ํ๋ จ ๋ฐ์ดํฐ์ ์ feature ๊ฐ์๋ก (batch, input_size) ํํ๋ฅผ ๊ฐ๋๋ค. (๋ฐฐ์น, ์ ๋ ฅ ๋ฐ์ดํฐ ์นผ๋ผ๊ฐ์)
- hidden_size : ์๋์ธต์ ๋ด๋ฐ ๊ฐ์๋ก (batch, hidden_size) ํํ๋ฅผ ๊ฐ๋๋ค.
โธ ht = self.rnn(word, ht)
- ht : ํ์ฌ์ํ
- word : ํ์ฌ์ ์ ๋ ฅ๋ฒกํฐ๋ก Xi ๋ฅผ ์๋ฏธ, (batch, input_size) ์ ํํ๋ฅผ ๊ฐ๋๋ค.
- ht : ์ด์ ์ํ๋ฅผ ์๋ฏธํ๋ฉฐ (batch, hidden_size) ํํ๋ฅผ ๊ฐ๋๋ค.
class Net(nn.Module) :
def __init__(self) :
super(Net, self).__init__()
self.em = nn.Embedding(len(TEXT.vocab.stoi), embedding_dim) # ์๋ฒ ๋ฉ ์ฒ๋ฆฌ
self.rnn = RNNCell_Encoder(embedding_dim, hidden_size)
self.fc1 = nn.Linear(hidden_size, 256)
self.fc2 = nn.Linear(256,3)
def forward(self,x) :
x = self.em(x)
x = self.rnn(x)
x = F.relu(self.fc1(x))
x = self.fc2(x)
return x
โธ nn.Embedding : ์๋ฒ ๋ฉ ์ฒ๋ฆฌ๋ฅผ ์ํ ๊ตฌ๋ฌธ์ผ๋ก ์๋ฒ ๋ฉ์ ํ ๋จ์ด ์ (๋จ์ด์งํฉํฌ๊ธฐ) ์ ์๋ฒ ๋ฉํ ๋ฒกํฐ์ ์ฐจ์์ ์ง์ ํด์ค๋ค.
โฃ ์ตํฐ๋ง์ด์ ์ ์์คํจ์ ์ ์
model = Net()
model.to(device)
loss_fn = nn.CrossEntropyLoss()
optimizer = torch.optim.Adam(model.parameters(), lr=0.0001)
โธ nn.CrossEntropyLoss : ๋ค์ค๋ถ๋ฅ์ ์ฌ์ฉ๋๋ ์์คํจ์
โค ๋ชจ๋ธ ํ์ต
โธ ๋ชจ๋ธ ํ์ต์ ์ํ ํจ์๋ฅผ ์ ์
# ๋ชจ๋ธ ํ์ต
def training(epoch, model, trainloader, validloader) :
correct = 0
total = 0
running_loss = 0
model.train()
for b in trainloader :
x,y = b.text, b.label # text ์ label ์ ๊บผ๋ด์จ๋ค.
x,y = x.to(device) , y.to(device) # ๋ฐ์ดํฐ๊ฐ CPU ๋ฅผ ์ฌ์ฉํ ์ ์๋๋ก ์ฅ์น ์ง์
y_pred = model(x)
loss = loss_fn(y_pred, y) # CrossEntropyLoss ์์คํจ์ ์ด์ฉํด ์ค์ฐจ ๊ณ์ฐ
optimizer.zero_grad() # ๋ณํ๋ (gradients) ์ด๊ธฐํ
loss.backward() # ์ญ์ ํ
optimizer.step() # ์
๋ฐ์ดํธ
with torch.no_grad() : # autograd๋ฅผ ๋์ผ๋ก์จ ๋ฉ๋ชจ๋ฆฌ ์ฌ์ฉ๋์ ์ค์ด๊ณ ์ฐ์ฐ ์๋๋ฅผ ๋์
y_pred = torch.argmax(y_pred, dim =1)
correct += (y_pred == y).sum().item()
total += y.size(0)
running_loss += loss.item()
epoch_loss = running_loss / len(trainloader.dataset)
# ๋์ ๋ ์ค์ฐจ๋ฅผ ์ ์ฒด๋ฐ์ดํฐ์
์ผ๋ก ๋๋์ด ์ํฌํฌ ๋จ๊ณ๋ง๋ค ์ค์ฐจ๋ฅผ ๊ตฌํ๋ค.
epoch_acc = correct/total
valid_correct = 0
valid_total = 0
valid_running_loss = 0
model.eval() # evaluation ๊ณผ์ ์์ ์ฌ์ฉํ์ง ์์์ผ ํ๋ layer๋ค์ ์์์ off ์ํค๋๋ก ํ๋ ํจ์
with torch.no_grad() : # evaluation ํน์ validation ์์๋ no_grad ๋ฅผ ์ด๋ค.
for b in validloader :
x,y = b.text, b.label
x,y = x.to(device) , y.to(device)
y_pred = model(x)
loss = loss_fn(y_pred,y)
y_pred = torch.argmax(y_pred, dim =1)
valid_correct += (y_pred == y).sum().item()
valid_total += y.size(0)
valid_running_loss += loss.item()
epoch_valid_loss = valid_running_loss / len(validloader.dataset)
epoch_valid_acc = valid_correct / valid_total
print('epoch :', epoch,
'loss :', round(epoch_loss,3),
'accuracy : ', round(epoch_acc,3),
'valid_loss :', round(epoch_valid_loss,3),
'valid_accuracy :', round(epoch_valid_acc,3)
)
return epoch_loss, epoch_acc, epoch_valid_loss, epoch_valid_acc
โธ ๋ชจ๋ธ ํ์ต
# ๋ชจ๋ธ ํ์ต ์งํ
epochs = 5
train_loss = []
train_acc = []
valid_loss = []
valid_acc = []
for epoch in range(epochs) :
epoch_loss, epoch_acc, epoch_valid_loss, epoch_valid_acc = training(epoch,
model,
train_iterator,
valid_iterator)
train_loss.append(epoch_loss) # ํ๋ จ ๋ฐ์ดํฐ์
์ ๋ชจ๋ธ์ ์ ์ฉํ์ ๋์ ์ค์ฐจ
train_acc.append(epoch_acc) # ํ๋ จ ๋ฐ์ดํฐ์
์ ๋ชจ๋ธ์ ์ ์ฉํ์ ๋ ์ ํ๋
valid_loss.append(epoch_valid_loss) # ๊ฒ์ฆ ๋ฐ์ดํฐ์
์ ๋ชจ๋ธ์ ์ ์ฉํ์ ๋ ์ค์ฐจ
valid_acc.append(epoch_valid_acc) # ๊ฒ์ฆ ๋ฐ์ดํฐ์
์ ๋ชจ๋ธ์ ์ ์ฉํ์ ๋ ์ ํ๋
end = time.time()
#print(end-start)
โช ์ํฌํฌ๊ฐ 5๋ผ ์ ํ๋๋ ๋ฎ์ง๋ง ํ์ต๊ณผ ๊ฒ์ฆ ๋ฐ์ดํฐ์ ์ ๋ํ ์ค์ฐจ๊ฐ ์ ์ฌํ๋ฏ๋ก ๊ณผ์ ํฉ์ ๋ฐ์ํ์ง ์์์ ํ์ธํด๋ณผ ์ ์๋ค.
โฅ ๋ชจ๋ธ ์์ธก
โธ ํ ์คํธ์ ์ ๋ํ ๋ชจ๋ธ ์์ธกํจ์ ์ ์
def evaluate(epoch, model, testloader) :
test_correct = 0
test_total = 0
test_running_loss = 0
model.eval()
with torch.no_grad() :
for b in testloader :
x,y = b.text, b.label
x,y = x.to(device) , y.to(device)
y_pred = model(x)
loss = loss_fn(y_pred, y)
y_pred = torch.argmax(y_pred, dim=1)
test_correct += (y_pred == y).sum().item()
test_total += y.size(0)
test_running_loss += loss.item()
epoch_test_loss = test_running_loss/len(testloader.dataset)
epoch_test_acc = test_correct/test_total
print('epoch : ', epoch,
'test_loss : ', round(epoch_test_loss,3),
'test_accuracy :', round(epoch_test_acc,3))
return epoch_test_loss, epoch_test_acc
โธ ํ ์คํธ์ ์ ๋ํ ๋ชจ๋ธ ์์ธก ๊ฒฐ๊ณผ ํ์ธ
epochs = 5
test_loss = []
test_acc = []
for epoch in range(epochs) :
epoch_test_loss, epoch_test_acc = evaluate(epoch, model, test_iterator)
test_loss.append(epoch_test_loss)
test_acc.append(epoch_test_acc)
end = time.time()
๋ ๋์ ์ ํ๋๋ฅผ ์ํ๋ค๋ฉด ์ํฌํฌ ํ์๋ฅผ ๋๋ฆฌ๋ฉด ๋๋ค.
๐น RNN ๊ณ์ธต ๊ตฌํ
โ RNN ์ ๋คํธ์ํฌ์ ํฌ๊ฒ ๋ค๋ฅด์ง ์๋ค. ๋ฏธ์ธํ ์ฐจ์ด ์์ฃผ๋ก ์ดํด๋ณด๊ธฐ!
โ ๋ฐ์ดํฐ ๋ก๋ ๋ฐ ์ ์ฒ๋ฆฌ (RNN ์ ๊ณผ์ ๊ณผ ๊ฐ์ผ๋ฏ๋ก ์๋ต)
โก ๋ชจ๋ธ ๋คํธ์ํฌ ์ ์
โธ ๋ณ์๊ฐ ์ง์
vocab_size = len(TEXT.vocab) # ์ํ ๋ฆฌ๋ทฐ์ ๋ํ ํ
์คํธ ๊ธธ์ด
n_classes = 2 # ๊ธ์ ๋ถ์
โธ RNN layer ๋คํธ์ํฌ
class BasicRNN(nn.Module) :
def __init__(self, n_layers, hidden_dim, n_vocab, embed_dim, n_classes, dropout_p=0.2) :
super(BasicRNN,self).__init__()
self.n_layers = n_layers # RNN ๊ณ์ธต์ ๋ํ ๊ฐ์
self.embed = nn.Embedding(n_vocab, embed_dim) # ์๋ ์๋ฒ ๋ฉ ์ ์ฉ
self.hidden_dim = hidden_dim
self.dropout = nn.Dropout(dropout_p) # ๋๋กญ์์ ์ ์ฉ
self.rnn = nn.RNN(embed_dim, self.hidden_dim, num_layers = self.n_layers, batch_first = True)
self.out = nn.Linear(self.hidden_dim, n_classes)
def forward(self,x) :
x = self.embed(x) # ๋ฌธ์๋ฅผ ์ซ์/๋ฒกํฐ๋ก ๋ณํ
h_0 = self._init_state(batch_size = x.size(0)) # ์ต์ด ์๋์ํ์ ๊ฐ์ 0์ผ๋ก ์ด๊ธฐํ
x,_ = self.rnn(x, h_0) # RNN ๊ณ์ธต
h_t = x[:,-1,:] # ๋ชจ๋ ๋คํธ์ํฌ๋ฅผ ๊ฑฐ์ณ ๊ฐ์ฅ ๋ง์ง๋ง์ ๋์จ ๋จ์ด์ ์๋ฒ ๋ฉ๊ฐ (๋ง์ง๋ง ์๋์ํ์ ๊ฐ)
self.dropout(h_t)
logit = torch.sigmoid(self.out(h_t))
return logit
def _init_state(self, batch_size =1) :
weight = next(self.parameters()).data # ๋ชจ๋ธ ํ๋ผ๋ฏธํฐ ๊ฐ์ ๊ฐ์ ธ์ weight ์ ์ ์ฅ
return weight.new(self.n_layers, batch_size, self.hidden_dim).zero_()
# ํฌ๊ธฐ๊ฐ (๊ณ์ธต์ ๊ฐ์, ๋ฐฐ์นํฌ๊ธฐ, ์๋์ธต์ ๋ด๋ฐ๊ฐ์) ์ธ ์๋์ํ์ ํ
์๋ฅผ ์์ฑํด 0์ผ๋ก ์ด๊ธฐํํ ํ ๋ฐํ
โช nn.RNN
- embed_dim : ํ๋ จ ๋ฐ์ดํฐ์ ์ ํน์ฑ(์นผ๋ผ) ๊ฐ์
- hidden_dim : ์๋ ๊ณ์ธต์ ๋ด๋ฐ ๊ฐ์
- num_layers : RNN ๊ณ์ธต์ ๊ฐ์
โข ์์คํจ์์ ์ตํฐ๋ง์ด์ ์ค์
model = BasicRNN(n_layers = 1, hidden_dim = 256, n_vocab = vocab_size, embed_dim = 128, n_classes = n_classes, dropout_p = 0.5)
model.to(device)
loss_fn = nn.CrossEntropyLoss()
optimizer = torch.optim.Adam(model.parameters(), lr=0.0001)
โฃ ๋ชจ๋ธ ํ๋ จ ๋ฐ ํ๊ฐ
def train(model, optimizer, train_iter):
model.train()
for b, batch in enumerate(train_iter):
x, y = batch.text.to(device), batch.label.to(device)
y.data.sub_(1)
# ๋ ์ด๋ธ์ด ๊ธ์ (2), ๋ถ์ (1) ๋ก ๋์ด์๊ธฐ ๋๋ฌธ์ ๊ฐ๊ฐ 1๊ณผ 0์ผ๋ก ๊ฐ์ ๋ฐ๊ฟ์ฃผ๊ธฐ ์ํจ
optimizer.zero_grad()
logit = model(x)
loss = F.cross_entropy(logit, y)
loss.backward()
optimizer.step()
if b % 50 == 0:
print("Train Epoch: {} [{}/{} ({:.0f}%)]\tLoss: {:.6f}".format(e,
b * len(x),
len(train_iter.dataset),
100. * b / len(train_iter),
loss.item()))
def evaluate(model, val_iter):
model.eval()
corrects, total, total_loss = 0, 0, 0
for batch in val_iter:
x, y = batch.text.to(device), batch.label.to(device)
y.data.sub_(1)
logit = model(x)
loss = F.cross_entropy(logit, y, reduction = "sum")
total += y.size(0)
total_loss += loss.item()
corrects += (logit.max(1)[1].view(y.size()).data == y.data).sum()
avg_loss = total_loss / len(val_iter.dataset)
avg_accuracy = corrects / total
return avg_loss, avg_accuracy
BATCH_SIZE = 100
LR = 0.001
EPOCHS = 5
for e in range(1, EPOCHS + 1):
train(model, optimizer, train_iterator)
val_loss, val_accuracy = evaluate(model, valid_iterator)
print("[EPOCH: %d], Validation Loss: %5.2f | Validation Accuracy: %5.2f" % (e, val_loss, val_accuracy))
test_loss, test_acc = evaluate(model,test_iterator)
print("Test Loss: %5.2f | Test Accuracy: %5.2f" % (test_loss, test_acc))
์ ํ๋๊ฐ ๊ทธ๋ฅ ๋์ง ์๋ค. ์ํฌํฌ๋ฅผ ์ฆ๊ฐ์์ผ๋ณด๊ฑฐ๋, ๋ค๋ฅธ ๋ชจ๋ธ๋ก ๋ณ๊ฒฝํด๋ณธ๋ค. ์ฌ๋ฌ ์ ํ์ ๋ชจ๋ธ์ ์ ์ฉํ ํ ๊ฐ์ฅ ๊ฒฐ๊ณผ๊ฐ ์ข์ ๋ชจ๋ธ์ ์ ํํ๋ค. ๋ํ ํ์ดํผํ๋ผ๋ฏธํฐ (๋ฐฐ์นํฌ๊ธฐ, ํ์ต๋ฅ ๋ฑ) ๋ฅผ ํ๋ํด๊ฐ๋ ๊ณผ์ ์ด ํ์ํ๋ค.
๋๊ธ