강의영상

- (1/3) 기울기소멸

- (2/3) 추천시스템

- (3/3) 숙제설명

기울기소멸

고요속의 외침

- 중간에 한명이라도 잘못 말한다면..

정의

- In machine learning, the vanishing gradient problem is encountered when training artificial neural networks with gradient-based learning methods and backpropagation.

이해

- 당연한것 아닌가?

  • 그레디언트 기반의 학습 (그레디언트 기반의 옵티마이저): 손실함수의 기울기를 통하여 업데이트 하는 방식
  • 역전파: 손실함수의 기울기를 구하는 테크닉 (체인룰 + $\alpha$). 구체적으로는 (1) 손실함수를 여러단계로 쪼개고 (2) 각 단계의 미분값을 각각 구하고 (3) 그것들을 모두 곱하여 기울기를 계산한다.
  • 0 근처의 숫자를 계속 곱하면 터지거나 0으로 간다. (사실 안정적인 기울기가 나올 것이라고 생각하는것 자체가 사실 이상함)
import numpy as np 
grads = np.random.uniform(low=-2,high=2,size=100) 
grads
array([-0.04807346, -1.29968172,  0.60383275, -1.11097504, -1.44903838,
       -1.81770819, -0.82995838, -0.30307517, -1.62486386,  1.14841271,
        0.05317044, -1.63595745, -1.27254039, -0.2793212 ,  1.4193291 ,
       -1.87957006, -1.50413435, -1.6143583 , -1.97979251, -0.1319077 ,
       -0.36277507,  0.24188449,  0.8205258 , -1.15353317, -0.94630341,
        0.60335535, -1.4326661 ,  1.27171997,  1.51390194, -0.3285052 ,
       -1.56389259, -1.55964141,  0.29636461, -0.74477874, -1.6119124 ,
        0.01097634, -0.25255016,  1.80684873,  0.90766818, -0.25062987,
       -0.74179267, -0.50494702,  1.89992546, -0.2911778 ,  0.3116119 ,
        1.63679034,  0.86030588, -1.10851323, -1.13171181, -1.58949804,
        0.82233945, -1.81269013, -0.31257892,  1.00176613, -0.49897359,
       -1.05885742,  0.49664799, -1.02531506, -0.81051658, -1.34188376,
       -1.98882591,  0.32634551,  1.39557143, -1.13150272, -1.5616825 ,
       -0.0478864 , -1.34446087, -0.56475749, -1.90177931, -0.38516623,
        0.75889489, -1.86710065, -1.85428312,  1.82248239,  1.04363342,
        1.08128997, -0.36466711, -1.99204485,  0.81840524, -1.11993576,
       -0.53666968, -0.09132125,  1.57303259,  1.82113986,  0.31948837,
        0.34639304, -1.68151764,  1.0845407 , -0.01560537,  0.73393873,
        0.85033511,  1.98350062, -0.45107395,  1.45704639,  1.48581033,
       -1.57196761,  0.92182647,  0.78667051, -1.01047196, -0.13306075])
grads.prod()
-3.5832605040779705e-14
  • 기울기가 소멸함
grads = np.random.uniform(low=-5,high=5,size=100) 
grads.prod()
2.2118230498890502e+26
  • 기울기가 폭발함.
grads = np.random.uniform(low=-1,high=3.5,size=100) 
grads.prod()
5.058085058858178e-07

- 도깨비: 기울기가 소멸하기도 하고 터지기도 한다.

해결책 (기울기 소멸에 대한 해결책)

Note: 잘 정리되어있는것이 없어서 제 머리속에서 정리했습니다. 부정확할수도 있어요

- 개념

  • 데이터 $\to$ (아키텍처,손실함수,역전파,업데이트)

- Multi-level hierarchy

  • 여러층을 쪼개서 학습하자 $\to$ 어떻게? 사전학습, 층벼학습
  • 기울기소실문제를 해결하여 딥러닝을 유행시킨 태초의(?) 방법임.
  • 결국 입력자료를 바꾼뒤에 학습하는 형태

