반응형
- 데이터를 기반으로 전복의 고리수를 예측하는 단층 퍼셉트론을 구현해보자.
- 주어진 데이터는 'Sex', 'Length', 'Diameter', 'Height', 'Whole weight', 'Shucked weight', 'Viscera weight', 'Shell weight', 그리고 결과치가 되는 'Rings' 이상 9개이다.
- 데이터의 80%는 학습 데이터로, 20%는 테스트 데이터로 활용된다.
- 수업에서 사용된 코드를 분석한 자료입니다.
파이썬 모듈 불러오기
import numpy as np
import csv
import time
np.random.seed(1234)
def randomize(): np.random.seed(time.time())
- 필요한 모듈을 불러온다.
- 추후 활용될 랜덤에 대비하여 미리 random함수의 seed와 randomize라는 seed 재설정함수를 만들어 놓는다.
하이퍼 파라미터 정의
RND_MEAN = 0
RND_STD = 0.0030
LEARNING_RATE = 0.001
- 학습이 진행될 동안 변하지 않는 제어값인 하이퍼 파라미터들을 정의한다.
- RND_MEAN과 RND_STD는 추후 정규분포로 랜덤난수를 가져올 때 평균값과 표준편차값으로 활용된다.
- LEARNING_RATE는 학습률이다.
메인 실험함수
def abalone_exec(epoch_count = 10, mb_size = 10, report = 1): # 에폭횟수, 미니배치크기, 보고주기
load_abalone_dataset() # 데이터 불러오는 함수
init_model() # 모델 초기화 함수
train_and_test(epoch_count, mb_size, report) # 학습 및 테스트 수행 함수
- 에폭횟수와 미니배치크기, 보고주기를 파라미터로 갖는 메인 실험함수이다. 본 함수를 구동하여 훈련 및 테스트를 진행한다.
- load_abalone_dataset() : abalone 관련 데이터를 불러온다.
- init_model() : 모델을 초기화한다.
- train_and_test() : 파라미터로 받아온 에폭횟수와 미니배치크기, 보고주기를 사용하여 학습 및 테스트를 수행하도록 한다.
데이터 불러오는 함수
def load_abalone_dataset():
with open('abalone.csv') as csvfile: # csv 파일 데이터 rows 리스트에 저장
csvreader = csv.reader(csvfile)
next(csvreader, None)
rows = []
for row in csvreader:
rows.append(row)
global data, input_cnt, output_cnt # 전역변수 생성
input_cnt, output_cnt = 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:]
- kaggle에서 다운로드 받은 abalone dataset을 활용해 데이터를 전역변수로 저장한다.
- csv 모듈을 활용해 먼저 rows 리스트에 모든 데이터정보를 저장한다.
- next는 csv데이터 내부에 첫번째 행에 존재하는 칼럼명 부분을 스킵하는 역할을 한다.
- input_cnt, output_cnt은 입력벡터와 출력벡터의 데이터 개수를 의미한다.
- data는 csv에서 얻어온 데이터들의 개수만큼 행 개수를 지정하고, input_cnt + output_cnt의 칼럼 개수를 지정하여 0으로 이루어진 numpy배열을 생성한다.
- 제일 위에도 적혀있듯이, 결과치가 되는 Rings(고리수)를 제외한 데이터 개수는 총 8개인데, input_cnt를 10으로 설정했다. 그 이유는 '원 핫 벡터'를 사용했기 때문이다. 'Sex(성별)'값을 유충, 수컷, 암컷으로 나누어야하는데 이러한 비선형 데이터를 단순히 0,1,2와 같은 값으로 처리한다면 컴퓨터가 제대로 처리하지 못할 것이다. (해당 값을 곱하는 등의 과정이 반복될텐데, 그 과정에서 0,1,2가 여전히 의미를 가지긴 힘들다) 따라서 유충, 수컷, 암컷의 컬럼을 각각 따로 만들고, 데이터별로 해당하는 컬럼의 값을 1로, 해당하지 않는 칼럼의 값을 0으로 설정하여 활용한다.
- 맨 아래의 for문은 그러한 원 핫 벡터를 처리하는 반복문이다.
모델 초기화 함수
def init_model():
global weight, bias, input_cnt, output_cnt
weight = np.random.normal(RND_MEAN, RND_STD,[input_cnt, output_cnt])
# 기존에 정해놓은 하이퍼 파라미터를 활용해 정규분포를 갖는 난수 생성
bias = np.zeros([output_cnt]) # 난수 행렬 생성
- 모델에 기본적으로 필요한 초기정보(가중치, 편향)를 설정한다
- weight는 가중치이다. 기존에 정의한 하이퍼 파라미터 RND_MEAN, RND_STD과 데이터를 불러오는 함수(load_abalone_dataset)에서 정의한 input_cnt와 output_cnt를 사용하여 정규분포를 갖는 랜덤난수 numpy배열을 생성하여 각 데이터를 가중치로 활용한다.
- bias는 편향치이다. output_cnt만큼만 있으면 되므로 그를 활용해 0으로 이루어진 numpy배열을 생성하고 초기엔 편향을 모두 0으로 설정한다.
- 두 값 모두 전역변수로 선언하여 다른 함수에서도 활용할 수 있도록 했다.
학습 및 테스트 수행 함수
def train_and_test(epoch_count, mb_size, report):
step_count = arrange_data(mb_size)
# mb_size를 기반으로 배치가 총 몇개의 미니배치로 나눠져 학습이 진행되어야 하는지 파악
test_x, test_y = get_test_data() # 테스트 데이터를 얻는다.
for epoch in range(epoch_count):
# epoch_count를 기반으로 반복횟수를 결정함
losses, accs = [], []
# 매 에폭마다 손실과 정확도를 저장
for n in range(step_count):
# 위 arrange_data에서 구한 미니배치 덩어리 수 만큼 반복
train_x, train_y = get_train_data(mb_size, n)
# n번째 미니배치에 대한 학습진행
loss, acc = run_train(train_x, train_y)
# 해당 학습에 대한 손실함수값과 정확도 리턴
losses.append(loss)
accs.append(acc)
if report > 0 and (epoch+1) % report == 0:
# 보고 주기가 0보다 크고, epoch값이 보고주기에 이르러야함
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)) # 최종보고
- 지금까지 설정한 것을 기반으로 학습 및 테스트를 수행하는 함수이다.
- 설정한 epoch_count만큼 학습을 진행한다. 각 epoch별로도 배치 통채로 학습을 하지 않고 미니배치의 크기로 나눠서 학습을 진행한다. (cf. epoch은 학습 데이터 전체를 모델에 한 번 학습시키는 것이다. 즉, epoch_count는 모델에 학습 데이터를 몇번 학습시킬지를 뜻하는 것이다.)
- 각 에폭마다 미니배치만큼의 사이즈로 학습을 진행한다. get_train_data로 학습 데이터를 얻고, run_train으로 학습을 수행한다.
- 사용자가 현 상황을 확인할 수 있도록 보고주기의 에폭마다 현 상황(몇번째 에폭인지, 미니배치들의 결과로 생긴 손실도들의 평균은 무엇인지, 미니배치들의 결과로 생긴 정확도들의 평균은 무엇인지, 현 상황에서 테스트 데이터에 대한 정확도는 얼마인지)을 출력한다.
학습 및 평가 데이터 획득 함수
def arrange_data(mb_size):
global data, shuffle_map, test_begin_idx
shuffle_map = np.arange(data.shape[0]) # 데이터의 행크기의 전역 np배열 shuffle_map생성
np.random.shuffle(shuffle_map) # 해당 배열을 무작위로 섞는다
step_count = int(data.shape[0] * 0.8) // mb_size
# 전체데이터 크기의 80%를 기준으로 미니배치 사이즈에 대한 미니배치 개수 출력 (1에폭 수행횟수)
test_begin_idx = step_count * mb_size
# 전역변수 test_begin_idx 설정해놓음 (추후 사용)
return step_count
- 미니배치의 사이즈 및 총 데이터 중 어디까지 학습 데이터로 활용하고, 어디부터 테스트 데이터로 활용할지 결정한다. 그리고 미니배치의 개수를 리턴한다.
- 전역변수 shuffle_map에는 '0 ~ 데이터행개수'의 값을 가지는 numpy 배열을 저장한다. 해당 배열은 shuffle로 무작위로 섞어서 사용된다. 해당 numpy 배열은 추후 데이터들을 단순히 배열순서로 사용하지 않고 섞어서 활용하기 위해 사용된다.
- 위 코드에서는 데이터의 80% 정도를 학습 데이터로 활용한다. (data.shape[0] * 0.8)
- 따라서 그 개수(데이터 개수의 80%)에 미니배치 사이즈를 나눠서 미니배치크기의 학습을 총 몇번 수행해야 1 에폭이 되는지 파악한다.
- 그리고 그 횟수를 step_count에 저장하고, 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:]
# 테스트 데이터의 독립변수(인풋 데이터), 종속변수(결과) 분할
- 테스트 데이터가 어디까지인지 파악하고, 그를 리턴한다. 단, 독립변수(인풋 데이터)와 종속변수(결과)로 데이터를 나눠서 처리한다.
- 앞선 arrange_data 함수에서 처리한 shuffle_map에 test_begin_idx를 활용해 fancy indexing을 하여 테스트 데이터를 찾는다.
- shuffle_map이 섞여있는 덕분에 주어진 데이터 순서대로가 아닌 섞인 데이터를 얻어올 수 있다.
def get_train_data(mb_size, nth): # 몇번째 미니배치인지 파악
global data, shuffle_map, test_begin_idx, output_cnt
if nth == 0: # 첫번째 시작하는 미니배치이면 처음부터 테스트 경계선까지 인덱스 섞기
np.random.shuffle(shuffle_map[:test_begin_idx])
train_data = data[shuffle_map[mb_size*nth:mb_size*(nth+1)]]
# n번째 미니배치부터 다음 미니배치까지의 데이터 파악하고 학습 데이터로 리턴
return train_data[:, :-output_cnt], train_data[:, -output_cnt:]
# 테스트 데이터의 독립변수(인풋 데이터), 종속변수(결과) 분할
- 파라미터로 미니배치 사이즈와 순번을 받는다.
- 만약 현재가 처음 미니배치에 돌입하는 것이라면 shuffle_map에서 학습 데이터 부분만을 찾아 셔플한다. (에폭마다 다른 결과를 이끌기 위함?)
- 그리고 shuffle_map의 현 미니배치에 해당하는 부분을 슬라이싱하여 그를 기반으로 데이터를 fanxy indexing하여 학습 데이터로 명시한다.
- 마찬가지로 독립변수(인풋 데이터)와 종속변수(결과)로 데이터를 나눠서 처리한다.
단층 퍼셉트론에 대한 순전파 함수
def forward_neuralnet(x):
global weight, bias # 전역변수 가중치와 편향 활용
output = np.matmul(x, weight) + bias
# matmul : x와 weight 행렬곱. / 그 결과에 편향 더함
return output, x
- 입력 데이터를 받아 해당 데이터 기반 순전파 결과값과 함께 리턴한다.
- 입력 데이터 (독립 변수)에 가중치 행렬을 곱하고 편향을 더해 결과 행렬을 도출한다.
- 그렇게 계산된 결과 행렬과 기존의 입력 데이터 행렬을 함께 리턴한다.
def forward_postproc(output, y):
diff = output - y
square = np.square(diff)
loss = np.mean(square) # 손실함수값 리턴
return loss, diff
- 순전파 후처리 과정으로, mse(평균 제곱오차)값을 구하고,추후 활용을 위해 단순 오차값인 diff도 함께 리턴한다.
단층 퍼셉트론에 대한 역전파 함수
def backprop_postproc(G_loss, diff):
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
- 출력층으로부터 받은 손실기울기 G_loss값(단층 퍼셉트론이므로 1을 받음)과 단순 오차값 diff를 받아 역전파에 필요한 값 'G_output'을 리턴한다.
- G_output은 복잡해보이지만, 결국은 MSE를 기울기 혹은 편차에 대해 편미분했을 때 도출되는 공통적인 부분을 표현한 것이다. (직접 편미분해보면 2(y-y')/size 정도의 공통값이 보일것이다.)
- 체인룰의 형태를 자세히 보여주기 위해 저런식으로 코드를 구현한 듯 하다. (아마 추후 활용성을 다양하게 하기 위함으로도 보임)
- 해당 값을 방식으로 학습에 대한 최종 값으로 활용한다.
def backprop_neuralnet(G_output, x):
global weight, bias # 전역변수 가중치와 편향 활용
g_output_w = x.transpose() # x의 전치행렬, G_output과 연산이 가능하도록 한다.
G_w = np.matmul(g_output_w, G_output)
G_b = np.sum(G_output, axis=0)
weight -= LEARNING_RATE * G_w # 전역변수 weight 갱신
bias -= LEARNING_RATE * G_b # 전역변수 bias 갱신
- 그렇게 얻은 값들을 기반으로 실질적 학습을 진행하는 함수이다.
- 기울기인 weight는 편미분을 하면 (2(y-y')/size) * x 의 형태가 나타난다. 해당값을 학습률에 곱해 빼준다.
- 편향인 bias는 편미분을 하면 (2(y-y')/size) 의 형태가 나타난다. 해당값을 학습률에 곱해 빼준다.
학습 실행 함수와 평가 실행함수
def run_train(x, y):
# 순전파 및 정확도 추출
output, aux_nn = forward_neuralnet(x) # 신경망 연산 부분 : 순전파 결과와 기존행렬 x 리턴
loss, aux_pp = forward_postproc(output, y) # 신경망 후처리 : 손실함수값, 단순 오차값 리턴
accuracy = eval_accuracy(output, y) # 정확도처리
# 역전파 과정
G_loss = 1.0 # 단층 퍼셉트론이기 때문에 초반에 주어지는 loss는 무조건 1로 시작
G_output = backprop_postproc(G_loss, aux_pp) # 손실함수값에 단순 차이값 기반 역전파 필요값(공통 편미분값) 준비
# 직접적인 학습이 이뤄지는 부분
backprop_neuralnet(G_output, aux_nn) # 기본벡터 x와 공통 편미분값을 이용해 가중치와 편향값 업데이트
return loss, accuracy # 손실함수값과 정확도 리턴
- 하나의 미니배치를 받아 학습을 수행하는 함수이다. 해당 학습의 손실함수값과 정확도를 리턴한다.
- 앞서 정의한 순전파와 역전파의 과정이 순서대로 진행된다.
def run_test(x, y):
output, _ = forward_neuralnet(x) # 신경망 연산 보조정보(기존행렬 x)는 필요없으므로 _처리
accuracy = eval_accuracy(output, y) # 정확도처리
return accuracy # 정확도 리턴
- 주어진 값에 대해 테스트를 진행하고 정확도를 리턴하는 함수이다.
def eval_accuracy(output, y):
mdiff = np.mean(np.abs((output - y)/y))
return 1 - mdiff
- 1에서 diff 절대값의 평균을 빼줘서 정확도를 리턴하는 함수이다.
반응형
'🛠 기타 > Data & AI' 카테고리의 다른 글
파이썬 토크나이저 - 기초활용 (0) | 2020.07.15 |
---|---|
파이썬 정규표현식 - 문자 내용 바꾸기 (0) | 2020.07.15 |
Pandas 데이터프레임 정렬 (0) | 2020.07.15 |
Pandas 데이터 시각화 카테고리 (0) | 2020.07.14 |
Pandas 데이터 시각화 한글깨짐 (0) | 2020.07.14 |