SKN/Remind

sk네트웍스 family AI 캠프 11기 3월 3주차 회고록

claovy☘️ 2025. 3. 24. 02:28

7주차 회고기간 : 2025.03.17월~2025.03.21금

이번 주차는 딥러닝의 기본이 되는 퍼셉트론~손실함수 원리에 대해 배웠다.

 

1. 퍼셉트론

: 퍼셉트론은 생물학적 뉴런을 모방한 간단한 인공지능 모델

AND/NAND/OR 게이트

  • AND 게이트
    • 두 입력이 모두 1일 때만 출력이 1이 된다. 퍼셉트론 모델에서는 가중치와 편향 값을 설정하여 이를 구현할 수 있다.
  • NAND 게이트
    • AND 게이트의 출력을 뒤집은 형태로, 두 입력이 모두 1일 때만 출력이 0이 된다.
  • OR 게이트
    • 두 입력 중 하나라도 1이면 출력이 1이 된다.
test_cases = [(0,0),(0,1),(1,0),(1,1)]


class Perceptron:
    def __init__(self, weights, bias):
        self.weights = weights
        self.bias = bias

    def activate(self, x) : # 퍼셉트론 연산 
        return 1 if np.dot(self.weights, x) + self.bias > 0 else 0
AND_gate = Perceptron(weights=[0.5, 0.5], bias=-0.7)

for test in test_cases:
    print(f'input : {test} | output : {AND_gate.activate(test)}')
    
[출력]
input : (0, 0) | output : 0
input : (0, 1) | output : 0
input : (1, 0) | output : 0
input : (1, 1) | output : 1
OR_gate = Perceptron(weights=[0.5, 0.5], bias=-0.2)

for test in test_cases:
    print(f'input : {test} | output : {OR_gate.activate(test)}')
    
[출력]
input : (0, 0) | output : 0
input : (0, 1) | output : 1
input : (1, 0) | output : 1
input : (1, 1) | output : 1
NAND_gate = Perceptron(weights=[-0.5, -0.5], bias=0.7)

for test in test_cases:
    print(f'input : {test} | output : {NAND_gate.activate(test)}')
    
[출력]
input : (0, 0) | output : 1
input : (0, 1) | output : 1
input : (1, 0) | output : 1
input : (1, 1) | output : 0

 

퍼셉트론의 한계(XOR 게이트 구현 못함)

XOR (Exclusive OR, 배타적 논리합)
두 개의 비트가 서로 다를 때 1, 같을 때 0을 반환하는 논리 연산이다.


단층 퍼셉트론만으로는 XOR와 같은 비선형 문제를 해결할 수 없다. 이를 극복하기 위해 다층 구조가 필요하다 

 

다중 퍼셉트론의 등장(XOR 문제해결) 

  • 다층 퍼셉트론(Multi-Layer Preceptron, MLP)은 여러 개의 퍼셉트론을 연결하여 비선형 문제를 해결한다. 입력층, 은닉층, 출력층을 가지고 있으며 은닉층에서 비선형 활성화 함수를 사용하여 복잡한 패턴을 학습할 수 있다.
  • 각 층은 활성화 함수를 사용하여 출력을 계산한다.

X = np.array([[0,0],[0,1],[1,0],[1,1]])
y = np.array([0, 1, 1, 0])

hidden_layers = [(1,), (2, ), (4, ), (8, )]

for config in hidden_layers:
    mlp = MLPClassifier(
        hidden_layer_sizes=(4,4),	# 은닉층 개수
        activation='tanh',		# 활성화 함수 종류
        solver='lbfgs',         # 가중치 업데이트 방식(최적화 알고리즘)
        max_iter=5000,			# 반복값 (epch 값)
        random_state=42         # 가중치 초기화값 고정 
    )

    mlp.fit(X,y)
    pred = mlp.predict(X)
    print(f'은닉층 구조 {config} -> XOR 예측 결과 :  {pred}')

2. 활성화 함수

01. Sigmoid 함수

