오류 역전파 알고리듬 구현 및 테스트

이카루스·2024년 1월 9일
0

읽을거리

목록 보기
26/29
post-thumbnail

사용한 데이터 : Iris 데이터

Iris 데이터(Iris 데이터는 train 데이터와 test 데이터로 구성되어 있으며 하나의 데이터는 4개의 요소(component, feature)로 구성되어 있으며 train 데이터와 test데이터는 각각 75개의 데이터로 구성되어 있습니다. 클래스의 개수는 3개이며(1~25줄, 26~50줄, 51~75줄) 각 클래스는 25개의 데이터로 구성되어 있습니다.!

들어가기 전에

오류 역전파란?

신경망 개요

  1. 신경망 구조: 신경망을 각각 노드(뉴런)를 포함하는 일련의 레이어로 상상해 보세요. 이러한 노드는 레이어를 통해 서로 연결됩니다. 정보는 입력 계층에서 중간 계층(숨겨진 계층)을 거쳐 최종적으로 출력 계층으로 흐릅니다.
  2. 초기 설정: 처음에는 노드 간의 각 연결(가중치)에 임의의 값이 할당됩니다. 네트워크는 출력을 정확하게 예측하는 방법을 아직 모릅니다.

역전파 작동 방식

1. 포워드 패스:

  1. 입력 처리: 네트워크는 입력을 취하고, 각 노드는 이 입력에 가중치를 곱하고 편향(해당되는 경우)을 추가하여 처리합니다.
  2. 활성화 함수: 각 노드는 이 값에 활성화 함수를 적용합니다. 일반적인 선택은 값을 0과 1 사이의 범위로 압축하는 시그모이드 함수입니다.
    출력 생성: 이 프로세스는 최종 출력이 생성될 때까지 레이어별로 계속됩니다.

2. 계산 오류:

오류는 출력에서 ​​계산됩니다. 이 오류는 본질적으로 네트워크가 예측한 것과 실제 예상 출력(목표) 간의 차이입니다.

3.역방향 전달(역전파):

오류 전파: 핵심 단계는 이 오류를 가져와 네트워크를 통해 역방향으로 전파하는 것입니다. 여기서 알고리즘은 이 오류를 최소화하기 위해 연결의 가중치를 조정합니다.
부분 미분/기울기 계산: 각 가중치에 대해 해당 가중치가 오류에 얼마나 기여했는지 계산합니다. 여기에는 미적분학, 특히 각 가중치에 대한 오류의 편도함수 계산이 포함됩니다(이는 가중치의 작은 변화가 오류를 얼마나 줄이거나 늘리는지 알려줍니다).
가중치 업데이트: 일반적으로 현재 가중치 값에서 이 파생물의 일부를 빼서 가중치가 업데이트됩니다. 이 비율은 학습률이라는 매개변수에 의해 결정됩니다.

4.반복:

이 프로세스(정방향 패스, 오류 계산, 역방향 패스)가 여러 번(에포크) 반복됩니다. 각 반복은 전체 오류를 줄이는 것을 목표로 합니다.
델타 규칙(역전파의 핵심 개념)
'델타 규칙'은 역전파의 핵심입니다. 오차를 기준으로 각 가중치에 필요한 변화량을 계산하는 방식입니다. 계산 시 활성화 함수(시그모이드 함수와 같은)의 미분을 사용합니다.

역전파가 효과적인 이유

  1. 효율적인 학습: 오류를 기준으로 가중치를 조정함으로써 네트워크는 올바른 출력을 예측하는 능력이 점차 향상됩니다.
  2. 계층별 개선: 각 계층의 가중치는 오류와 네트워크에서 수행하는 역할을 기반으로 조정되므로 복잡한 학습이 가능합니다.

단순화된 비유

누군가에게 피아노 곡 연주를 가르치는 것을 상상해 보십시오. 처음에는 손가락(추)이 임의의 음표를 쳤습니다. 당신(역전파 알고리즘)은 듣고(오류 계산) 손가락을 약간 조정하여 올바른 음을 칠 수 있도록 안내합니다. 시간이 지남에 따라 반복된 연습(시대)을 통해 그들은 곡을 완벽하게 연주합니다.

결론

역전파는 신경망이 실수로부터 학습하여 시간이 지남에 따라 정확도를 향상시킬 수 있는 강력한 알고리즘입니다. 이는 기계 학습 분야에서 중추적인 수학과 반복적 개선의 혼합입니다.

역전파 알고리즘 구현

import numpy as np

# Function to preprocess data with class labels
def preprocess_data_with_labels_corrected(data, num_lines_per_class):
    features = []
    labels = []
    for idx, line in enumerate(data):
        class_label = idx // num_lines_per_class + 1  # Assign class labels based on line number
        values = line.strip().split()
        if len(values) == 4:  # Ensure only valid data lines are processed
            features.append([float(val) for val in values])
            labels.append(class_label)
    return np.array(features), np.array(labels)

# Define the SimpleMLP class
class SimpleMLP:
    def __init__(self, input_size, hidden_size, output_size):
        self.weights_input_to_hidden = np.random.randn(input_size, hidden_size)
        self.weights_hidden_to_output = np.random.randn(hidden_size, output_size)
        self.bias_hidden = np.random.randn(hidden_size)
        self.bias_output = np.random.randn(output_size)

    def relu(self, x):
        return np.maximum(0, x)

    def softmax(self, x):
        print("Softmax input shape:", x.shape)  # Debug statement
        if x.size == 0:
            print("Empty input received in softmax")
            return x  # Return the input as is to avoid error
        exp_x = np.exp(x - np.max(x))
        return exp_x / exp_x.sum(axis=1, keepdims=True)

    def forward(self, x):
        print("Forward input shape:", x.shape)  # Debug statement
        self.hidden = self.relu(np.dot(x, self.weights_input_to_hidden) + self.bias_hidden)
        self.output = self.softmax(np.dot(self.hidden, self.weights_hidden_to_output) + self.bias_output)
        return self.output

    def compute_loss(self, y_pred, y_true):
        m = y_true.shape[0]
        return -np.sum(y_true * np.log(y_pred)) / m

    def backprop(self, X, y_true, y_pred, learning_rate):
        m = y_true.shape[0]
        output_error = y_pred - y_true
        hidden_error = np.dot(output_error, self.weights_hidden_to_output.T) * (self.hidden > 0)

        self.weights_hidden_to_output -= learning_rate * np.dot(self.hidden.T, output_error) / m
        self.bias_output -= learning_rate * np.mean(output_error, axis=0)
        self.weights_input_to_hidden -= learning_rate * np.dot(X.T, hidden_error) / m
        self.bias_hidden -= learning_rate * np.mean(hidden_error, axis=0)

    def fit(self, X, y, epochs, learning_rate):
        y_one_hot = np.zeros((y.size, y.max() + 1))
        y_one_hot[np.arange(y.size), y] = 1

        for epoch in range(epochs):
            y_pred = self.forward(X)
            loss = self.compute_loss(y_pred, y_one_hot)
            self.backprop(X, y_one_hot, y_pred, learning_rate)
            if epoch % 100 == 0:
                print(f"Epoch {epoch}, Loss: {loss}")

# Function to evaluate model accuracy
def evaluate_model(model, X, y):
    y_pred = model.forward(X)
    y_pred_labels = np.argmax(y_pred, axis=1)
    return np.mean(y_pred_labels == y)

def load_and_preprocess_data(file_path, num_lines_per_class):
    with open(file_path, 'r') as file:
        data = file.readlines()
    return preprocess_data_with_labels_corrected(data, num_lines_per_class)

# Load and preprocess data
training_data = r"C:\Users\l0535\OneDrive - koreatech.ac.kr\Python\Lecture\기계학습\중간고사 대체 과제\training.dat"
testing_data = r"C:\Users\l0535\OneDrive - koreatech.ac.kr\Python\Lecture\기계학습\중간고사 대체 과제\testing.dat"
X_train, y_train = load_and_preprocess_data(training_data, 25)
X_test, y_test = load_and_preprocess_data(testing_data, 25)

# Initialize and train the model
input_size = 4
hidden_size = 100
output_size = 3
epochs = 1000
learning_rate = 0.01
model = SimpleMLP(input_size, hidden_size, output_size)
y_train_adj = y_train - 1
model.fit(X_train, y_train_adj, epochs, learning_rate)

# Evaluate the model
y_test_adj = y_test - 1
test_accuracy = evaluate_model(model, X_test, y_test_adj)
print(f"Test Accuracy: {test_accuracy}")

정확도는 조금씩 다를 수 있겠지만, 쓰니는 0.933 도출

함수 설명

preprocess_data_with_labels_correded 함수:

목적: 이 함수는 입력 데이터를 전처리하기 위해 설계되었습니다. 기능과 해당 레이블을 구분합니다.

구현 세부정보:

두 개의 인수를 사용합니다. 데이터 파일의 줄 목록인 'data'와 데이터세트의 클래스당 데이터 줄 수를 나타내는 'num_lines_per_class'입니다.
이 함수는 각 라인을 반복하고 라인 번호를 기반으로 클래스 레이블을 할당한 다음 각 라인을 개별 값으로 분할합니다.
features 목록에 값을 추가하기 전에 각 줄에 올바른 수의 값(이 경우 4개)이 있는지 확인합니다.
이 함수는 기능 배열과 라벨 배열을 포함하는 튜플을 반환합니다.

SimpleMLP 클래스:

목적: 이 클래스는 피드포워드 인공 신경망의 일종인 간단한 MLP(Multi-Layer Perceptron)를 정의합니다.

주요 방법:

init: 입력-숨김 레이어와 숨김-출력 레이어 모두에 대해 임의의 가중치와 편향을 사용하여 모델을 초기화합니다. 이러한 레이어의 크기는 'input_size', 'hidden_size' 및 'output_size'로 정의됩니다.

relu: ReLU(Rectified Linear Unit) 활성화 함수를 구현합니다.

softmax: 일반적으로 다중 클래스 분류를 위한 출력 레이어에 사용되는 Softmax 함수를 구현합니다.

forward: 숨겨진 레이어에 ReLU를 적용하고 출력 레이어에 Softmax를 적용하여 네트워크를 통한 순방향 전달을 정의합니다.

compute_loss: 손실 함수를 사용하여 손실을 계산합니다(제공된 코드에는 완전히 표시되지 않음).

backprop: 손실 함수의 기울기를 계산하여 네트워크의 가중치를 업데이트하는 방법인 역전파를 구현합니다.

fit: 역전파 알고리즘을 사용하여 지정된 epoch 수 동안 제공된 교육 데이터에 대한 모델을 교육합니다.

evaluate_model 함수:

목적: 이 함수는 훈련된 모델의 성능을 평가하는 데 사용됩니다.

구현 세부정보:
모델, 특징(X), 라벨(y)을 입력으로 사용합니다.
함수는 모델을 통해 순방향 전달을 수행하여 예측된 레이블을 계산한 다음 이러한 예측을 실제 레이블과 비교합니다.
정확도는 예측 라벨과 실제 라벨을 비교한 평균으로 계산되며, 이는 올바르게 예측된 인스턴스의 비율을 나타냅니다.

모델 훈련 및 평가 코드:
이 코드 섹션에서는 위의 함수와 클래스를 실제로 적용하는 방법을 다룹니다. 여기에는 데이터 로드 및 전처리, MLP 모델 초기화, 훈련 데이터로 모델 훈련, 최종적으로 테스트 데이터에 대한 성능 평가가 포함됩니다.

profile
Der Schmerz, der mich nicht töten kann, macht mich nur stärker (나를 죽이지 못하는 고통은 나를 더 강하게 만든다)

0개의 댓글