지난 글에서 선형 레이어를 구현했습니다. 이번 글에서는 신경망에서 자주 쓰는 활성화 함수를 알아보고 구현해 보겠습니다. 👍

🔎 활성화 함수

ReLU(Rectified Linear Unit)라고도 불리는 활성화 함수는 입력이 0보다 크면 그대로 통과시키고, 0 이하이면 0으로 바꿉니다.

$$ReLU(x)=max(0,x)$$

신경망에 비선형성을 넣어 복잡한 관계를 학습하는 역할을 합니다. 계산이 간단하고 학습이 잘 되는 편이라 널리 쓰입니다.

활성화 함수 없이 선형층만 여러 개 쌓으면, 층이 아무리 많아도 결국 하나의 선형 변환과 같아요. 반면 ReLU를 중간중간 넣으면 입력과 출력의 관계를 더 복잡하게 표현할 수 있습니다.

⤴️ 미분은 아래와 같습니다.

$$\frac{dy}{dx}=\begin{cases}1&x\gt 0\\0&x\le 0\end{cases}$$

따라서 역전파 때 입력에 대한 기울기는 들어온 기울기에 ReLU의 미분값을 곱한 값입니다.

$$\frac{\partial L}{\partial x}=\frac{\partial L}{\partial y}\cdot ReLU'(x)$$

구현

⚠️ 아래 코드는 오래된 코드일 수 있습니다. 이 곳을 참고해 주세요.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
// relu.hpp
Matrix<T> forward(const Matrix<T>& input) {
    this->_input = input;

    const std::size_t kRow = input.row();
    const std::size_t kCol = input.col();
    Matrix<T> result = input;

    for(std::size_t row=0; row<kRow; row++) {
        for(std::size_t col=0; col<kCol; col++) {
            // ReLU 핵심
            if(result(row, col) < 0) {
                result(row, col) = 0;
            }
        }
    }

    return result;
}

Matrix<T> backward(const Matrix<T>& gradOutput) {
    const std::size_t kRow = gradOutput.row();
    const std::size_t kCol = gradOutput.col();
    Matrix<T> result = gradOutput;

    for(std::size_t row=0; row<kRow; row++) {
        for(std::size_t col=0; col<kCol; col++) {
            if(this->_input(row, col) <= 0) result(row, col) = 0;
        }
    }

    return result;
}

테스트

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
TEST_CASE(AI_RELU_FORWARD_TEST) {
    Yami::AI::Matrix<int> input(2,2);
    for(std::size_t i=0; i<4; i++) {
        input[i] = i+1;
    }
 
    input(0,0) *= -1;
    input(1,1) *= -1;

    Yami::AI::ReLU<int> relu;
    Yami::AI::Matrix<int> ret = relu(input);
    ASSERT_EQ(ret(0,0), 0);
    ASSERT_EQ(ret(0,1), 2);
    ASSERT_EQ(ret(1,0), 3);
    ASSERT_EQ(ret(1,1), 0);
}

TEST_CASE(AI_RELU_BACKWARD_TEST) {
    Yami::AI::Matrix<float> x(2, 2);
    x.setElements(
        1.5, -0.5,
        -2.0,  3.0
    );

    Yami::AI::Matrix<float> dy(2, 2);
    dy.setElements(
        0.4,  0.8,
        0.2,  0.6
    );

    Yami::AI::ReLU<float> relu;
    relu(x);

    Yami::AI::Matrix<float> dZ = relu.backward(dy);
    ASSERT_FLOAT_EQ(dZ(0,0), 0.4);
    ASSERT_FLOAT_EQ(dZ(0,1), 0);
    ASSERT_FLOAT_EQ(dZ(1,0), 0);
    ASSERT_FLOAT_EQ(dZ(1,1), 0.6);
}
1
2
3
4
[RUN      ] AI_RELU_FORWARD_TEST
[     OK  ] AI_RELU_FORWARD_TEST
[RUN      ] AI_RELU_BACKWARD_TEST
[     OK  ] AI_RELU_BACKWARD_TEST
good

샘플 데이터로 결과값 정확히 적중했습니다. 👍