def sigmoid(x):
    return 1 / (1 + np.exp(-x))
  • 출력범위를 0 , 1 사이로 압축하는 비선형 함수이다. 주로 이진 분류에서 사용된다. 
  • 시그모이드 함수는 경사하강법을 사용하여 학습할 때 기울기가 매우 작은 값으로 변할 수 있어 학습이 느려지는 기울기 소실 문제가 발생할 수 있다.

02. 계단함수

def step(x):
    return np.where(x >= 0, 1, 0)
  • 입력값이 0(일정 임계값)보다 크면 1, 작거나 같으면 0을 반환하는 함수이다. 퍼셉트론에서 출력층으로 사용된다.
  • 비선형 문제를 해결할 수 있지만, 미분이 불가능하여 신경망 학습에 사용하기 어렵다.

 

03. ReLU 함수

 

def relu(x):
    return np.maximum(0, x)
  • 출력 범위 : (0, ∞ ) 
  • 음수 입력에 대해 0을 출력하여 계산이 간단하고 학습 속도가 빠름
  • 죽은 뉴런(Dying ReLU) 문제 발생 가능

 

 

04. Leaky ReLU 함수

def leaky_relu(x, alpha=0.01):
    return np.where(x >= 0, x, alpha * x)
  • 출력범위 : (- ∞, ∞)
  • ReLU의 죽은 뉴런 문제를 해결하기 위해 음수 입력에 작은 기울기 α 를 적용 ( α 는 일반적으로 0.01 사용)
  • alpha값을 올려주면 음수 영역의 기울기를 올려줌

05. Tanh (Hypyerbolic Tangent) 함수 

def tanh(x):
    # return (np.exp(x) - np.exp(-x) / np.exp(x) + np.exp(-x))
    return np.tanh(x)
  • 출력범위를 (-1, 1) 로 설정한 함수 
  • 시그모이드를 변형한 함수로써, 시그모이드보다 중심이 0에 가까워 더 빠른 학습 진행이 가능 (균형적으로 학습) 
  • 기울기 소실 문제 발생 가능

3. 다차원 텐서

💡다차원 배열은 여러 개의 차원을 가지는 배열로, 2차원 이상의 배열을 의미한다.
주로 행렬 또는 텐서라는 용어를 사용

 

다차원 텐서로 이미지를 구현해보자 !

import matplotlib.pyplot as plt

# 이미지 생성
batch_size = 5
channels = 3
height = 32
width = 32

images = np.random.rand(batch_size, channels, height, width)

bright_images = np.clip(images + 0.2, 0, 1) # 0.2를 더해 밝기 조정 / 0~1 사이로 밝기에 한계두기

gray_images = np.mean(images, axis=1)

# PyTorch 프레임워크에서는 이미지 데이터를 (채널, 높이, 너비) 순서로 저장
# matplotlib.pyplot.imshow()는 (높이, 너비, 채널) 순서로 기대
# 따라서 transpose()를 이용해 축의 순서를 재배열
images_for_display = images[0].transpose(1, 2, 0)
bright_images_display = bright_images[0].transpose(1, 2, 0)

plt.subplot(1, 3, 1)
plt.imshow(images_for_display)
plt.axis('off')

plt.subplot(1, 3, 2)
plt.imshow(bright_images_display)
plt.axis('off')

plt.subplot(1, 3, 3)
plt.imshow(gray_images[0], cmap='gray')
plt.axis('off')

⚠️ 몰랐던 개념

  • np.clip
    • 배열의 값을 제한시키는 데에 사용
    • np.clip(arr,  a_min, a_max) # arr 배열에서 a_min보다 작은 값은 a_min으로, a_max보다 큰 값은 a_max로 설정

4. 출력층 설계

  • 출력층에서는 주로 항등항수, 시그모이드 함수, 소프트맥스 함수 세 가지가 사용된다
  • 세 함수 모두 미분 가능하여 역전파 알고리즘에 적합하다.

01. 항등함수

  • 항등 함수(Identity Function)는 회귀 문제에서 입력 값을 그대로 출력할 때 적합하다

