도서 : 신경망 첫걸음(한빛미디어)

지음 : 타리크라시드(송교석 옮김)


  •  학습률 변경을 통한 신경망의 개선
    • 우리는 MNIST 데이터에 대해 약 95%의 정확도를 달성했지만 좀 더 개선을 할 여지가 있을지 보겠습니다.
    • 첫 번째로 학습률을 조정해보는 것입니다. 우리는 0.3이라는 한 가지 값으로만 학습률을 정했습니다. 따라서 여러 학습률 값을 실험해보며 성능을 측정해 보겠습니다.

    • 그래프를 보면 0.1~0.3의 학습률을 가질 때 좋은 성능을 보입니다. 0.2의 학습률이 가장 높으므로 0.2로 학습률을 정해놓겠습니다.
  • 여러 번 수행을 통한 신경망의 개선
    • 우리는 데이터 모음에 대해 학습을 여러 번 반복함으로써 신경망의 성능을 개선 할 수 있습니다. 한 번의 수행을 주기(epoch)라고 합니다.
    • 이러한 반복 작업이 가치가 있는 이유는 경사 하강법에 의해 가중치의 값이 업데이트되는 과정에서 더 많은 가능성을 제공해주기 때문입니다.
    • 이전에 만든 인공 신경망 소스에 다음과 같은 코드를 추가하여 주기를 넣어보도록 하겠습니다.

 

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
106
107
108
109
import numpy
import scipy.special
import matplotlib.pyplot
%matplotlib inline
 
#신경망 클래스의 정의
class neutalNetwork:
    
    #신경망 초기화하기
    def __init__(self, inputnodes, hiddennodes, outputnodes, learningrate):
        #입력, 은닉, 출력 계층의 노드 개수 설정
        self.inodes = inputnodes
        self.hnodes = hiddennodes
        self.onodes = outputnodes
        
        #가중치 행렬 wih와 who -> 정규분포의 중심은 0.0으로 설정, 표준편차는 노드로 들어오는 연결 노드의 개수에 루트를 씌우고 역수를 취함(pow함수)
        #배열 내 가중치는 w_i_j로 표기. 노드 i에서 다음 계층의 j로 연결됨을 의미
        #w11 w21
        #w12 w22 등
        self.wih = numpy.random.normal(0.0, pow(self.hnodes, -0.5), (self.hnodes, self.inodes))
        self.who = numpy.random.normal(0.0, pow(self.onodes, -0.5), (self.onodes, self.hnodes))
 
        #학습률
        self.lr = learningrate
        
        #활성화 함수는 시그모이드 함수를 이용
        self.activation_function = lambda x: scipy.special.expit(x)
        
        pass
    
    #신경망 학습시키기
    def train(self, inputs_list, targets_list) :
        #입력 리스트를 2차원의 행렬로 변환
        inputs = numpy.array(inputs_list, ndmin=2).T
        targets = numpy.array(targets_list, ndmin=2).T
        
        #은닉 계층으로 들어오는 신호를 계산
        hidden_inputs = numpy.dot(self.wih, inputs)
        #은닉 계층에서 나가는 신호를 계산
        hidden_outputs = self.activation_function(hidden_inputs)
        
        #최종 출력 계층으로 들어오는 신호를 계산
        final_inputs = numpy.dot(self.who, hidden_outputs)
        #최종 출력 계층에서 나가는 신호를 계산
        final_outputs = self.activation_function(final_inputs)
        
        #오차는 (실제 값 - 계산 값)
        output_errors = targets - final_outputs
        #은닉 계층의 오차는 가중치에 의해 나뉜 출력 계층의 오차들을 재조합해 계산
        hidden_errors = numpy.dot(self.who.T, output_errors)
        
        #은닉 계층과 출력 계층 간의 가중치 업데이트
        self.who += self.lr*numpy.dot((output_errors*final_outputs*(1.0 - final_outputs)), numpy.transpose(hidden_outputs))
        
        #입력 계층과 은닉 계층 간의 가중치 업데이트
        self.wih += self.lr*numpy.dot((hidden_errors*hidden_outputs*(1.0 - hidden_outputs)), numpy.transpose(inputs))
        
        pass
    
    #신경망에 질의하기
    def query(self, inputs_list):
        #입력 리스트를 2차원 행렬로 변환
        inputs = numpy.array(inputs_list, ndmin=2).T
        
        #은닉 계층으로 들어오는 신호를 계산
        hidden_inputs = numpy.dot(self.wih, inputs)
        #은닉 계층에서 나가는 신호를 계산
        hidden_outputs = self.activation_function(hidden_inputs)
        
        #최종 출력 계층으로 들어오는 신호를 계산
        final_inputs = numpy.dot(self.who, hidden_outputs)
        #최종 출력 계층에서 나가는 신호를 계산
        final_outputs = self.activation_function(final_inputs)
        
        return final_outputs
    
