import torch
import pandas as pd
import matplotlib.pyplot as plt
1. imports
2. 예비학습
A. 파이토치의 유연성
-
아래는 엄밀한 의미에서는 계산이 불가능하지만 파이토치에서는 그냥 해줌
= torch.tensor([0,1.5])
x = torch.randn(2,1)
W @W x
tensor([1.4052])
-
아래도 엄밀한 의미에서는 계산 불가능하지만 파이토치에서는 그냥 해줌
= torch.nn.Linear(2,1,bias=False)
linr = W.T
linr.weight.data linr(x)
tensor([1.4052], grad_fn=<SqueezeBackward4>)
B. loss를 계산하는 다른 방식
-
아래를 가정
= torch.randn(100,5)
X = torch.randn(100,1)
y = torch.nn.Linear(5,1)
net = torch.nn.MSELoss() loss_fn
-
loss를 계산하는 방법1
= net(X)
yhat = loss_fn(yhat,y)
loss loss
tensor(1.5209, grad_fn=<MseLossBackward0>)
-
loss를 계산하는 방법2
= 0
loss for i in range(100):
= X[i]
Xi = y[i]
yi = net(Xi)
yi_hat = loss + loss_fn(yi_hat, yi)
loss /100 loss
tensor(1.5209, grad_fn=<DivBackward0>)
3. 겹장(덧장)
(생각1) \({\boldsymbol h}\)에 대한 이해
-
\({\boldsymbol h}\)는 사실 문자열 “abc, abcd”들을 숫자로 바꾼 표현이라 해석할 수 있음. 즉 원핫인코딩과 다른 또 다른 형태의 숫자표현이라 해석할 수 있다.
-
사실 \({\boldsymbol h}\)는 원핫인코딩보다 약간 더 (1) 액기스만 남은 느낌 + (2) 숙성된 느낌을 준다
- (why1) \({\boldsymbol h}\)는 \({\boldsymbol x}\) 보다 \({\boldsymbol y}\)를 예측함에 좀 더 직접적인 역할을 한다. 즉 \({\boldsymbol x}\) 숫자보다 \({\boldsymbol h}\) 숫자가 잘 정리되어 있고 (차원이 낮고) 입력의 특징을 잘 정리한 (추천시스템의 MBTI처럼) 의미있는 숫자이다.
- (why2) \({\boldsymbol x}\)는 학습없이 그냥 얻어지는 숫자표현이지만, \({\boldsymbol h}\)는 학습을 통하여 고치고 고치고 고친 숫자표현이다.
결론: 사실 \({\boldsymbol h}\)는 잘 숙성되어있는 입력정보 \({\boldsymbol x}\) 그 자체로 해석 할 수 있다.
(생각2) 수백년전통을 이어가는 방법
“1리터에 500만원에 낙찰된 적 있습니다.”
“2kg에 1억원 정도 추산됩니다.”
“20여 종 종자장을 블렌딩해 100ml에 5000만원씩 분양 예정입니다.”
모두 씨간장(종자장) 가격에 관한 실제 일화다.
(중략...)
위스키나 와인처럼 블렌딩을 하기도 한다.
새로 담근 간장에 씨간장을 넣거나, 씨간장독에 햇간장을 넣어 맛을 유지하기도 한다.
이를 겹장(또는 덧장)이라 한다.
몇몇 종갓집에선 씨간장 잇기를 몇백 년째 해오고 있다.
매년 새로 간장을 담가야 이어갈 수 있으니 불씨 꺼트리지 않는 것처럼 굉장히 어려운 일이다.
이렇게 하는 이유는 집집마다 내려오는 고유 장맛을 잃지 않기 위함이다.
씨간장이란 그만큼 소중한 주방의 자산이며 정체성이다.
덧장: 새로운간장을 만들때, 옛날간장을 섞어서 만듦
*
기존방식 - \(\text{콩물} \overset{\text{숙성}}{\longrightarrow} \text{간장}\)
*
수백년 전통의 간장맛을 유지하는 방식
- \(\text{콩물}_1 \overset{\text{숙성}}{\longrightarrow} \text{간장}_1\)
- \(\text{콩물}_2, \text{간장}_1 \overset{\text{숙성}}{\longrightarrow} \text{간장}_2\)
- \(\text{콩물}_3, \text{간장}_2 \overset{\text{숙성}}{\longrightarrow} \text{간장}_3\)
*
수백년 전통의 간장맛을 유지하면서 조리를 한다면?
- \(\text{콩물}_1 \overset{\text{숙성}}{\longrightarrow} \text{간장}_1 \overset{\text{조리}}{\longrightarrow} \text{간장계란밥}_1\)
- \(\text{콩물}_2, \text{간장}_1 \overset{\text{숙성}}{\longrightarrow} \text{간장}_2 \overset{\text{조리}}{\longrightarrow} \text{간장계란밥}_2\)
- \(\text{콩물}_3, \text{간장}_2 \overset{\text{숙성}}{\longrightarrow} \text{간장}_3 \overset{\text{조리}}{\longrightarrow} \text{간장계란밥}_3\)
점점 맛있는 간장계란밥이 탄생함
*
알고리즘의 편의상 아래와 같이 생각해도 무방
- \(\text{콩물}_1, \text{간장}_0 \overset{\text{숙성}}{\longrightarrow} \text{간장}_1 \overset{\text{조리}}{\longrightarrow} \text{간장계란밥}_1\), \(\text{간장}_0=\text{맹물}\)
- \(\text{콩물}_2, \text{간장}_1 \overset{\text{숙성}}{\longrightarrow} \text{간장}_2 \overset{\text{조리}}{\longrightarrow} \text{간장계란밥}_2\)
- \(\text{콩물}_3, \text{간장}_2 \overset{\text{숙성}}{\longrightarrow} \text{간장}_3 \overset{\text{조리}}{\longrightarrow} \text{간장계란밥}_3\)
아이디어
*
수백년 전통의 간장맛을 유지하면서 조리하는 과정을 수식으로? (콩물을 \(x\)로, 간장을 \(h\)로!!)
- \(\boldsymbol{x}_1, \boldsymbol{h}_0 \overset{\text{숙성}}{\longrightarrow} \boldsymbol{h}_1 \overset{\text{조리}}{\longrightarrow} \hat{\boldsymbol y}_1\)
- \(\boldsymbol{x}_2, \boldsymbol{h}_1 \overset{\text{숙성}}{\longrightarrow} \boldsymbol{h}_2 \overset{\text{조리}}{\longrightarrow} \hat{\boldsymbol y}_2\)
- \(\boldsymbol{x}_3, \boldsymbol{h}_2 \overset{\text{숙성}}{\longrightarrow} \boldsymbol{h}_3 \overset{\text{조리}}{\longrightarrow} \hat{\boldsymbol y}_3\)
이제 우리가 배울것은 (1) “\(\text{콩물}_{t}\)”와 “\(\text{간장}_{t-1}\)”로 “\(\text{간장}_t\)”를 숙성
하는 방법 (2) “\(\text{간장}_t\)”로 “\(\text{간장계란밥}_t\)를 조리
하는 방법이다
즉 숙성
담당 네트워크와 조리
담당 네트워크를 각각 만들어 학습하면 된다.
4. RNNCell
A. 차원의 정리
-
기본버전
- X.shape = \((L, H_{in})\)
- h.shape = \((L, H_{out})\)
- y.shape = \((L, Q)\)
- Xt.shape = \((H_{in}, )\)
- ht.shape = \((H_{out},)\)
- yt.shape = \((Q,)\)
-
AbAcAd
를 2차원공간에 임베딩하려고 할 경우.
- X.shape = \((L, H_{in})\) = \((L,4)\)
- h.shape = \((L, H_{out})\) = \((L,2)\)
- y.shape = \((L, Q)\) = \((L,4)\)
- Xt.shape = \((H_{in}, )\) = \((4,)\)
- ht.shape = \((H_{out},)\) = \((2,)\)
- yt.shape = \((Q,)\) = \((4,)\)
B. 순환신경망 알고리즘
# 버전1
step 1: 일단 \(\text{간장}_0(={\boldsymbol h}_0)\)을 맹물로 초기화 한다. 즉 아래를 수행한다.
\[{\boldsymbol h}_0 = [0,0]\]
step 2: \(\text{콩물}_1(={\boldsymbol x}_1)\), \(\text{간장}_0(={\boldsymbol h}_0)\) 을 이용하여 \(\text{간장}_1(={\boldsymbol h}_1)\)을 숙성한다. 즉 아래를 수행한다.
\[{\boldsymbol h}_1= \tanh({\boldsymbol x}_1{\bf W}_{ih}+{\boldsymbol h}_0{\bf W}_{hh}+{\boldsymbol b}_{ih}+{\boldsymbol b}_{hh})\]
step 3: \(\text{간장}_1\)을 이용하여 \(\text{간장계란밥}_1\)을 만든다. 그리고 \(\hat{\boldsymbol y}_1\)을 만든다.
\[{\boldsymbol o}_1= {\bf W}_{ho}{\boldsymbol h}_1+{\boldsymbol b}_{ho}\]
\[\hat{\boldsymbol y}_1 = \text{soft}({\boldsymbol o}_1)\]
step 4: \(t=2,3,4,5,\dots,L\) 에 대하여 step2-3을 반복한다.
#
# 버전2
init \(\boldsymbol{h}_0\)
for \(t\) in \(1:L\)
- \({\boldsymbol h}_t= \tanh({\boldsymbol x}_t{\bf W}_{ih}+{\boldsymbol h}_{t-1}{\bf W}_{hh}+{\boldsymbol b}_{ih}+{\boldsymbol b}_{hh})\)
- \({\boldsymbol o}_t= {\bf W}_{ho}{\boldsymbol h}_1+{\boldsymbol b}_{ho}\)
- \(\hat{\boldsymbol y}_t = \text{soft}({\boldsymbol o}_t)\)
#
# 버전3
= [0,0]
ht for t in 1:T
= tanh(linr(xt)+linr(ht))
ht = linr(ht)
ot = soft(ot) yt_hat
- 코드상으로는 \(h_t\)와 \(h_{t-1}\)의 구분이 교모하게 사라진다. (그래서 오히려 좋아)
#
-
따라서 실질적인 전체코드는 아래와 같은 방식으로 구현할 수 있다.
class rNNCell(torch.nn.Module):
def __init__(self):
super().__init__()
= torch.nn.Linear(?,?)
linr1 = torch.nn.Linear(?,?)
linr2 = torch.nn.Tanh()
tanh def forward(self,Xt,ht):
= tanh(lrnr1(Xt)+lrnr2(ht))
ht return ht
init ht= rNNCell()
rnncell
for t in 1:L
= X[t], y[t]
Xt, yt = rnncell(Xt, ht)
ht = linr(ht)
ot = loss + loss_fn(ot, yt) loss
C. 구현1 – rNNCell
-
데이터정리
= list('AbAcAd'*50)
txt 10] txt[:
['A', 'b', 'A', 'c', 'A', 'd', 'A', 'b', 'A', 'c']
= pd.DataFrame({'x':txt[:-1], 'y':txt[1:]})
df_train 5] df_train[:
x | y | |
---|---|---|
0 | A | b |
1 | b | A |
2 | A | c |
3 | c | A |
4 | A | d |
= torch.tensor(df_train.x.map({'A':0,'b':1,'c':2,'d':3}))
x = torch.tensor(df_train.y.map({'A':0,'b':1,'c':2,'d':3}))
y = torch.nn.functional.one_hot(x).float()
X = torch.nn.functional.one_hot(y).float() y
-
순환신경망으로 적합
class rNNCell(torch.nn.Module):
def __init__(self):
super().__init__()
self.i2h = torch.nn.Linear(4,2)
self.h2h = torch.nn.Linear(2,2)
self.tanh = torch.nn.Tanh()
def forward(self,Xt,ht):
= self.tanh(self.i2h(Xt)+self.h2h(ht))
ht return ht
43052)
torch.manual_seed(= rNNCell()
rnncell = torch.nn.Linear(2,4)
cook #
= torch.nn.CrossEntropyLoss()
loss_fn = torch.optim.Adam(list(rnncell.parameters())+ list(cook.parameters()),lr=0.1)
optimizr #---#
= len(X)
L for epoc in range(200):
## 1~2
= torch.zeros(2) # 첫간장은 맹물
ht = 0
loss for t in range(L):
= X[t], y[t]
Xt, yt = rnncell(Xt, ht)
ht = cook(ht)
ot = loss + loss_fn(ot, yt)
loss = loss/L
loss ## 3
loss.backward()## 4
optimizr.step() optimizr.zero_grad()
-
결과 확인 및 시각화
= torch.zeros(L,2)
h = torch.zeros(2)
water 0] = rnncell(X[0],water)
h[for t in range(1,L):
= rnncell(X[t],h[t-1])
h[t] = torch.nn.functional.softmax(cook(h),dim=1)
yhat yhat
tensor([[4.1978e-03, 9.4555e-01, 1.9557e-06, 5.0253e-02],
[9.9994e-01, 5.5569e-05, 8.4751e-10, 1.3143e-06],
[2.1349e-07, 1.1345e-06, 9.7019e-01, 2.9806e-02],
...,
[2.1339e-07, 1.1339e-06, 9.7020e-01, 2.9798e-02],
[9.9901e-01, 9.6573e-04, 6.9303e-09, 2.1945e-05],
[7.2919e-04, 2.5484e-02, 3.3011e-02, 9.4078e-01]],
grad_fn=<SoftmaxBackward0>)
= torch.concat([X,h,yhat],axis=1).data
mat 12],cmap='bwr',vmin=-1,vmax=1)
plt.matshow(mat[:
plt.xticks(range(10),
r"$X_A$", r"$X_b$",r"$X_c$",r"$X_d$",
[r'$h_1$',r'$h_2$',
r'$\hat{y}_A$',r'$\hat{y}_b$',r'$\hat{y}_c$',r'$\hat{y}_d$']
;
)=3.5,color='lime')
plt.axvline(x=5.5,color='lime') plt.axvline(x
-
yhat값 분석
- 미세하게 뒤로갈수록 좀 더 성능이 좋음
round(3)[:12] yhat.data.numpy().
array([[0.004, 0.946, 0. , 0.05 ],
[1. , 0. , 0. , 0. ],
[0. , 0. , 0.97 , 0.03 ],
[0.999, 0.001, 0. , 0. ],
[0.001, 0.025, 0.033, 0.941],
[0.983, 0.016, 0. , 0. ],
[0.004, 0.965, 0. , 0.031],
[1. , 0. , 0. , 0. ],
[0. , 0. , 0.97 , 0.03 ],
[0.999, 0.001, 0. , 0. ],
[0.001, 0.025, 0.033, 0.941],
[0.983, 0.016, 0. , 0. ]], dtype=float32)
-
h1,h2 분석 (= 임베딩스페이스 분석)
= h.T.data
h1,h2 6],h2[::6],'X',label="A")
plt.plot(h1[::1::6],h2[1::6],'X',label="b")
plt.plot(h1[2::6],h2[2::6],'X',label="A")
plt.plot(h1[3::6],h2[3::6],'X',label="c")
plt.plot(h1[4::6],h2[4::6],'X',label="A")
plt.plot(h1[5::6],h2[5::6],'X',label="d")
plt.plot(h1[ plt.legend()
D. 구현2 - RNNCell
-
데이터 정리
= torch.tensor(df_train.x.map({'A':0,'b':1,'c':2,'d':3}))
x = torch.tensor(df_train.y.map({'A':0,'b':1,'c':2,'d':3}))
y = torch.nn.functional.one_hot(x).float()
X = torch.nn.functional.one_hot(y).float() y
-
Net 설계 및 가중치 설정 (구현 1과 동일하도록 가중치 초기화)
43052)
torch.manual_seed(= rNNCell()
_rnncell = torch.nn.Linear(2,4) cook
= torch.nn.RNNCell(4,2)
rnncell = _rnncell.i2h.weight.data
rnncell.weight_ih.data = _rnncell.h2h.weight.data
rnncell.weight_hh.data = _rnncell.i2h.bias.data
rnncell.bias_ih.data = _rnncell.h2h.bias.data rnncell.bias_hh.data
-
손실함수 및 옵티마이저 설정
= torch.nn.CrossEntropyLoss()
loss_fn = torch.optim.Adam(list(rnncell.parameters())+list(cook.parameters()),lr=0.1) optimizr
-
학습
= len(X)
L for epoc in range(200):
## 1~2
= torch.zeros(2)
ht = 0
loss for t in range(L):
= X[t], y[t]
Xt, yt = rnncell(Xt, ht)
ht = cook(ht)
ot = loss + loss_fn(ot, yt)
loss = loss/L
loss ## 3
loss.backward()## 4
optimizr.step() optimizr.zero_grad()
-
결과확인
- 구현 1과 같은 결과
= torch.zeros(L,2)
h = torch.zeros(2)
water 0] = rnncell(X[0],water)
h[for t in range(1,L):
= rnncell(X[t],h[t-1])
h[t] = torch.nn.functional.softmax(cook(h),dim=1)
yhat yhat
tensor([[4.1978e-03, 9.4555e-01, 1.9557e-06, 5.0253e-02],
[9.9994e-01, 5.5569e-05, 8.4751e-10, 1.3143e-06],
[2.1349e-07, 1.1345e-06, 9.7019e-01, 2.9806e-02],
...,
[2.1339e-07, 1.1339e-06, 9.7020e-01, 2.9798e-02],
[9.9901e-01, 9.6573e-04, 6.9303e-09, 2.1945e-05],
[7.2919e-04, 2.5484e-02, 3.3011e-02, 9.4078e-01]],
grad_fn=<SoftmaxBackward0>)