02. 시그모이드 함수

  • 시그모이드 함수 (Sigmoid Function)는 이진 분류나 다중 레이블 분류 문제에서 주로 사용되는 활성화 함수이다.
  • 이진 분류 문제에서 출력층에 시그모이드 함수를 적용하면, 각 뉴런의 출력이 특정 클래스에 속할 확률로 해석되며, 다중 레이블 분류에서도 각 레이블에 대한 존재 여부를 예측할 수 있다.

03. 소프트맥스 함수

  • 소프트맥스(Softmax) 함수는 다중분류 문제에서 출력층에서 사용되는 활성화 함수이다.

 

⭐ 소프트맥스 함수 구현 시 주의점! 

  1. 소프트맥스 함수는 지수 연산을 포함하기 때문에 오버플로우(Overflow) 문제가 발생할 수 있다.
  2. 이를 방지하기 위해 입력에서 최대값을 빼는 기법을 적용한다.
소프트맥스의 오버플로우 문제

지수 함수 의 계산 시, 지수값이 너무 커져 계산이 불가하거나 무한대로 커지는 현상

소프트맥스의 오버플로우 문제는 소프트맥스 함수의 계산이 정확하지 않거나 나머지 클래스의 확률 계산에 영향을 미칠 수 있다

소프트맥스의 오버플로우 방지 (입력신호 중 최대값을 빼준다)

최대값을 빼지 않은 소프트맥스 함수와, 오버플로우를 방지한 소프트맥스 함수를 각각 구현해보자 

import numpy as np

def softmax(x):
    exp_x = np.exp(x)
    return exp_x / np.sum(exp_x)

def stable_softmax(x):
    exp_x = np.exp(x - np.max(x))
    return exp_x / np.sum(exp_x)


x = np.array([1000, 1001, 1002])

print(softmax(x))
print(stable_softmax(x))

[출력]
[nan nan nan]
[0.09003057 0.24472847 0.66524096]

출력결과 오버플로우가 방지된 값을 비교해볼 수 있다

 

출력층의 뉴런 수

  • 출력층의 뉴런 수는 해결하려는 문제에 따라 결정된다.
    • 회귀 문제: 1개 (연속된 값 출력)
    • 이진 분류: 1개 또는 2개 (시그모이드 사용 시 1개, 소프트맥스 사용 시 2개)
    • 다중 클래스 분류: 클래스 개수와 동일한 뉴런 개수 (소프트맥스 사용)

다중 클래스 분류 모델 구현

5개의 입력값으로 3개의 클래스를 예측하는 모델을 구현해보자
import torch
import torch.nn as nn
import torch.optim as optim

class SimpleMultiClassModel(nn.Module): # step1. 클래스는 pytorch의 nn.Module을 상속받는다
    def __init__(self):
        super(SimpleMultiClassModel, self).__init__()
        self.fc = nn.Linear(5, 3)

    def forward(self, x): # 순전파 메서드 
        return self.fc(x) # step2. fc(x)는 선형 변환을 통해 입력을 3개의 출력 클래스로 변환한다
    
model = SimpleMultiClassModel()
criterion = nn.CrossEntropyLoss() # 교차 엔트로피 손실 함수 이용 (주로 다중클래스 분류에서 사용)
optimizer = optim.Adam(model.parameters(), lr=0.01) # adam 옵티마이저를 사용한 파라미터 업데이트

# step 3. 데이터 생성
inputs = torch.randn(4, 5)
labels = torch.tensor([0, 2, 1, 0])

for _ in range(10):
    preds = model(inputs)          # step4. 순전파에 입력값을 넣고 예측값 계산
    loss = criterion(preds, labels) # step 5. 손실 계산
    print(loss.item())

    optimizer.zero_grad()   # step 6. 이전 단계에서 계산된 기울기를 0으로 초기화 (기울기 계속 증가를 막음)
    loss.backward()         # step 7. 손실에 대한 역전파 수행 (파라미터에 대한 기울기 계산)
    optimizer.step()        # step 8. 계산된 기울기를 사용하여 옵티마이저가 모델의 파라미터 업데이트