#입력, 은닉, 출력 노드의 수
input_nodes = 784 #손글씨 숫자 이미지를 구성하는 픽셀이 28x28의 크기를 가지기 때문
hidden_nodes = 100 #입력 값의 수보다 작은 값을 선택함으로써 신경망이 주요 특징을 찾아낸다, 너무 적은 수의 은닉 계층 노드를 선택한다면 제한적이 될 수도 있다
output_nodes = 10 #0~9까지의 레이블을 구분해야 하므로 출력은 10개면 충분하다.
 
#학습률은 0.3으로 정의
learning_rate = 0.2
#신경망의 인스턴스를 생성
= neutalNetwork(input_nodes,hidden_nodes,output_nodes,learning_rate)
 
#데이터를 불러오고 그 파일을 읽는다.
training_data_file = open("mnist_train.csv",'r')
training_data_list = training_data_file.readlines()
training_data_file.close()
 
epochs = 7
 
#신경망 학습시키기
for e in range(epochs):
    #학습 데이터 모음 내의 모든 레코드 탐색
    for record in training_data_list:
        #레코드를 쉼표에 의해 분리
        all_values = record.split(','#split는 구분자로 사용할 기호를 그 매개변수로 가진다. 리스트를 불러와서 쉼표로 구분하여 분리
        #입력 값의 범위와 값 조정
        inputs = (numpy.asfarray(all_values[1:])/255.0*0.99)+0.01
        #결과 값 생성 (실제 값인 0.99 외에는 모두 0.01)
        targets = numpy.zeros(output_nodes) + 0.01
        #all_values[0]은 이 레코드에 대한 결과 값
        targets[int(all_values[0])] = 0.99
        n.train(inputs,targets)
 
        pass
    pass
cs
    • epochs라는 것을 넣어 train을 여러번 하게 했습니다. 그리고 학습률을 0.2로 바꿨습니다.
    • 그 결과 스코어를 계산해보니

    • 96%정도로 올랐습니다. 주기에 따른 정확도를 그래프로 나타내면 다음과 같습니다.

    • 학습률이 작을수록 더욱 성능이 좋아지는 것을 확인할 수 있었습니다. 이는 학습률이 작을 수록 경사 하강법에서 더 정밀한 최적의 경로를 찾을 수 있다는 말이 됩니다. 물론 이는 과학적인 방식이 아니며 손글씨 인식에만 국한되는 법칙임을 알려드립니다. 제대로 하기 위해서는 각각의 학습률과 주기의 다양한 조합에 대해 여러 번 실험을 함으로써 경사 하강법에 내재되어 있는 임의적 요소의 효과를 최소화해야 합니다.
  • 신경망 구조 변경하기
    • 신경망을 개선하는 또 한 가지 방법은 신경망의 구조를 변경하는 것입니다. 중간에 있는 은닉 계층의 노드의 수를 변경해보겠습니다.

  • 은닉 노드의 개수가 커질수록 성능이 높아지기는 하지만 200이상부터는 거의 변하지 않는 것을 확인할 수 있었습니다.
  • 500개의 은닉 노드에서 학습률 0.1로 5번의 학습을 해보고 정확도를 확인해보겠습니다.
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
#신경망 테스트
 
#신경망의 성능의 지표가 되는 성적표를 아무 값도 가지지 않도록 초기화
scorecard = []
 
#테스트 데이터 모음 내의 모든 레코드 탐색
for record in test_data_list:
    #레코드를 쉼표에 의해 분리
    all_values = record.split(',')
    #정답은 첫 번째 값
    correct_label = int(all_values[0])
    #print(correct_label,"correct label")
    #입력 값의 범위와 값 조정
    inputs = (numpy.asfarray(all_values[1:])/255.0*0.99)+0.01
    #신경망에 질의
    outputs = n.query(inputs)
    #가장 높은 값의 인덱스는 레이블의 인덱스와 일치
    label = numpy.argmax(outputs)
    #print(label, "network's answer")
    #정답 또는 오답을 리스트에 추가
    if(label == correct_label):
        #정답인 경우 성적표에 1을 더함
        scorecard.append(1)
    else:
        #정답이 아닌 경우 성적표에 0을 더함
        scorecard.append(0)
        pass
    pass
 
#print(scorecard)
 
#스코어 평균
scorecard_array = numpy.asarray(scorecard)
print("performance = ", scorecard_array.sum()/scorecard_array.size)
cs

    • 97.5%의 정확도까지 올라갔습니다! 

+ Recent posts