- Faster hardware

  • GPU를 중심으로 한 테크닉
  • 근본적인 문제해결책은 아니라는 힌튼의 비판
  • CPU를 쓸때보다 GPU를 쓰면 약간 더 깊은 모형을 학습할 수 있다 정도?

- Residual networks

  • 훌륭한 접근법중 하나임
  • 아키텍처를 변경하는 방법이지만, 사실상 손실함수를 부드럽게 만드는 기법으로 이해해도 된다.
  • 솟컷이라는 아키텍처를 추가하여 이리저리 실험해보니까 손실함수가 부드러워졌다. <--- 이런게 아니고
  • 손실함수를 부드럽게 하기 위해서는 층별의차이(residual)를 학습하는게 유리할 것 같다. 그런데 이것을 위한 효과를 주기 위해서는 단지 아키텍처에 숏만만 추가하면 되겠다. <--- 이런 모티브였을 것이다.

- Other activation functions

  • 렐루의 개발

- 배치정규화

  • 어쩌다보니 되는것.
  • 배치정규화는 원래 공변량 쉬프트를 잡기 위한 방법임. 그런데 기울기 소멸에도 효과가 있음. 현재는 기울기소멸문제에 대한 해결책으로 빠짐없이 언급되고 있음. 2015년의 원래 논문에는 기울기소멸에 대한 언급은 없었음. (https://arxiv.org/pdf/1502.03167.pdf)
  • 심지어 배치정규화는 오버피팅을 잡는효과도 있음 (이것은 논문에 언급했음)

- 기울기를 안구하면 안되나?`

추천시스템

import

import pandas as pd 
import torch 
from fastai.collab import * 
from fastai.tabular.all import * 

Data (시뮬레이션)

df=pd.read_csv('https://raw.githubusercontent.com/guebin/2021BDA/master/_notebooks/2021-11-30-recommend.csv')
df
user item rating item_name
0 1 15 1.084308 홍차5
1 1 1 4.149209 커피1
2 1 11 1.142659 홍차1
3 1 5 4.033415 커피5
4 1 4 4.078139 커피4
... ... ... ... ...
995 100 18 4.104276 홍차8
996 100 17 4.164773 홍차7
997 100 14 4.026915 홍차4
998 100 4 0.838720 커피4
999 100 7 1.094826 커피7

1000 rows × 4 columns

user-item matrix

- 아래와 같은 매트릭스를 고려하자.

df2 = pd.DataFrame([[None]*20]*100,columns=['커피'+str(i) for i in range(1,11)]+['홍차'+str(i) for i in range(1,11)]) 
df2.index = pd.Index(['user'+str(i) for i in range(1,101)])
df2
커피1 커피2 커피3 커피4 커피5 커피6 커피7 커피8 커피9 커피10 홍차1 홍차2 홍차3 홍차4 홍차5 홍차6 홍차7 홍차8 홍차9 홍차10
user1 None None None None None None None None None None None None None None None None None None None None
user2 None None None None None None None None None None None None None None None None None None None None
user3 None None None None None None None None None None None None None None None None None None None None
user4 None None None None None None None None None None None None None None None None None None None None
user5 None None None None None None None None None None None None None None None None None None None None
... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ...
user96 None None None None None None None None None None None None None None None None None None None None
user97 None None None None None None None None None None None None None None None None None None None None
user98 None None None None None None None None None None None None None None None None None None None None
user99 None None None None None None None None None None None None None None None None None None None None
user100 None None None None None None None None None None None None None None None None None None None None

100 rows × 20 columns

for (i,j) in zip(df.user.to_list(), df.item.to_list()):
    df2.iloc[i-1,j-1]=df.query('user == @i and item == @j')['rating'].to_list()[0]
df2.iloc[:5]
커피1 커피2 커피3 커피4 커피5 커피6 커피7 커피8 커피9 커피10 홍차1 홍차2 홍차3 홍차4 홍차5 홍차6 홍차7 홍차8 홍차9 홍차10
user1 4.149209 None None 4.078139 4.033415 4.071871 None None None None 1.142659 1.109452 None 0.603118 1.084308 None 0.906524 None None 0.903826
user2 4.031811 None None 3.822704 None None None 4.07141 3.996206 None None 0.839565 1.011315 None 1.120552 0.91134 None 0.860954 0.871482 None
user3 4.082178 4.196436 None 3.956876 None None None 4.450931 3.97209 None None None None 0.983838 None 0.918576 1.206796 0.913116 None 0.956194
user4 None 4.000621 3.89557 None 3.838781 3.967183 None None None 4.105741 1.147554 None 1.34686 None 0.614099 1.297301 None None None 1.147545
user5 None None None None 3.888208 None 3.97033 3.97949 None 4.010982 None 0.920995 1.081111 0.999345 None 1.195183 None 0.818332 1.236331 None
  • user1~user5는 대체로 커피를 선호한다.
  • user1은 커피 2,3,7,8,9,10을 먹지는 않았는데, 아마 먹었다면 높은 점수를 주었을 것임.
df2.iloc[47:53]
커피1 커피2 커피3 커피4 커피5 커피6 커피7 커피8 커피9 커피10 홍차1 홍차2 홍차3 홍차4 홍차5 홍차6 홍차7 홍차8 홍차9 홍차10
user48 None None 3.718709 None None None 4.484278 4.242403 None 3.654447 0.981285 1.089422 0.945297 None 1.367151 0.984146 None 0.779323 None None
user49 4.012018 4.347253 3.752401 None None None 4.182699 3.763614 4.112049 None None 0.921155 0.947049 0.937485 None None 1.046802 None None None
user50 4.208589 None 3.995654 3.85952 4.186735 None None 3.955416 4.017231 4.312376 None None 1.148465 None None None 1.2535 None None 0.985061
user51 None None 1.158754 None None 0.961621 0.958685 0.986134 1.207339 0.799213 None 4.053459 None None 4.214456 3.94822 None 4.168223 None None
user52 0.794371 None None None None 0.72175 None 0.99541 None None None None 4.713239 4.061688 3.791844 4.008471 3.96703 4.420732 4.055309 None
user53 None 0.586384 1.170372 0.939492 None None 1.299762 None None 0.818039 4.155103 4.131003 None 3.839847 3.859583 None None 3.873978 None None
  • user1 $\sim$ user50은 커피를 선호하고 user51 $\sim$ user100은 홍차를 선호함
df2.iloc[94:]
커피1 커피2 커피3 커피4 커피5 커피6 커피7 커피8 커피9 커피10 홍차1 홍차2 홍차3 홍차4 홍차5 홍차6 홍차7 홍차8 홍차9 홍차10
user95 1.190142 None 0.818878 None 1.150194 None None 1.287971 None 0.586373 None None 4.06455 4.399665 None None 3.573139 3.944602 None 4.012274
user96 0.511905 1.066144 None 1.31543 None 1.285778 None 0.6784 1.02302 0.886803 None 4.055996 None None 4.156489 4.127622 None None None None
user97 None 1.035022 None 1.085834 None 0.812558 None 1.074543 None 0.852806 3.894772 None 4.071385 3.935935 None None 3.989815 None None 4.267142
user98 None 1.115511 None 1.101395 0.878614 None None None 1.329319 None 4.12519 None 4.354638 3.811209 4.144648 None None 4.116915 3.887823 None
user99 None 0.850794 None None 0.927884 0.669895 None None 0.665429 1.387329 None None 4.329404 4.111706 3.960197 None None None 3.725288 4.122072
user100 None None 1.413968 0.83872 None None 1.094826 0.987888 None 1.177387 3.957383 4.136731 None 4.026915 None None 4.164773 4.104276 None None

예측?

- None의 값을 추론하는 방법? 머리속으로는 알고있음. 그런데 어떻게 수식화 할지?

df2.loc['user1']
커피1     4.149209
커피2         None
커피3         None
커피4     4.078139
커피5     4.033415
커피6     4.071871
커피7         None
커피8         None
커피9         None
커피10        None
홍차1     1.142659
홍차2     1.109452
홍차3         None
홍차4     0.603118
홍차5     1.084308
홍차6         None
홍차7     0.906524
홍차8         None
홍차9         None
홍차10    0.903826
Name: user1, dtype: object

- 유저의 취향은 예를들면 아래와 같이 표현할 수 있다.

  • user1의 취향 = (커피좋아함, 홍차싫어함) = (0.8, 0.21)

- 코딩해보자.

user1_prprnc = (0.8,0.21) 
user1_prprnc
(0.8, 0.21)

- 여기에서 (0.8,0.21)은 각각 (커피음료,홍차음료)와 같은 음료의 특징으로 해석가능. 현재는 단순하게 원소가 2인 벡터이지만 다양한 feature도 가능하다.

  • 음료의특징: (커피음료, 홍차음료, 우유포함, 설탕포함정도, 아이스여부, 행사여부)

- 아이템4와 아이템11의 특징은 예를들면 아래와 같이 표현할 수 있다.

item4_ftr = (0.71, 0.03) # 커피4 
item11_ftr = (0.1, 0.88) # 홍차1  

- 유저의 선호도와 아이템의 feature를 내적하여보자.

np.array(user1_prprnc) @ np.array(item4_ftr) 
0.5742999999999999
np.array(user1_prprnc) @ np.array(item11_ftr) 
0.26480000000000004

- 유저의 선호도와 item의 feature가 비슷할수록 값이 높게 나온다. 실제로 user1이 커피4와 홍차1에 매긴 평점은 (4.078139,1.142659) 인데 (0.5743, 0.2648) 가 실제평점과 비슷하게 나오도록 하면 좋겠다.

- 적당히 값을 조정하여 보자.

user1_prprnc = (0.8,0.21) 
item4_ftr = (5, 0.03) # 커피4 
item11_ftr = (0.1, 4.8) # 홍차1
np.array(user1_prprnc) @ np.array(item4_ftr) 
4.0063
np.array(user1_prprnc) @ np.array(item11_ftr) 
1.088

- 신기한것은 이 파라메터들이 있다면 None에 대한 추론도 가능하다는 것이다. 예를들어서 user2는 홍차1을 먹지 않았지만 item11_ftr(홍차1)의 계수값이 다른유저를 통해 학습되었다면 user2가 홍차1을 먹고 내릴 평점이 예측가능하다.

df2.loc['user2']
커피1     4.031811
커피2         None
커피3         None
커피4     3.822704
커피5         None
커피6         None
커피7         None
커피8      4.07141
커피9     3.996206
커피10        None
홍차1         None
홍차2     0.839565
홍차3     1.011315
홍차4         None
홍차5     1.120552
홍차6      0.91134
홍차7         None
홍차8     0.860954
홍차9     0.871482
홍차10        None
Name: user2, dtype: object
user2_prprnc = (0.77,0.18) 
np.array(user2_prprnc) @ np.array(item11_ftr)
0.9410000000000001

- 생각해보니까 user1_prprnc, ... , user100_prprnc, item1_ftr, ... , item20_ftr를 ${\bf W}$라고 해석하면 우리가 ${\bf W}$ 바꿈에 따라 예측값과 손실이 결정되므로 네트워크 구조가 만들어진다.

- 정리하면 적당한 아래와 같은 매트릭스를 정의하여

  • ${\bf U} = \begin{bmatrix} \tt user1~ prprnc \\ \dots \\ \tt user100~prprnc \end{bmatrix}$ $\quad 100 \times 2$ mat
  • ${\bf V} = \begin{bmatrix} \tt item1~ ftr \\ \dots \\ \tt item20~ftr \end{bmatrix}$ $\quad 20 \times 2$ mat

${\bf U}{\bf V}^\top \approx $ df2 가 만족하도록 하면 된다. (None은 무시)

fastai를 학습: df $\to$ dls

dls=CollabDataLoaders.from_df(df,bs=100) 
dls.items
user item rating item_name
814 82 8 0.927823 커피8
328 33 7 4.175642 커피7
68 7 12 0.995359 홍차2
205 21 3 3.797043 커피3
509 51 6 0.961621 커피6
... ... ... ... ...
886 89 5 1.064357 커피5
142 15 1 4.245275 커피1
512 52 8 0.995410 커피8
731 74 18 4.053283 홍차8
318 32 9 3.850437 커피9

800 rows × 4 columns

fastai를 통한 학습: learn

lrnr = collab_learner(dls,n_factors=2,y_range=(0,5))
lrnr.fit(30,0.01)
epoch train_loss valid_loss time
0 2.332310 2.324714 00:00
1 2.301398 2.314132 00:00
2 2.255456 2.235110 00:00
3 2.180693 2.062851 00:00
4 2.068622 1.799302 00:00
5 1.918429 1.467824 00:00
6 1.734515 1.099332 00:00
7 1.529240 0.753254 00:00
8 1.318664 0.473745 00:00
9 1.118887 0.281612 00:00
10 0.941217 0.170759 00:00
11 0.790015 0.113465 00:00
12 0.664468 0.087310 00:00
13 0.561132 0.076604 00:00
14 0.476078 0.072097 00:00
15 0.405709 0.069820 00:00
16 0.347135 0.068991 00:00
17 0.298197 0.068708 00:00
18 0.257201 0.068535 00:00
19 0.222736 0.068374 00:00
20 0.193690 0.067657 00:00
21 0.169164 0.066795 00:00
22 0.148427 0.065751 00:00
23 0.130861 0.064602 00:00
24 0.115963 0.064182 00:00
25 0.103277 0.063416 00:00
26 0.092521 0.062831 00:00
27 0.083378 0.062791 00:00
28 0.075562 0.062564 00:00
29 0.068923 0.061822 00:00
lrnr.show_results()
user item rating rating_pred
0 64.0 4.0 1.193997 1.018805
1 65.0 14.0 3.956821 3.988665
2 59.0 2.0 1.161738 0.927356
3 36.0 6.0 4.028756 4.073104
4 78.0 10.0 0.968873 0.834417
5 1.0 17.0 0.906524 0.973807
6 96.0 6.0 1.285778 0.798828
7 91.0 10.0 1.453194 1.013060
8 25.0 10.0 4.342545 4.081783

- 학습이 잘 되었음.

추천: 마지막유저에 대한 예측결과 (아마 홍차를 좋아할듯)

X,y = dls.one_batch()
X[:5]
tensor([[94,  3],
        [46, 12],
        [61, 19],
        [47, 11],
        [87, 20]])
y[:5]
tensor([[1.1616],
        [0.6749],
        [4.1603],
        [0.6241],
        [4.3710]])
torch.tensor([[94,3]])
tensor([[94,  3]])
lrnr.model(torch.tensor([[94,3]]).to("cuda:0"))
tensor([1.2002], device='cuda:0', grad_fn=<AddBackward0>)
x100 = torch.tensor([[100,j] for j in range(1,21) ])
lrnr.model(x100.to("cuda:0"))
tensor([1.0416, 1.0414, 1.0742, 1.1004, 1.0125, 1.0141, 1.1448, 1.0488, 1.1155,
        1.0428, 3.9658, 4.0532, 4.0562, 3.9919, 4.0497, 4.0288, 3.9983, 4.0984,
        3.9943, 4.0803], device='cuda:0', grad_fn=<AddBackward0>)

- 예상대로 홍차를 매우 좋아함.

숙제

숙제는 유저2번에 대한 선호도 조사