파이토치의 nn.Linear에 대한 참고 자료 

 

[PyTorch] nn.Linear에 대한 질문

안녕하세요, 오늘은 PyTorch를 쓰다보면 굉장히 많이 접하게 되는 nn.Linear 함수에 대해 설명하려고 합니다. nn.Linear란 먼저, 논문을 읽거나 기타 자료를 볼 때 nn.Linear는 Fully Connected Layer(FC) 또는 Dense

thecho7.tistory.com


5. 손실함수

 💡손실함수는 신경망 학습에서 예측값 - 실제값을 측정하는 함수이다
모델 학습 방향을 결정하는 중요한 요소이며, 가중치를 조정하는 기준이 된다.

회귀문제 분류문제 강화학습
평균제곱오차(MSE)
평균절대오차(MAE)
Huber 손실
교차 엔트로피 손실(CEE)
음성 로그 가능도(NLL)
Hinge Loss(SVM)
정책 그래디언트 손실
Huber 손실
# MSE
def mean_squared_error(y_true, y_pred):
    return np.mean((y_true - y_pred) ** 2)

# MAE
def mean_absolute_error(y_true, y_pred):
    return np.mean(np.abs(y_true - y_pred))
    
# Huber loss
def huber_loss(y_true, y_pred, delta=1.0):
    error = y_true - y_pred
    is_small_error = np.abs(error) <= delta # np의 잔차가 delta보다 작거나 같으면 mse, 그렇지 않으면 mae로로 판별 
    squared_loss = 0.5 * error**2
    linear_loss = delta * (np.abs(error) - 0.5 * delta)
    return np.mean(np.where(is_small_error, squared_loss, linear_loss))

 

+ reference

https://blog.naver.com/koreadeep/222600824716

 

딥러닝 기초 - 순전파와 역전파

앞선 포스팅에선 모델을 학습 시키는 방법으로 미니 배치 확률적 경사 강하법(stochastic gradient descent...

blog.naver.com


6. 수치미분 vs 편미분

  • 수치미분
    • 함수의 변화량을 근사적으로 구하는 방법이다.
    • 미분값을 구하는 것이 어렵거나 불가능한 경우 사용된다
    • 하지만 미분근사값 h가 너무 크거나 작으면 오차가 발생한다
  • 편미분
    • 다변수 함수에서 (다른 변수는 상수로 고정시킨 뒤) 한 변수에 대해서만 미분하는 것이다
    • ex) x 이외에도 y, z 등 여러 변수들이 있을 수 있는데, 이 중에서 x에 대해서만 미분한다면 y, z 등 다른 변수들은 고정한 채 x만을 변화시켰을 때 함수값이 얼마나 변하는지를 측정한 값
    • 수치미분의 단점을 보완한다

=> 다변수 함수 f(x, y) 에 대해 편미분을 벡터 형태로 나타낸 것을 기울기 벡터(Gradient Vector)라고 한다.

 


7. 기울기

01. sigmoid의 기울기 소실 문제

import numpy as np
import matplotlib.pyplot as plt

def sigmoid(x):
    return 1 / (1 + np.exp(-x))

np.random.seed(0)
weight = np.random.randn(10, 10) * 0.01
x = np.random.randn(10,1)

for i in range(1, 11):
    x = sigmoid(np.dot(weight, x)) # x값과 가중치 내적
    print(f'{i}번째 층 출력 평균 : {np.mean(x)}')
    
    
[출력]
1번째 층 출력 평균 : 0.4923981391662232
2번째 층 출력 평균 : 0.5007433168636295
3번째 층 출력 평균 : 0.5007308165030734
4번째 층 출력 평균 : 0.5007305713032845
5번째 층 출력 평균 : 0.5007305725302615
6번째 층 출력 평균 : 0.5007305725406197
7번째 층 출력 평균 : 0.5007305725405601
8번째 층 출력 평균 : 0.5007305725405591
9번째 층 출력 평균 : 0.5007305725405591
10번째 층 출력 평균 : 0.5007305725405591

