(14주차) 12월16일
순환신경망
-
(1/6) AGI
-
(2/6) Fastai를 이용한 언어모델 실습
-
(3/6) hello, 간단한 순환신경망 구현 (1)
-
(4/6) hello, 간단한 순환신경망 구현 (2)
-
(5/6) hello, 간단한 순환신경망 구현 (3)
-
(6/6) hello, 간단한 순환신경망 구현 (4)
김 교수는 “인공지능은 사람보다 훌륭한 강인공지능과 사람보다 못하지만 사람에게 도움이 되는 약인공지능으로 나눌 수 있다”며 “90년대 후반부터 부활한 인공지능은 기계학습의 발전, 데이터로부터의 지식 추출, 빠른 컴퓨터와 다양한 데이터로 새로운 지식을 창출할 수 있는 방향으로 발전했다”고 설명했다. 또 “사람들은 지식의 오류를 인정하는 약인공지능으로 갔고 약인공지능은 확률, 통계 이론을 중심으로 발전하고 있다”고 덧붙였다. 강인공지능은 영화 터미네이터, 아이로봇에 나오는 로봇처럼 사람의 능력을 뛰어넘는 인공지능을 의미한다. 사람보다 강한 체력과 지능으로 인간이 못하는 일을 척척 해내는 인공지능이다. 반면 약인공지능은 바이센티니얼맨이나 A.I.에 나오는 로봇으로 감성 등 인간 고유의 특성을 넘을 수 없고 오류가 나기도 하지만 뛰어난 연산능력으로 사람의 업무에 도움을 주는 인공지능이다. 인공지능 초기에는 강인공지능이 대세를 이뤘다. 튜링테스트를 시작으로 초기의 자연어 처리 기능이 등장했지만 강인공지능은 커진 기대감을 충족시키지 못하고 큰 실망감을 안기며 사라졌다. 김 교수는 “70년대 중반부터 과학기술 투자 펀드가 인공지능 연구 지원을 끊었는데 이유는 결과물이 없었기 때문”이라고 설명했다.
-
GPT3를 기점으로 언어모델의 발전이 눈부심 $\to$ AGI의 출현이라 말하는 사람도 있음
from fastai.text.all import *
import numpy as np
path = untar_data(URLs.IMDB)
files = get_text_files(path)
files
- files: path의 모든 하위폴더에 존재하는 text파일들을 목록화하여 저장한것
dls = DataBlock(
blocks=TextBlock.from_folder(path,is_lm=True),
get_items=get_text_files, splitter=RandomSplitter(0.1)
).dataloaders(path,bs=128,seq_len=80)
dls.show_batch()
- xxbox: 새로운 텍스트의 시작
- xxmaj: 다음단어가 대문자임로 시작함을 의미함 (모든단어는 기본적으로 소문자로 생각함)
lrnr = language_model_learner(dls,AWD_LSTM,metrics=accuracy).to_fp16()
lrnr.fit_one_cycle(5)
lrnr.predict('I liked this movie because',40)
- 말이 안되는게 있긴 하겠지만 그럴듯해보임
text = 'h e l l o '*100
text
tokens = text.split(' ')[:-1]
tokens[:10]
-
바로직전의 문자로 다음문자를 맞춰보자
- hello니까, h $\to$ e, e$\to$ l, l $\to$ l/o (?), o $\to$ h, ...
- l 다음에 올 문자가 조금 애매하다.
-
마치 아래의 표에서 $X \to y$인 맵핑을 알아차려 $X$를 보고 $y$를 예측하듯이
X | y |
---|---|
1 | 2 |
2 | 4 |
3 | 6 |
1 | 2 |
2 | 4 |
3 | 6 |
1 | 2 |
2 | 4 |
... | ... |
아래의 규칙을 알아차리는 것이 목표이다.
X | y |
---|---|
h | e |
e | l |
l | l/o |
o | h |
h | e |
... | ... |
-
X,y를 설정하자.
len(tokens)
X= tokens[:(len(tokens)-1)]
y= tokens[1:]
X[0],y[0]
X[1],y[1]
print(X[:10])
print(y[:10])
-
이제 문자를 숫자로 바꾸어서 컴퓨터가 이해할수 있는 형태, 즉 학습가능한 형태로 만들자.
dic = {'h':0, 'e':1, 'l':2, 'o':3}
dic
dic['h'],dic['e'],dic['l'],dic['o']
nums = [dic[i] for i in tokens]
tokens[:10], nums[:10]
-
(맵핑방식1) 아래와 같이 문자와 숫자를 맵핑하였다.
문자(tokens) | 숫자(nums) |
---|---|
'h' | 0 |
'e' | 1 |
'l' | 2 |
'l' | 2 |
'o' | 3 |
'h' | 0 |
'e' | 1 |
'l' | 2 |
'l' | 2 |
'o' | 3 |
... | ... |
-
(맵핑방식2) 위의 방식보다 아래의 방식이 더 의미상 좋다. 위의 방식대로 맵핑하면하면 의미가 e=1
, l=2
가 되는데 그렇다고 해서 l이 e보다 2배 강한 입력
을 의미하는 것은 아니잖음?
문자(tokens) | 숫자(nums) |
---|---|
'h' | 1,0,0,0 |
'e' | 0,1,0,0 |
'l' | 0,0,1,0 |
'l' | 0,0,1,0 |
'o' | 0,0,0,1 |
'h' | 1,0,0,0 |
'e' | 0,1,0,0 |
'l' | 0,0,1,0 |
'l' | 0,0,1,0 |
'o' | 0,0,0,1 |
... | ... |
-
맵핑방식2로 처리하고 싶은데, 데이터 전처리 하기가 너무 힘들것 같다.
- 그런데 이러한것은 빈번하게 일어나는 상황
- 누군가가 구해놓지 않았을까?
- torch.nn.Embedding
-
맵핑방식1의 구현
_x = torch.tensor([[0.0],[1.0],[2.0],[2.0],[3.0],[0.0],[1.0],[2.0],[2.0],[3.0]])
_x
_l1 = torch.nn.Linear(in_features=1, out_features=20, bias=False)
_l1(_x)
- 입력: (10,1)
- 출력: (10,20)
-
맵핑방식2의 구현
e1= torch.nn.Embedding(num_embeddings=4, embedding_dim=20)
_x = torch.tensor([0,1,2,2,3,0,1,2,2,3])
_x
e1(_x)
- 입력 (10,1)
- 출력 (10,20)
-
torch.nn.Linear(), torch.nn.Embedding() 의 차이가 없어보인다? $\to$ 파라메터를 조사하면 차이가 있다
len(list(_l1.parameters())[0])
list(e1.parameters())[0].shape
-
결국에는 맵핑방식1의 경우 아래와 같이 이해할 수 있고
- ${\bf X}$: (10,1)
- ${\bf W}$: (1,20)
- ${\bf XW}$: (10,20)
-
맵핑방식2의 경우 아래와 같이 이해가능하다.
- ${\bf X}$: (10,1)
- $\tilde{\bf X}$: (10,4)
- ${\bf W}$: (4,20)
- $\tilde{\bf X}{\bf W}$: (10,20)
-
결국 우리가 맵핑방식2처럼 구현하고 싶다고 해도, 입력은 아래와 같이 넣어도 무방하다. 이후에는 파이토치의 torch.nn.Embedding()이 알아서 해결해준다.
_x
-
이제 숫자화된 자료 nums
를 이용하여 다시 X,y를 선언하자.
X = torch.tensor(nums[:499])
y = torch.tensor(nums[1:])
X[0],y[0]
X[1],y[1]
-
간단한 네트워크를 설계하자.
e1=torch.nn.Embedding(num_embeddings=4, embedding_dim=20)
l1=torch.nn.Linear(in_features=20,out_features=20)
a1=torch.nn.ReLU()
l2=torch.nn.Linear(in_features=20,out_features=4)
a2=torch.nn.Softmax()
X.shape, e1(X).shape
e1(X).shape, a1(l1(e1(X))).shape
a1(l1(e1(X))).shape, l2(a1(l1(e1(X)))).shape
a2(l2(a1(l1(e1(X))))).shape
- $X$의 차원이 정확하게 명시되지 않아서 대충 컴퓨터가 알아서 계산했다라는 뜻의 워닝
-
워닝이 찝찝하여 내가 softmax를 수동으로 직접계산해봄
l2(a1(l1(e1(X))))[0]
np.exp(-0.1398)/(np.exp(-0.1398)+np.exp(-0.1216)+np.exp(-0.0264)+np.exp(0.2706))
a2(l2(a1(l1(e1(X)))))[0]
- 잘 계산된것 같다.
-
순전파의 차원변화 요약
torch.Size([499]) # X
torch.Size([499, 20]) # e1이후
torch.Size([499, 20]) # l1이후
torch.Size([499, 20]) # a1이후
torch.Size([499, 4]) # l1이후
torch.Size([499, 4]) # a2이후 = yhat
net = torch.nn.Sequential(
torch.nn.Embedding(num_embeddings=4,embedding_dim=20),
torch.nn.Linear(in_features=20,out_features=20),
torch.nn.ReLU(),
torch.nn.Linear(in_features=20,out_features=4))
#torch.nn.Softmax()
net(X)
-
손실함수, 옵티마이저
loss_fn = torch.nn.CrossEntropyLoss()
optimizer = torch.optim.Adam(net.parameters())
-
학습
for i in range(1000):
## 1
yhat = net(X)
## 2
loss = loss_fn(yhat,y)
## 3
loss.backward()
## 4
optimizer.step()
optimizer.zero_grad()
X[:7]
net(X)[:7]
- 학습이 잘 되었다.
-
단어수가 4에서 바뀔때마다 아래를 반복하여 입력해야할까?
net = torch.nn.Sequential(
torch.nn.Embedding(num_embeddings=4,embedding_dim=20),
torch.nn.Linear(in_features=20,out_features=20),
torch.nn.ReLU(),
torch.nn.Linear(in_features=20,out_features=4))
#torch.nn.Softmax()
-
net
을 찍어내는 무언가가 있으면 좋겠다. 제가 만들어볼게요!
class BDA(Module):
def __init__(self, num_embeddings):
self.embedding = torch.nn.Embedding(num_embeddings,20)
self.linear1 = torch.nn.Linear(in_features=20,out_features=20)
self.relu = torch.nn.ReLU()
self.linear2 = torch.nn.Linear(in_features=20,out_features=num_embeddings)
def forward(self, X): # net(X)를 계산해주는 방식
u=self.linear1(self.embedding(X))
v=self.relu(u)
return self.linear2(v) # net(X)의 결과
net2 = BDA(4)
net
net2
-
net2도 학습하여 net와 동일한 결과가 나오는지 체크해보자.
loss_fn= torch.nn.CrossEntropyLoss()
optimizer2 = torch.optim.Adam(net2.parameters())
for i in range(1000):
## 1
yhat = net2(X)
## 2
loss = loss_fn(yhat,y)
## 3
loss.backward()
## 4
optimizer2.step()
optimizer2.zero_grad()
net2(X)
net(X)
-
net2도 잘 학습되었다.
-
X,y 를 다시 설정하자.
X = torch.tensor([nums[:498],nums[1:499]]).T
y = torch.tensor(nums[2:])
X[0],y[0] # h,e -> l
X[1],y[1] # e,l -> l
X[2],y[2] # l,l -> o
X[3],y[3] # l,o -> h
-
아키텍처를 대충 스케치하여 보자.
_e1 = torch.nn.Embedding(num_embeddings=4, embedding_dim=20)
X.shape, _e1(X).shape
-
이전의 아키텍처는 아래와 같았음
torch.Size([499]) # X
torch.Size([499, 20]) # e1이후
torch.Size([499, 20]) # l1이후
torch.Size([499, 20]) # a1이후
torch.Size([499, 4]) # l1이후
torch.Size([499, 4]) # a2이후 = yhat
-
마지막의 차원을 처리하기 애매해진다. $\to$ 순환망을 설계함
X[:,1]
class BDA2(Module):
def __init__(self, num_embeddings):
self.embedding = torch.nn.Embedding(num_embeddings,20)
self.linear1 = torch.nn.Linear(in_features=20,out_features=20)
self.relu = torch.nn.ReLU()
self.linear2 = torch.nn.Linear(in_features=20,out_features=num_embeddings)
def forward(self, X): # net(X)를 계산해주는 방식
x1=X[:,0] # X의 첫번째 칼럼, y보다 2시점이전 (x1,x2) -> y // (h,e) --> l
x2=X[:,1] # X의 두번째 칼럼, y보다 1시점이전
h=self.relu(self.linear1(self.embedding(x1))) # x1 -> x2를 예측하는 네트워크의 일부
h2=self.relu(self.linear1(h+ self.embedding(x2))) # x2 -> y를 예측하는 네트어크의 일부
return self.linear2(h2) # net(X)의 결과
-
결국 최종출력인 self.linear2(h2)는 h와 x2가 담긴 함수이다. 그런데 h는 x1이 담긴 함수이다. 따라서 h2는 x2가 담겨있는 동시에 x1대한 정보도 약하게 담겨있다고 볼 수 있음
net3=BDA2(4)
net3
net2
- 구조의 차이는 없지만 순전파의 계산방식이 다른다! (그렇다면 역전파 계산방식도 다르겠죠?)
-
다시 학습해보자.
loss_fn = torch.nn.CrossEntropyLoss()
optimizer3= torch. optim.Adam(net3.parameters())
for i in range(1000):
## 1
yhat = net3(X)
## 2
loss = loss_fn(yhat,y)
## 3
loss.backward()
## 4
optimizer3.step()
optimizer3.zero_grad()
X[:5]
net3(X)[:5]
- h,e $\to$ l
- e,l $\to$ l
- l,l $\to$ o
- l,o $\to$ h
- o,h $\to$ e
-
학습이 잘 되었다.