Post

Pytorch 로 XOR 구현해보기

XOR 샘플 예제를 간단하게 구현하면서, PyTorch를 이용한 학습의 전반적인 흐름을 파악해본다. 학습이라는 개념과 학습을 시키기 위한 대상은 어떻게 정의할 수 있는지를 알아보고, PyTorch를 이용할 때, 자동완성? 기능처럼 보이지 않는 동작 내용을 파악하려고 한다.


개념정리


Note: 항시 마인드 셋을 갖고, 새롭게 도전전하는 일에 대해 “왜?” 해야하는지 스스로의 동기가 필요합니다 :D

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
import torch
import numpy as np
from tqdm import tqdm

# XOR 문제 1개의 input/hidden/output 레이어의 MLP를 classification으로 활용하여 XOR 문제를 푼다

X = np.array([[0,0],[1,0],[0,1],[1,1]]) # 입력
Y = np.array([[1,0],[0,1],[0,1],[1,0]]) # 출력

print('numpy:',X,Y)

X = torch.tensor(X, dtype=torch.float64) # numpy -> torch (double)
Y = torch.tensor(Y, dtype=torch.float64) # numpy -> torch (double)

print('torch:',X,Y)

# MLP
w1 = torch.tensor(np.random.normal(size=(2,2)), requires_grad=True) # 학습을 위해 requires_grad 옵션 추가
b1 = torch.tensor(np.random.normal(size=(1,2)), requires_grad=True) # 학습을 위해 requires_grad 옵션 추가

print('w1:',w1) #  tensor([[ 0.8241, -1.8039], [ 0.6684, -0.6701]], dtype=torch.float64, requires_grad=True)

w2 = torch.tensor(np.random.normal(size=(2,2))) # 학습 불가
b2 = torch.tensor(np.random.normal(size=(1,2))) # 학습 불가

print(w2) # tensor([[ 1.2678,  0.4371], [-1.4530, -0.9344]], dtype=torch.float64) # require_grad가 없음

# 학습을 위해 requires_grad 추가
w2.requires_grad_(True)

print(w2) # tensor([[ 1.2678,  0.4371], [-1.4530, -0.9344]], dtype=torch.float64, requires_grad=True) # requires_grad가 있음

# torch 함수 특징, 함수 끝에 '_'가 있으면 변수 자체에 바로 적용됨. (ex: require_grad, require_grad_)
print(b2) # tensor([[-0.7115], [-0.0490]], dtype=torch.float64)

try:
    b2.requires_grad(True) # 함수 명령어는 있으나 이렇게 사용하면 에러
except Exception as e:
    print(e)

print('b2:',b2) # tensor([[-0.7115], [-0.0490]], dtype=torch.float64) # 아직 적용 안됨
b2.requires_grad_(True)
print('b2:',b2) # tensor([[-0.7115], [-0.0490]], dtype=torch.float64, requires_grad=True) # 적용 됨

print('''[참고]
      신경망 weight 초기화를 위한 권장되는 방법
      xavier uniform 이나 normal을 사용하는 것.(코드 참고)
      XOR 문제도 local minima가 있음을 기억할 것.
      hyperparameter 변경을 통해 극복 가능할지도?''')

# torch.nn.init.xavier_normal_(w1)
# torch.nn.init.xavier_normal_(b1)
# torch.nn.init.xavier_normal_(w2)
# torch.nn.init.xavier_normal_(b2)

# 학습을 위한 준비
opt = torch.optim.Adam([w1,b1,w2,b2]) # optimizer는 Adam, hyperparameter는 임의 입력이 가능하지만, 일단 default값 사용 (learning rate:0.001, beta = (0.9, 0.999), eps=1e-8)

# [학습 1회]
opt.zero_grad()

# 네트워크 동적 계산
xor_mlp_input_layer = X
xor_mlp_hidden_layer = torch.sigmoid(xor_mlp_input_layer@w1+b1)
xor_mlp_output_layer = torch.sigmoid(xor_mlp_hidden_layer@w2+b2)

print('out:',xor_mlp_output_layer) # 현재 모델의 output

loss = torch.mean((xor_mlp_output_layer-Y)**2) # mean-square loss

print('loss:',loss) # 현재 모델의 loss
print('loss(value only):',loss.item())

loss.backward()
print('b2(before):',b2)
opt.step()
print('b2(after):',b2)

# [학습 100000회 반복]
for i in (pbar := tqdm(range(100000))):
    opt.zero_grad()

    # 네트워크 동적 계산
    xor_mlp_input_layer = X
    xor_mlp_hidden_layer = torch.sigmoid(xor_mlp_input_layer@w1+b1)
    xor_mlp_output_layer = torch.sigmoid(xor_mlp_hidden_layer@w2+b2)

    loss = torch.mean((xor_mlp_output_layer-Y)**2) # mean-square loss

    loss.backward()
    opt.step()

    if i%1000 == 0:
        pbar.set_description(f'loss:{loss.item()}')

print('입력 데이터:',X)
print('희망 데이터:',Y)

with torch.no_grad(): # 학습 할 필요가 없어서 gradient를 적용하지 않고 계산만 하길 원하는 경우 no_grad 사용
    xor_mlp_input_layer = X
    xor_mlp_hidden_layer = torch.sigmoid(xor_mlp_input_layer@w1+b1)
    xor_mlp_output_layer = torch.sigmoid(xor_mlp_hidden_layer@w2+b2)
    xor_classifier_value = xor_mlp_output_layer

print('XOR 신경망 결과:',xor_classifier_value)

*추가 응용 예제

This post is licensed under CC BY 4.0 by the author.