sigmoid 함수를 여러번 적용하면 0또는 1에 가까운 값으로 수렴하게 되어 변화가 일어나지 않는다.

이러한 문제를 기울기 소실(vanishing gradient) 이라고  한다.

따라서 다양한 활성화 함수를 활용해야 한다.

 

 

02. 잔차연결 효과

잔차연결(skip connection)

일반적인 신경망 모델 학습 시에는 모델 층이 깊어질수록 학습 결과가 좋다.

하지만 층이 너무 깊거나 노드 수가 너무 크면 이전 층에 대한 정보 손실이 발생하고 이는 가중치가 잘못 갱신되는 문제를 초래한다.

따라서 이전 층의 정보를 이용하여 정보를 연결하는 잔차 연결을 적용한다.

즉, 네트워크의 입력과 출력이 더해진 것이 다음 층의 입력으로 사용되는 것이다

# 두 개의 선형 레이어를 가진 신경망
class SimpleNN(nn.Module):
    def __init__(self):
        super(SimpleNN, self).__init__()
        self.layer1 = nn.Linear(10,10)
        self.layer2 = nn.Linear(10, 10)

    def forward(self, x):
        return self.layer2(torch.relu(self.layer1(x)))

# 단일 선형 레이어를 가진 신경망
class ResidualNN(nn.Module):
    def __init__(self):
        super(ResidualNN, self).__init__()
        self.layer = nn.Linear(10,10)

    def forward(self, x):
        return torch.relu(self.layer(x))
        
# 잔차 연결을 포함하는 3개의 레이어를 가진 신경망 
class DeeperResidualNN(nn.Module):
    def __init__(self):
        super(DeeperResidualNN, self).__init__()
        self.layer1 = nn.Linear(10,10)
        self.layer2 = nn.Linear(10,10)
        self.layer3 = nn.Linear(10,10)

    def forward(self, x):
        residual = x
        x = torch.relu(self.layer1(x)) + residual
        residual = x
        x = torch.relu(self.layer2(x)) + residual
        x = self.layer3(x) + residual
        return x

 

신경망을 변경하고 손실함수를 적용하여 학습시킨 결과, 잔차  연결 신경망의 기울기 크기가 가장 작았다.

물론 항상 잔차 연결이 최적의 손실값을 가지는 것은 아니므로 노드 수나 은닉에 따라 조정해야 한다.


💡 Keep

머신러닝 프로젝트와 학습을 병행하는 일이 생각보다 쉽지 않은데 그래도 할거는 꿋꿋하게 해나가고 있다는 점 칭찬...⭐

프로젝트랑 학습이랑 병행하니까 힘든 점도 있지만 오히려 좋은 점은 욕심이 생긴다는 점? 주제를 찾고 좋은 팀원들이랑 의견을 맞춰나가는 것 자체도 즐겁고, 평소에 관심 있던 이슈를 구현해본다는 점이 재밌는 일인것 같다 !

 

⚠️ Problem

일단 딥러닝 강의를 들어오면서부터 개념적으로 헷갈리는게 너무 많다.... 특히 편미분이랑 수치미분 계산식 적용 이런 수학적인 것들.. 개강 초반에 예복습 스터디에 안들었는데 프로젝트하랴 코테하랴 복습하랴 정신이 없어지고 있다 ㅜㅜㅜ그래도 한달 반 조금 넘는 시간동안 기대 이상으로 지식을 많이 얻었다는 느낌이다. 학교때 배웠던 개념도 종강과 동시에 머릿속에서 없어지는 편이었는데 지금은 반강제적으로(?) 꽉 잡고 있을 수 있는 환경이 좋다ㅋㅋㅋ 제때제때 복습하고 체력관리 잘하도록 하자

 

🔥 Try

코드 한번씩 생각하고 다시 돌려보자

그리고 3주에 한번씩 감기걸리는 꼴인데 체력관리도 능력이라고 한다..알긴아는데 ㅠㅡㅠ 

몸관리 잘해서 앞으로 일정에 지장이 없었으면 좋겠다 내일도 월요팅하자잣