우리는 다중 분류 문제를 예측하는 단층 신경망을 만들어 볼 것이다.
다중 분류 문제에서는 불량철판문제 데이터를 가지고 불량 철판의 종류를 예측하는 신경망을 구축해 볼 예정이다.
데이터 셋은 Kaggle에 있는 불량 철판 문제 에서 받아서 사용 할 수 있다.
✔︎ 데이터 살펴보기
import numpy as np
import csv
import pandas as pd
data = pd.read_csv('data/faults.csv')
독립 변수는 철판의 불량 상태를 의미하는 지표인 총 27가지의 값이 주어지며,
종속 변수로는 one − hot vector 로 표현된 7가지의 불량 종류가 존재하는 1941개의 데이터셋 이다.
📍 다중 단층 신경망을 만들기 위한 설계도
1. 모듈 불러오기
import numpy as np
import csv
# 실험 결과를 재현하기 위해 난수 발생패턴을 고정시키는 np.random.seed()함수값을 설정
np.random.seed(333)
2. 하이퍼 파라미터 설정
- learning rate 학습률
- 정규분포 난숫값 [ 평균 / 표준편차 ]
RND_MEAN = 0 # 평균
RND_STD = 0.003 # 표준편차
Learning_rate = 0.003 # 학습률
3. 메인함수 정의
def multi_class_exec(epoch_count=10, mb_size=10, report=1,train_rate=0.75):
multi_load_dataset()
init_model()
train_and_test(epoch_count, mb_size, report,train_rate)
메인 함수 multi_classification_exec() 함수를 실행하게 되면
#1 데이터를 불러들이는 multi_load_dataset() 함수,
#2 가중치와 편향을 초기화 해주는 init_model() 함수,
#3 학습 및 신경망 성능 테스트를 위한 train_and_test()함수
이렇게 세가지 함수가 차례로 실행되며, 신경망 모델을 생성하고 학습 전체 과정을 일괄 처리한다.
데이터를 관리하는 함수인 multi_load_dataset()를 제외하고는 모두 이전과 같은 형식으로 작성되었다.
4. 데이터를 불러오는 multi_load_dataset() 정의
# 데이터 받기 > 버퍼로 옮겨주기 > np.asarray()로 감싸주기
def multi_load_dataset():
with open('/content/faults.csv') as csvfile:
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 = 27, 7 # 독립변수, 종속변수
data = np.asarray(rows, dtype='float32') # 배열 구조로 변환하는 과정
데이터를 자세히 살펴보면 오직 실수로된 데이터만 존재한다.
이미 원-핫벡터로 변환되어 있기 때문에 원-핫 벡터 표현과정은 생략할 수 있고, 독립변수 input_cnt과 종속변수 output_cnt은 각 27, 7의 값을 가진다.
리스트 구조로 되어있는 변수를 asarray 배열 구조로 변환하게 되면 numpy에서 제공하는 다양한 산술 연산과정을 효율적으로 사용할 수 있게된다.
5. 파라미터 초기화 함수 init_model() 정의
def init_model():
global weight, bias, input_cnt, output_cnt
weight = np.random.normal(RND_MEAN, RND_STD,[input_cnt, output_cnt])
# normal 메서드 : (mean, sd, shape)
bias = np.zeros([output_cnt])
# weight 가중치 행렬은 [8,1] , bias 편향 벡터는 [1]형태
6. 학습을 수행 run_train() 정의
def run_train(x,y): #신경망 연산
output, aux_nn = forward_neuralnet(x)
loss, aux_pp = forward_postproc(output,y)
accuracy = eval_accuracy(output,y)
return loss, accuracy
학습을 위해서는 제일 먼저 신경망 연산을 수행하는 forward_neuralnet() 가 동작한다.
6.1 순전파 forward_neuralnet()
def forward_neuralnet(x):
global weight, bias
output = np.matmul(x, weight) + bias
return output, x
이 함수를 통해서 output을 연산하고 값을 돌려받는다. 그리고 이러한 output을 바탕으로 손실값을 계산하는 forward_postproc()가 동작하게 된다. 이 함수에서는 손실을 계산하는 소프트맥스 교차 엔트로피를 연산하는 수식이 필요하다. 그리고 이렇게 구한 값을 바탕으로 np.mean() 를 통해 최종 손실을 도출해낸다.
6.1 역전파 forward_postproc()
def forward_postproc(output, y):
entropy = softmax_cross_entropy_with_logits(y, output)
loss = np.mean(entropy)
return loss, [y, output, entropy] # [y, output, entropy]는 역전파 때 활용(받은것을 그대로 반환)
소프트맥스 교차 엔트로피를 계산할 수 있는 softmax_cross_entropy_with_logits() 가 사용되는데, 이 함수를 수행하기 위해서는 softmax()를 통과해 얻어진 신경망 연산에 대한 확률분포가 필요하다.
7. softmax() 함수 정의
def softmax(x):
#output 값 중 최댓값 / axis = 1 ->'열'== '후보값'들 기준하여 가장 큰 값을
#추출하기 위해 열 기준으로 추출하는 axis = 1을 넣어줌
#max_elem의 shape은 (10,)
max_elem = np.max(x, axis = 1) # x_k값을 찾는다.
#i번째 x와 가장 큰 x값을 빼주는 과정에서
#diff = output - max_elem 이렇게는 연산이 되지 않는다. (10,7) (10,) > (1,10) 행벡터 X
#그래서 x(output)을 행렬전환하여 연산을 수행한 다음 다시 원상 복귀
diff = (x.transpose() - max_elem).transpose()
#diff에 np.exp()를 씌워 분자의 수식을 완성
exp = np.exp(diff)
#분모는 이러한 exp를 모두 합산
sum_exp = np.sum(exp, axis=1)
#앞서 마찬가지로 분자와 분모의 나눗셈을 위해 행렬전환 후 연산. 그리고 다시 원상복귀를 위해 행렬전환
probs = (exp.transpose() / sum_exp).transpose() #softmax를 통과한 확률
return probs
7.1 소프트맥스 교차 엔트로피 개선식 정의
def softmax_cross_entropy_with_logits(labels, logits):
probs = softmax(logits)
#기존 확률값에 아주 작은 양수를 더해 log함수의 폭주 방지
return -np.sum(labels * np.log(probs + 1.0e-10), axis=1)
8. 평가지표 함수 eval_accuracy() 정의
def eval_accuracy(output, y):
estimate = np.greater(output, 0)
answer = np.greater(y, 0.5)
correct = np.equal(estimate, answer)
return np.mean(correct)
np.argmax(axis = 1) 를 통해 신경망의 연산결과 중 가장 높은 값을 가진 위치, 데이터 마다의 종속변수 중 가장 높은 값을 가진 위치를 각각 출력하여 estimate, answer 라는 변수에 저장한다.
신경망의 연산결과를 통해서도 어느 후보가 더 높은 확률값을 갖고 있는지 추론이 가능하기에 이렇게 구한 결과를 비교하는 np.equal()를 활용하여 정확도를 구해낼 수 있다.
9. 역전파를 넣은 run_train() 정의
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 = y, output, entropy)
G_output = backprop_postproc(aux_pp)
#aux_nn은 x.transpose()를 위해
backprop_neuralnet(G_output, aux_nn)
return loss, accuracy
9.1 역전파 과정
def backprop_neuralnet(G_output, x):
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
def backprop_postproc(aux_pp):
y, output, entropy = aux_pp
G_loss = 1.0
g_loss_entropy = 1.0/np.prod(entropy.shape)
g_entropy_output = softmax_cross_entropy_with_logits_derv(y,output)
G_entropy = g_loss_entropy * G_loss
G_output = g_entropy_output * G_entropy
return G_output
9.2 소프트 맥스 크로스 엔트로피 편미분
def softmax_cross_entropy_with_logits_derv(labels,logits):
return softmax(logits) - labels
10. 학습 및 평가 함수 train_and_test() 정의
def train_and_test(epoch_count, mb_size, report, train_rate):
#1.데이터 섞어주는 기능(mb_size값을 활용해 한 에폭에 필요한 미니배치 스텝수를 step_count에 저장)
#학습데이터와 평가 데이터를 섞어주는 기능 수행
step_count = arrange_data(mb_size, train_rate) #2. 테스트 데이터를 따로 저장하는 함수
test_x, test_y = get_test_data()
# 학습을 수행하는 단계 (에폭 수 만큼)
for epoch in range(epoch_count):
#학습을 수행하는 만큼 손실값과 정확도가 생성되니 이것들을 저장해줄 빈 리스트 생성
losses, accs = [], []
#step_count(한번의 에폭에 필요한 미니배치 스텝 수)를 활용해 미니배치 처리를 수행
#step_count가 다 돌면 1에폭 수행
for n in range(step_count):
#3. 학습을 수행하기 위한 학습 데이터 분할(1에폭에 대한 n = 미니배치 스텝 수)
train_x, train_y = get_train_data(mb_size, n)
#4. 학습 수행
loss, acc = run_train(train_x, train_y)
losses.append(loss)
accs.append(acc)
#에폭이 돌면서 학습이 수행됨. 즉 모델 학습 #에폭값이 report로 설정한 수에 해당될 때 마다 중간평가 수행
if report > 0 and (epoch + 1) % report == 0:
#5.테스트 데이터를 활용한 테스트 수행 - 중간 평가
#에폭만큼 학습이 수행된 모델을 활용하여 중간평가
acc = run_test(test_x, test_y)
print(
f"Epoch{epoch+1}: Train-loss = {np.mean(losses):5.3f}, accuracy = {np.mean(accs):5.3f} / Test={acc:5.3f}"
)
#모든 학습 완료 후 / 최종 모델을 활용한 테스트 수행
final_acc = run_test(test_x, test_y)
print(f'\n 최종 테스트 : final accuracy = {final_acc:5.3f}')
◼ train_and_test() 안에 정의된 함수
- arrange_data()
- get_train_data()
- get_test_data()
- run_train()
- run_test()
def arrange_data(mb_size, train_rate):
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] * train_rate) // mb_size
test_begin_idx = step_count * mb_size
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:
np.random.shuffle(shuffle_map[:test_begin_idx])
train_data = data[shuffle_map[mb_size * nth:mb_size * (nth + 1)]]
return train_data[:, :-output_cnt], train_data[:, -output_cnt:]
11. 학습 결과를 테스트 하는 run_test() 정의
def run_test(test_x,test_y):
output,_ = forward_neuralnet(test_x)
accuracy = eval_accuracy(output,test_y)
return accuracy
순전파 과정인 forward_neuralnet()를 진행하여 얻은 output을 평가함수인 eval_accuracy()의 인자로 활용하여 정확도를 계산해 주면된다. 이렇게 run_test()를 통해 얻어진 결과와 더불어 앞서 학습을 통해 얻어진 정확도 및 손실은 print()를 통해 출력된다.
12. 실행하기
Learning_rate = 0.0001
multi_class_exec(epoch_count=1000,mb_size = 100, report=100, train_rate=0.7)
하이퍼파라미터를 수정했음에도 불구하고 오히려 더 낮은 결과값을 확인할 수 있다. 이러한 낮은 품질의 원인은 여러가지가 있겠지만 그 중에서도 데이터의 수에 대해 이야기 할 필요가 있다. 지금 신경망의 학습을 위해 사용되어진 데이터의 수는 총 데이터의 70%인 약 1350개 이다. 그리 많은 숫자라고는 할 수 없지만 그래도 적은 숫자는 아니다. 그러나 우리가 가진 27가지의 변수, 즉 27개의 벡터 공간을 통해 종속변수의 크기인 총 7개의 구역으로 적절하게 분할할 수 있어야 한다. 그렇기 위해서는 1350개의 데이터는 그리 많은 숫자가 아니라는 것을 정확도와 손실값이 이야기 해주고 있다.
또 한가지 문제점은 단층 퍼셉트론의 구조 자체에 대한 점이다. 단층 퍼셉트론은 지나치게 단순한 구조이기에 복잡한 문제를 풀기 위함에 있어 적합하지 않을 수 있다.
'DL(Deep-Learning) > 개념' 카테고리의 다른 글
[실습] numpy로 만드는 다층 신경망 (0) | 2022.01.29 |
---|---|
[실습] numpy로 만드는 단층 신경망 - 다중 분류1 (0) | 2022.01.29 |
[실습] numpy로 만드는 단층 신경망 - 이진 분류 2 (0) | 2022.01.29 |
[실습] numpy로 만드는 단층 신경망 - 이진 분류 1 (0) | 2022.01.29 |
train/test/validation 나누기 - splitfolders (0) | 2022.01.29 |