경사하강법: 함수의 기울기를 반복적으로 계산하면서 이 기울기에 따라 함수값이 낮아지는 방향으로 이동하는 알고리즘 (손실함수, 비용함수를 대상으로), 순전파와 역전파를 반복하며 파라미터를 변경해 나감
순전파: 입력 데이터에 대해 신경망 구조를 따라가면서 현재 파라미터를 이용해 손실함숫값을 계산
역전파: 순전파의 계산과정울 역순으로 거슬러 올라가면서 손실 함수값에 대해 영향을 미친 모든 성분에 대하여 손실 기울기를 계산함 (선형대수학 중 연쇄법칙 사용)
파라미터 성분에 대해서 계산된 손실 기울기를 이용해 값을 변경시킴
학습율(alpha)이라는 하이퍼 파리미터를 이용
편미분을 사용/ 전미분은 x가 다른 변수에 미치는 직 간접적인 영향까지 모두 따지는 반면 편미분은 직접적인 영향만 따짐
난수함수를 사용해 초기 파라미터 값을 초기화 함
Local Minimum을 해결할 만한 보조 기법들이 필요 ex)adam
import numpy as np
import csv
import time
np.random.seed(1234)
def randomize(): np.random.seed(time.time())
RND_MEAN = 0 #정규분포 난숫값 평균
RND_STD = 0.0030 #정규분포 난숫값 표준편차
LEARNING_RATE = 0.001 #학습율
def abalone_exec(epoch_count=10, mb_size=10, report=1):
load_abalone_dataset()
init_model()
train_and_test(epoch_count, mb_size, report)
def load_abalone_dataset():
with open('.../Study/파이썬 날코딩으로 알고 짜는 딥러닝/data/abalone.csv') as csvfile:
csvreader = csv.reader(csvfile) #csv파일을 메모리로 읽음
next(csvreader, None) # 첫 행을 읽지 않고 건너뛰게 만든다
rows = []
for row in csvreader:
rows.append(row)
global data, input_cnt, output_cnt
input_cnt, output_cnt = 10, 1 # 입출력 벡터 크기를 각각 10과 1
data = np.zeros([len(rows), input_cnt+output_cnt]) # 객체들의 입출력 정보를 저장할 데이터 행렬
# 원핫인코딩
for n, row in enumerate(rows):
if row[0] == 'I': data[n, 0] = 1
if row[0] == 'M': data[n, 1] = 1
if row[0] == 'F': data[n, 2] = 1
data[n, 3:] = row[1:]
def init_model():
global weight, bias, input_cnt, output_cnt
# 평균과 표준편차를 기준으로 input_cnt행 output_cnt열 가중치 생성 (10,1)
# 난숫값으로
weight = np.random.normal(RND_MEAN, RND_STD,[input_cnt, output_cnt])
# 편향벡터 1행1열
bias = np.zeros([output_cnt])
def train_and_test(epoch_count, mb_size, report):
step_count = arrange_data(mb_size) # 데이터를 섞고 학습용/평가용 데이터셋 분리, 정렬 등
test_x, test_y = get_test_data()
for epoch in range(epoch_count): # epoch만큼 학습 반복
losses, accs = [], []
for n in range(step_count): # 값 만큼 미니배치 처리
train_x, train_y = get_train_data(mb_size, n)
loss, acc = run_train(train_x, train_y)
losses.append(loss)
accs.append(acc)
if report > 0 and (epoch+1) % report == 0: # 지정된 보고 주기에 해당하는지 검사
acc = run_test(test_x, test_y)
print('Epoch {}: loss={:5.3f}, accuracy={:5.3f}/{:5.3f}'. \
format(epoch+1, np.mean(losses), np.mean(accs), acc))
final_acc = run_test(test_x, test_y)
print('\nFinal Test: final accuracy = {:5.3f}'.format(final_acc))
def arrange_data(mb_size):
global data, shuffle_map, test_begin_idx
shuffle_map = np.arange(data.shape[0]) #인덱스 만들기
np.random.shuffle(shuffle_map) #인덱스 섞음
step_count = int(data.shape[0] * 0.8) // mb_size # 미니배치 학습에 필요한 배치 수
test_begin_idx = step_count * mb_size # train test 분류 경계 인덱스
return step_count
def get_test_data():
global data, shuffle_map, test_begin_idx, output_cnt
test_data = data[shuffle_map[test_begin_idx:]]
return test_data[:, :-output_cnt], test_data[:, -output_cnt:]
def get_train_data(mb_size, nth):
global data, shuffle_map, test_begin_idx, output_cnt
if nth == 0: #epoch의 첫번째 호출에 학습데이터에 대한 부분적인 순서을 뒤섞음
np.random.shuffle(shuffle_map[:test_begin_idx])
train_data = data[shuffle_map[mb_size*nth:mb_size*(nth+1)]] #미니배치 구간의 위치를 따져 그 구간에 해당하는 shuffle_map만 가져옴
return train_data[:, :-output_cnt], train_data[:, -output_cnt:]
def run_train(x, y):
output, aux_nn = forward_neuralnet(x) #순전파 처리
loss, aux_pp = forward_postproc(output, y) #순전파 처리
accuracy = eval_accuracy(output, y) #정확도 계산
#aux_pp, aux_nn은 순전파 과정에서 확보 가능한 것들을 챙겨 역전파 함수에 전달
#불필요한 계산을 줄여 효율을 높임
G_loss = 1.0
G_output = backprop_postproc(G_loss, aux_pp)
backprop_neuralnet(G_output, aux_nn)
return loss, accuracy
def run_test(x, y):
output, _ = forward_neuralnet(x)
accuracy = eval_accuracy(output, y)
return accuracy
Y = XW +B일 때 Y_ij = X_i * W_j + B_j임 즉
이는 곧 다음 식과 같음
Y_ij의 손실 기울기는
결국 Y_ij를 통해 미치는 부분은
행렬 곱샘 형식과 일치하지 않아서 전치(Transpose)시킴
따라서
Y = XW +B일 때 Yij = X_i * W__j + Bj임 즉
결국
def forward_neuralnet(x):
global weight, bias
output = np.matmul(x, weight) + bias
return output, x #역전파의 보조를 위해 x도 같이 반환
def backprop_neuralnet(G_output, x): # G_output:순전파 output에 대한 손실 기울기
global weight, bias
g_output_w = x.transpose()
G_w = np.matmul(g_output_w, G_output)
G_b = np.sum(G_output, axis=0)
weight -= LEARNING_RATE * G_w
bias -= LEARNING_RATE * G_b
순전파는 편차 -> 제곱 -> 평균 순서로 output -> diff -> square -> loss임
따라서 역전파는 그 반대로 손실 기울기 1로부터 square -> diff -> output 으로 이어짐
평균 연산은
- 이 값을 square 텐서와 같은 구조로 모은 것이 G_loss_square임
제곱 연산은
편차 연산은
def forward_postproc(output, y):
diff = output - y
square = np.square(diff)
loss = np.mean(square) # 손실함수는 평균계산에 의한 함수 즉 스칼라텐서
return loss, diff
def backprop_postproc(G_loss, diff): # G_loss는 초기에 1로 설정돼있음
shape = diff.shape
g_loss_square = np.ones(shape) / np.prod(shape)
g_square_diff = 2 * diff
g_diff_output = 1
G_square = g_loss_square * G_loss
G_diff = g_square_diff * G_square
G_output = g_diff_output * G_diff
return G_output
def backprop_postproc_oneline(G_loss, diff): # backprop_postproc() 대신 사용 가능
return 2 * diff / np.prod(diff.shape)
def eval_accuracy(output, y):
mdiff = np.mean(np.abs((output - y)/y))
return 1 - mdiff