[Coursera] 4 얕은 신경망 네트워크 | Andrew Ng (한국어 자막 스크립트)
한국어 자막
01 신경망 네트워크 개요
이번 주에는 신경망 구현을 배우겠습니다. 기술적인 부분을 다루기 전에 이번 주 영상에서 다룰 내용을 먼저 살펴보겠습니다. 이 영상을 다 이해하지 못하셔도 괜찮습니다. 다음 영상들에서 더 기술적인 부분을 다룰 겁니다. 지금은 신경망을 어떻게 구현하는지에 대한 개요를 살펴보겠습니다.
지난주에는 로지스틱 회귀를 다뤘습니다. 이 모델이 이 계산 그래프와 일치한다는 것도 봤죠. 특성 x와 변수 w, b를 입력하면 z를 계산하는 겁니다. z는 ŷ라고도 썼던 a를 계산하는데 쓰입니다. 마지막으로 손실 함수인 L을 계산할 수 있죠.
신경망은 이렇게 생겼습니다. 제가 전에 말한 것처럼 신경망은 시그모이드 유닛을 쌓아서 만들 수 있습니다. 이 노드는 z를 계산하고 a를 계산하는 두 단계로 이루어졌습니다. 신경망에서 이 노드들은 z와 a를 계산하고 또 다른 z와 a를 계산합니다.
이후에 사용할 표기법은 위와 같습니다. 특성인 x와 변수 W와 b를 입력하면 z^[1]을 계산합니다. 새로운 표기법에서는 층이라고 불리는 일련의 노드 값을 위첨자 [1]를 사용해 표기하겠습니다. 나중엔 위첨자 [2]를 사용해 이 노드와 관련된 값을 표기하겠습니다. 신경망의 또 다른 층이죠. 대괄호 위첨자와 각 훈련 샘플을 뜻하는 소괄호 위첨자를 헷갈리시면 안됩니다. x^(i)는 i번째 훈련 샘플을 뜻하지만 위첨자 [1]과 [2]는 이 신경망의 1, 2번 레이어를 뜻합니다.
로지스틱 회귀와 비슷하게 z^[1]을 계산한 뒤 σ(z^[1])인 a^[1]을 계산해줍니다. 그 후에 다른 선형 식을 사용하여 z^[2]를 계산합니다. 다음엔 ŷ라고도 쓸 수 있는 신경망의 최종 출력값인 a^[2]를 계산해줍니다. 설명이 많았지만 여기서 배워야 할 직관은 로지스틱 회귀에선 z와 a를 계산했지만 신경망에서는 z와 a를 여러 번 계산해줍니다. 마지막으로 손실을 계산하죠.
로지스틱 회귀에서는 도함수 계산을 위해 역방향 계산을 했었습니다. da와 dz를 계산했죠. 신경망에서도 마찬가지로 역방향 계산을 합니다. 이런 식으로 계산을 해주고 da^[2]와 dz^[2]를 계산합니다. 그 값을 가지고 dW^[2]와 db^[2]를 계산하죠. 오른쪽에서 왼쪽으로 가는 빨간 화살표로 표시되는 역방향 계산이 됩니다.
신경망이 어떤 모습인지 간단히 살펴봤습니다. 로지스틱 회귀를 두 번 반복해주는 것 뿐입니다. 새로운 표기법과 세부사항이 많았지만 걱정하지 마세요. 이어지는 강좌에서 더 자세히 다루겠습니다. 다음 영상에서는 신경망 표현법에 대해 얘기하겠습니다.
02 신경망 네트워크의 구성 알아보기
신경망 그림을 그려 보았습니다. 이 영상에선 그 그림들이 무엇을 뜻하는지 다루겠습니다. 다시 말해 신경망 그림을 그렸던 게 정확히 뭘 묘사하는지에 대한 것이죠. 먼저 은닉 층이 하나인 신경망에 집중하겠습니다. 신경망의 그림입니다. 그림 부위별로 이름을 붙여주죠. 입력 특성 x₁, x₂, x₃가 세로로 쌓여있는 건 신경망의 입력 층이라고 불립니다. 당연하게 이 층엔 신경망의 입력이 있습니다. 신경망의 은닉 층이라고 불리는 또 다른 층이 있습니다. 조금 뒤에 은닉이 무슨 뜻인지 말하도록 하죠. 여기 노드 하나로 이루어진 마지막 층은 출력층이라고 불립니다. 예측 값인 ŷ의 계산을 책임지죠.
지도 학습으로 훈련시키는 신경망에선 훈련 세트가 입력값 X와 출력값 Y로 이루어져 있습니다. 은닉층의 실제 값은 훈련 세트에 기록되어 있지 않습니다. 훈련 세트에서 무슨 값인지 모른다는 거죠. 입력값과 출력값은 알 수 있지만 은닉층의 값들은 알 수 없습니다. 따라서 은닉층이라는 이름은 훈련 세트에서 볼 수 없다는 걸 의미합니다.
다른 표기법도 봅시다. 전에는 입력 특성을 나타내기 위해 벡터 X를 썼는데요, 입력값의 다른 표기법은 a^[0]가 됩니다. a는 활성값을 의미하고 신경망의 층들이 다음 층으로 전달해주는 값을 의미합니다. 입력층은 X를 은닉층으로 전달해주죠. a^[0]은 입력층의 활성값이라고 부릅니다. 다음 층인 은닉층은 활성값 a^[1]을 만듭니다. 이 첫 번째 노드는 a₁^[1]을 만들고 두 번째 노드도 a₁^[1]을 만들죠. 따라서 a^[1]는 4차원 벡터가 됩니다. 파이썬에서는 이렇게 생긴 (1, 4) 행렬 혹은 열 벡터라고 할 수 있겠네요. 4차원인 이유는 이 은닉층에 은닉 노드가 네 개 있기 때문입니다. 출력층은 실숫값 a^[2]를 만들게 됩니다. ŷ는 a^[2]의 값을 가지게 되죠. 로지스틱 회귀에서 ŷ가 a의 값을 가지는 것과 비슷합니다. 로지스틱 회귀에선 출력층 하나만 있어서 대괄호 위첨자를 쓰지 않았지만 신경망에서는 위첨자를 사용해 어떤 층에서 만들어진 건지 표기해줄 겁니다.
신경망 표기 관례에서 조금 이상한 점은 여기 보시는 이 신경망은 2층 신경망이라고 불린다는 겁니다. 신경망의 층을 셀 때 입력층은 세지 않기 때문이죠. 은닉층이 첫 번째 층이고 출력층이 두 번째 층입니다. 표기 관례에선 입력층을 0번째 층이라고 하기 때문에 이 신경망엔 입력층, 은닉층, 출력층 이렇게 층이 세 개 있다고 할 수도 있죠. 하지만 관례적으로 논문이나 이 수업에서는 이 신경망을 2층 신경망이라고 부릅니다.
나중에 알게 되겠지만 은닉층과 출력층은 연관된 매개변수가 있다는 것입니다. 은닉층은 매개변수 w^[1]과 b^[1]에 관련되어있고, 첫 번째 층인 은닉층에 관련된 변수이기 때문에 위첨자 [1]을 붙였습니다. w는 (4, 3) 행렬이 되고 b는 (4, 1) 벡터가 됩니다. 4는 이 층에 은닉 노드 네 개가 있기 때문이고 3은 입력 특성이 세 개이기 때문입니다. 나중에 행렬의 차원에 대해 다루면 더 잘 이해할 수 있을 겁니다.
출력층도 비슷하게 w^[2]와 b^[2]에 관련되어있고 차원은 (1, 4)와 (1, 1)이 됩니다. (1, 4)는 은닉층에 은닉 노드 네 개가 있고 출력층에는 노드 한 개가 있기 때문이죠. 나중에 이 행렬과 벡터의 차원을 더 자세히 다루겠습니다.
은닉층이 하나인 2층 신경망이 어떻게 생겼는지 살펴봤습니다. 다음 영상에선 이 신경망이 정확히 뭘 계산하는지 알아봅시다. 입력 X를 받아 출력 ŷ를 계산하는 방법을 보죠.
03 신경망 네트워크 출력의 계산
지난 영상에서는 은닉층이 하나인 신경망이 어떤 모습인지 살펴 봤습니다. 이 영상에선 신경망이 정확히 어떻게 출력값을 계산하는지 알아봅시다. 로지스틱 회귀와 비슷하지만 여러 번 반복됩니다. 한번 봅시다. 2층 신경망은 이렇게 생겼습니다. 이 신경망이 정확히 뭘 계산하는지 자세히 살펴봅시다.
전에 말했듯이 로지스틱 회귀를 나타내는 이 원은 두 단계의 계산을 나타냅니다. z를 위의 식과 같이 계산하고 활성화 a를 σ(z)로 계산합니다. 신경망에선 이걸 많이 반복합니다.
먼저 은닉층의 노드 하나를 집중해서 자세히 살펴보죠. 다른 노드는 회색 처리 하겠습니다. 로지스틱 회귀와 비슷하게 이 노드는 두 단계의 계산을 합니다. 첫 번째 단계는 이 노드의 왼쪽이라고 생각하고 z = wᵀx + b를 계산합니다. 이 값들은 모두 첫 번째 은닉층과 관련됐기 때문에 모두 [1]을 붙여줍니다. 은닉층의 첫 번째 노드이기 때문에 아래첨자 1도 붙여줍니다. 두 번째 단계에선 a₁^[1] = σ(z₁^[1])을 계산해 줍니다. aᵢ^[l]에서 대괄호 안에 있는 l은 층 번호이고 아래 첨자 i는 층 안의 노드 번호입니다. 저희가 보고 있는 노드는 1번 층의 1번 노드이므로 아래 첨자와 위 첨자 모두 1이 되죠. 신경망의 노드인 원은 이 두 계산을 의미합니다.
신경망의 두 번째 노드를 살펴보죠. 은닉층의 두 번째 노드입니다. 왼쪽의 로지스틱 회귀와 비슷하게 이 원은 계산 두 단계를 나타냅니다. 첫 번째는 z₂^[1]을 계산하는 겁니다. 아직 첫 번째 층이지만 두 번째 노드가 되죠. z₂^[1]은 w₂^[1]ᵀx + b₂^[1]이고 a₂^[1]은 σ(z₂^[1])가 됩니다.
처음 두 은닉 노드를 살펴봤고 나머지 두 노드도 비슷한 계산을 합니다. 이 두 쌍의 등식을 다음 슬라이드로 가져가죠. 여기 신경망이 있고 방금 적은 두 노드에 대한 등식이 있습니다. 나머지 노드에 대한 식도 적을 수 있습니다.
표기법을 이해하기 쉽게 말하자면 이건 벡터 w₁^[1]의 전치에 x를 곱한 겁니다. 예상했겠지만 신경망을 for 문을 써서 구현하면 굉장히 비효율적입니다. 그래서 이 네 등식을 벡터화하겠습니다.
먼저 z를 벡터로 계산하는 법을 보겠습니다. w를 행렬처럼 쌓는다면 열 벡터의 전치인 행 벡터 w₁^[1]ᵀ, w₂^[1]ᵀ, w₃^[1]ᵀ, w₄^[1]ᵀ가 됩니다. 벡터 w를 쌓아서 행렬을 만든 거죠. 다른 방식으로 생각하면 로지스틱 회귀 유닛 네 개가 있고 각 유닛은 상응하는 매개 변수 w가 있습니다. 그 네 벡터를 쌓으면 (4, 3) 행렬을 얻습니다.
이 행렬을 입력 특성 x₁, x₂, x₃과 곱하면 행렬 곱 규칙에 따라서 w₁^[1]ᵀx, w₂^[1]ᵀx, w₃^[1]ᵀx, w₄^[1]ᵀx를 얻게 됩니다. b₁, b₂, b₃, b₄로 이루어진 b를 잊지 말고 더해준다면 여기 있는 벡터와 같습니다. 결과에 각 값을 더해주면 되겠죠. 결과의 각 행은 위에서 계산한 네 값과 정확히 일치한다는 걸 알 수 있습니다. 따라서 이 값은 z₁^[1], z₂^[1], z₃^[1], z₄^[1]와 같다는 걸 볼 수 있죠.
각 z를 열 벡터로 쌓은 이 벡터를 z^[1]이라고 부르겠습니다. 벡터화 할 때 여러분을 도와줄 수 있는 조언은 한 층에 노드가 여러 개이면 세로로 쌓는다는 것입니다. 은닉 층의 네 노드에 상응하는 z₁^[1]부터 z₄^[1]를 세로로 쌓아서 벡터 z^[1]을 만든 이유이죠. 표기법 하나를 더 보여주자면 w를 쌓아서 만든 이 (4, 3) 행렬은 W^[1]라고 부르겠습니다. 이 (4, 1) 벡터는 b^[1]라고 부르죠. z를 행렬로 표기하여 계산해봤습니다. 마지막으로 해야 할 일은 a의 값을 계산하는 것 입니다. a^[1]을 a₁^[1]부터 a₄^[1]까지 쌓은 벡터로 정의하겠습니다. 이 값을 쌓아서 a^[1]이라고 부르고 값은 σ(z^[1])이 됩니다. 이 시그모이드 함수는 z^[1]의 각 원소들의 시그모이드 값을 계산하는 함수입니다.
다시 보자면 z^[1]은 W^[1]에 x를 곱하고 b^[1]을 더해준 것이고 a^[1]은 σ(z^[1])이 됩니다. 여기서 볼 수 있는건 신경망의 첫 층에서는 x가 주어졌을 때 z^[1]은 W^[1]x + b^[1]이고 a^[1]은 σ(z^[1])이 됩니다. z의 차원은 (4, 3) 행렬과 (3, 1) 벡터를 곱하고 (4, 1) 벡터를 더해 (4, 1)이고 a는 z와 같은 (4, 1) 벡터입니다. x는 a^[0]이고 ŷ는 a^[2]라는 걸 기억하세요. 따라서 x를 a^[0]으로 바꿀 수 있습니다. 비슷한 방법으로 다음 층에 대한 표현도 비슷하단 걸 알아낼 수 있습니다. 출력층에서는 매개 변수 w^[2]와 b^[2]가 있고 w^[2]는 (1, 4) 행렬이고 b는 실수인 (1, 1) 행렬입니다. 따라서 z^[2]는 실수가 되겠죠. (1, 4) 행렬과 (4, 1) 행렬을 곱한 뒤 (1, 1) 행렬을 더해줬죠. 결과는 실수가 됩니다.
마지막 출력 유닛을 매개 변수 w와 b를 가지는 로지스틱 회귀와 비슷하게 생각하면 w는 W^[2]ᵀ와 비슷하고 b는 b^[2]와 비슷합니다. 왼쪽 부분을 가리고 무시한다면 마지막 출력 유닛은 로지스틱 회귀와 굉장히 흡사하죠. w와 b 대신에 (1, 4) 차원 W^[2]와 (1, 1) 차원 b^[2]를 쓰는 것만 빼면요.
복습하자면 로지스틱 회귀에서 출력 혹은 예측값을 계산하려면 z = wᵀx + b와 ŷ = a = σ(z)를 계산했습니다. 은닉층이 하나인 신경망에서는 출력값을 계산하기 위해 이 네 등식을 구현하면 됩니다. 출력값을 계산하는 벡터화된 구현이라고 생각할 수 있죠. 은닉층의 네 로지스틱 회귀 유닛의 출력값 계산은 여기서 하고 출력층의 로지스틱 회귀는 여기서 해줍니다.
설명이 잘 됐으면 좋겠네요. 여기서 일아야 할 점은 이 신경망의 출력값을 계산하는 데엔 이 코드 네 줄만이 필요하다는 것입니다. 한 입력 특성 벡터가 주어졌을때 코드 네 줄만으로 신경망의 출력값을 계산하는 법을 봤습니다. 로지스틱 회귀에서처럼 여기서도 여러 개의 훈련 샘플에 대해 벡터화하고 싶습니다. 훈련 샘플을 행렬의 열로 쌓아 조금 변형하면 로지스틱 회귀에서 본 것처럼 한 번에 한 훈련 샘플이 아닌 모든 훈련 세트에 대해 출력값을 계산할 수 있습니다. 다음 영상에서 자세히 살펴보죠.
04 많은 샘플에 대한 벡터화
지난 영상에선 훈련 샘플이 하나만 있을 때 신경망에서 예측값을 계산하는 법을 봤습니다. 이 영상에서는 다수의 훈련 샘플에 대해 벡터화하는 법을 보겠습니다. 로지스틱 회귀의 결과와 비슷할 겁니다. 훈련 샘플을 행렬의 열로 쌓아서 지난 영상에서 본 등식을 조금 바꾸면 신경망은 모든 샘플에 대한 출력값을 거의 동시에 계산할 수 있습니다. 좀 더 자세히 살펴봅시다.
지난 영상에서 본 z^[1], a^[1], z^[2], a^[2]를 계산하는 등식입니다. 이 등식은 입력 특성 벡터인 x가 주어졌을 때 한 훈련 샘플에 대해 ŷ인 a^[2]를 계산해주죠. 훈련 샘플이 m개 있다면 이 과정을 첫 훈련 샘플인 x^(1)에 적용하여 첫 샘플의 예측값인 ŷ^(1)을 계산하고, 이 과정을 x^(2)와 x^(m)까지 적용해서 ŷ^(2)와 ŷ^(m)을 계산해줍니다. 이 값을 활성화 함수 표기법으로 나타내기 위해 이걸 a^[2](1)이라고 쓰겠습니다 다음은 a^[2](2)가 되고 a^[2](m)까지 이어집니다. 이 표기법인 a^[2](i)에서 (i)는 i 번째 훈련 샘플을 의미하고 [2]는 두 번째 층을 의미합니다. 대괄호와 소괄호 지수의 의미이죠.
모든 훈련 샘플의 예측값을 벡터화되지 않은 방법으로 계산한다면 i가 1부터 m까지 이 공식을 구현하면 됩니다.
위 네 등식에서 훈련 샘플과 관련있는 모든 변수에 위첨자 (i)를 붙여주면 됩니다. m개의 모든 훈련 샘플의 출력값을 계산하고 싶다면 x, z와 a에 위첨자 (i)를 붙여주면 되죠.
여기서 하고 싶은 건 for 문을 없애기 위해 이 계산을 벡터화하는 겁니다. 만약 제가 어려운 선형대수학을 많이 다루는 것 같지만 딥러닝 시대에서 이걸 정확히 구현하는 건 굉장히 중요합니다. 이 수업에선 벡터화 단계를 가능한 쉽게 설명하기 위해 표기법을 신중히 선택했습니다. 이 핵심 방법을 사용해 알고리즘을 더 빠르고 정확하게 구현하면 좋겠네요. 이 코드를 다음 슬라이드로 옮겨간 뒤 어떻게 벡터화하는지 봅시다.
m개의 모든 훈련 샘플을 계산하는 for 문 코드가 있습니다. X는 훈련 샘플이 열로 쌓은 행렬이란 걸 기억해주세요. 훈련 샘플을 열로 쌓으면 nₓ × m 행렬이 됩니다. 이 for 문을 벡터화하려면 뭘 해야 하는지 바로 말해드리겠습니다.
- Z^[1] = W^[1] X + b^[1]
- A^[1] = σ(Z^[1])
- Z^[2] = W^[2] A^[1] + b^[2]
- A^[2] = σ(Z^[2])
비교해본다면 벡터였던 소문자 x를 열로 쌓아 행렬인 대문자 X를 얻었습니다. z에도 같은 작업을 한다면 열 벡터인 z^[1](1), z^[1](2)부터 z^[1](m)까지 열로 쌓는다면 소문자 a에서 대문자 A^[1]이 됩니다. Z^[2]와 A^[2]도 비슷하게 계산할 수 있죠. 이 벡터들을 가로로 쌓아서 Z^[2]와 A^[2]를 얻을 수 있습니다.
이 표기법이 여러분의 생각을 도울 수 있는 점은 행렬 Z와 A에 가로는 훈련 샘플의 번호가 됩니다. 각각 다른 훈련 샘플에 대응됩니다. 왼쪽에서 오른쪽으로 가면 훈련 세트를 다 살펴볼 수 있습니다. 세로를 살펴보면 신경망의 노드들이 됩니다. 행렬 왼쪽 위에 있는 값은 첫 은닉 유닛의 첫 훈련 샘플의 활성값이 됩니다. 바로 아래 값은 첫 훈련 샘플의 두 번째 은닉 유닛의 활성값이 되겠죠. 아래로 내려갈수록 첫 훈련 샘플의 은닉 유닛가 바뀝니다. 세로는 은닉 유닛의 번호가 되는거죠. 가로로 움직인다면 은닉 유닛은 첫 번째로 고정되고 훈련 샘플이 바뀝니다. 이 노드는 첫 번째 은닉 유닛의 마지막 훈련 샘플인 m번째가 됩니다. 행렬 A의 가로는 다른 훈련 샘플을 의미하고, 행렬 A의 세로 번호들은 다른 은닉 유닛을 의미합니다. 행렬 Z에게도 비슷하게 적용됩니다. 행렬 X도 가로는 다른 훈련 샘플을 의미하고 세로는 다른 입력 특성을 의미합니다. 신경망 입력층의 다른 노드들이죠.
이 공식을 가지고 이제 여러 샘플에 대해 신경망을 벡터화할 수 있습니다. 다음 영상에서는 이게 왜 이런 벡터화에 대해 정확한 구현인지 설명하겠습니다. 로지스틱 회귀에서 본 설명과 비슷할 겁니다. 다음 영상으로 가시죠.
05 벡터화 구현에 대한 설명
지난 영상에서는 훈련 샘플을 행렬 X에 가로로 쌓아서 신경망의 정방향 전파를 벡터화했습니다. 왜 우리가 썼던 등식이 여러 훈련 샘플에 대한 정확한 벡터화인지 그 이유를 살펴봅시다.
몇 가지 예시에 대해 정방향 전파 계산을 살펴봅시다. 첫 훈련 샘플에 대해 Z^[1](1) = W^[1] x^(1) + b^[1]을 계산하고 두 번째 훈련 샘플에 대해선 Z^[1](2) = W[1](2) x^(2) + b^[1]을 계산하고 세 번째 훈련 샘플에선 Z^[1](3) = W^[1](3) x^(3) + b^[1]을 계산한다고 합시다. 설명을 간단히 하기 위해 b를 무시하도록 합시다. 간단히 b를 0이라고 합시다. 제 주장은 조금만 바꾸면 b가 0이 아닐 때도 잘 작동할 겁니다. 설명을 위해 조금 간단하게 하기 위함입니다. W^[1]은 어떤 한 행렬이 될 겁니다. 행렬에 행이 여러 개 있겠죠. x^(1)에 관련된 계산을 보면 W^[1]와 x^(1)의 곱은 열 벡터가 됩니다. W^[1]와 x^(1)의 곱은 열 벡터가 됩니다. 이렇게 그릴 수 있겠죠. x^(2)도 비슷하게 W^[1]에 곱하면 열 벡터가 나오죠. Z^[1](2)가 되겠네요. 마지막으로 W^[1]과 x^(3)를 곱하면 z^[1](3)인 열 벡터가 됩니다.
훈련 샘플을 모두 쌓아 만든 훈련 세트인 X를 살펴보면, 행렬 X는 x^(1), x^(2), x^(3)를 모두 가로로 쌓아 만든 겁니다. 훈련 샘플이 더 많다면 가로로 더 많이 쌓아야 합니다. 이 행렬 X와 W를 곱한다면, 행렬 곱이 어떻게 이루어지는지 생각해보면 첫 열은 여기 적은 보라색과 같게 되고, 둘째 열은 초록색 열과 같고, 셋째 열은 주황색 열과 같습니다. 이 값은 z^[1](1), z^[1](2), z^[1](3)이 열 벡터로 표기된 것과 같습니다. 훈련 샘플이 더 많다면 열이 더 많아지겠죠. 이 값은 행렬 Z^[1]가 됩니다.
이 설명이 전에 하나의 훈련 샘플만 봤을 경우 W^[1] x^(i) = Z^[1](i)가 있었을 때 훈련 샘플을 다른 열에 채운다면 나오는 결과인 Z도 z가 열로 쌓입니다. 파이썬 브로드캐스팅을 통해 b를 더한다면 값은 여전히 정확하겠죠. b^[i]를 이 행렬의 각 열에 더해주게 됩니다. 이 슬라이드에선 Z^[1] = W^[1] X + b^[1]이 전 슬라이드에서 봤던 네 단계 중 첫 단계에 맞는 식이라는 걸 보였습니다. 비슷한 분석을 하면 다른 단계들도 비슷한 논리를 통해 식을 구할 수 있습니다. 입력값을 열로 쌓는다면 결과도 열로 쌓인 값이 나오게 되죠.
마지막으로 이 영상에서 배운 것을 복습해봅시다. 한 번에 하나의 훈련 샘플에 대해 정방향 전파를 한다면 i가 1부터 m까지 이 코드를 실행해야 합니다. 다음엔 훈련 샘플을 이처럼 열로 쌓아서 여기 있는 값들 모두 열을 쌓는 거죠. 여기는 A^[1]만 보여졌지만 나머지 값들도 같은 방법으로 하면 됩니다.
전 슬라이드에서 본 것처럼 이 코드는 모든 m 훈련 샘플에 대해 벡터화하게 해줍니다. 비슷한 방법으로 나머지 줄도 위의 코드에 맞는 구현임을 보일 수 있습니다. X는 A^[0]과 같습니다. 입력 특성 벡터 x는 a^[0]와 같았기 때문이죠. 따라서 X는 A^[0]와 같습니다.
그렇다면 이 식에는 대칭성이 있습니다. 이 식은 W^[1] A^[0] + b^[1]이라고 쓸 수 있죠. 이 공식 쌍들은 굉장히 비슷하게 생겼죠. 지수가 1 증가했을 뿐입니다. 이건 신경망의 두 층은 대략 같은 것을 계산하고 있다는 걸 보여줍니다.
여기서는 2층 신경망을 봤지만, 다음주 영상에서 더 깊은 신경망을 봐도 이 두 단계를 더 많이 반복하는 것뿐입니다. 다수의 훈련 샘플에 대해 신경망을 벡터화하는 방법을 봤습니다. 지금까진 신경망에서 시그모이드 함수를 썼는데 최선의 선택은 아닙니다. 다음 영상에서는 다른 활성화 함수를 쓰는 방법에 대해 보겠습니다. 시그모이드 함수는 하나의 선택지일 뿐이죠.
06 활성화 함수
신경망을 만들 때 선택해야 해야하는 것 중 하나는 은닉층과 출력층에서 어떤 활성화 함수를 쓸지입니다. 지금까지는 시그모이드 활성화 함수를 썼지만, 다른 함수가 더 좋을 수도 있습니다. 몇 가지 함수를 살펴보죠.
시그모이드 함수를 쓰는 두 단계가 있었습니다. 시그모이드 함수는 이렇게 생겼었죠. a = 1 / 1+e^(-z)입니다. 일반적으로는 여기 쓴 것처럼 다른 함수인 g(z^[1])를 쓸 수 있습니다. g는 시그모이드 함수가 아닌 비선형 함수일 수 있죠. 시그모이드 함수는 0부터 1까지 나타내죠.
시그모이드 함수보다 대부분 경우 좋은 것은 쌍곡 탄젠트 함수인 tanh 함수입니다. 이 축은 z이고 이 축은 a이죠. 이 함수는 a = tanh(z) 함수입니다. 그리고 이 값은 +1부터 -1까지입니다. tanh 함수의 공식은 (e^z - e^-z) / (e^z + e^-z) 입니다. 수학적으로 시그모이드 함수를 조금 옮긴 함수입니다. 시그모이드 함수와 비슷하지만 원점을 지나고 비율이 달라졌습니다. -1과 +1 사이가 됐죠. 은닉 유닛에 대해 g(z^[1])을 tanh(z)로 놓는다면 거의 항상 시그모이드 함수보다 좋습니다. 값이 +1와 -1 사이이기 때문에 평균값이 0에 더 가깝기 때문입니다. 학습 알고리즘을 훈련할 때 평균값의 중심을 0으로 할 때가 있습니다. 시그모이드 함수 대신 tanh 함수를 쓴다면 데이터의 중심을 0.5 대신 0으로 만드는 효과가 있습니다. 다음 층의 학습을 더 쉽게 해주죠. 최적화 알고리즘을 다루는 두 번째 과목에서 더 다루겠습니다.
저는 시그모이드 활성화 함수를 거의 쓰지 않습니다. tanh 함수가 거의 항상 더 좋기 때문이죠. 출력층은 예외입니다. y가 0이나 1이라면 ŷ은 -1과 1 사이 대신 0과 1 사이로 출력하는 게 더 좋기 때문이죠. 시그모이드 활성화 함수를 쓰는 한 가지 예외는 이진 분류를 할 때입니다. 출력층에 시그모이드 함수를 쓰고 g(z^[2]) = σ(z^[2])가 됩니다. 이 예에서 보듯이 은닉층에선 tanh 활성화 함수를 쓰고, 출력층에서는 시그모이드 함수를 씁니다. 다른 층에는 다른 활성화 함수를 쓸 수 있죠. 다른 층에 다른 활성화 함수가 쓰였다는 걸 보여주기 위해 대괄호 위첨자를 쓸 수 있습니다. g^[1]와 g^[2]가 다를 수 있다는 거죠. [1]은 이 층을 의미하고 [2]는 출력층을 의미합니다.
시그모이드 함수와 tanh 함수의 단점은 z가 굉장히 크거나 작으면 함수의 도함수가 굉장히 작아진다는 것입니다. z가 크거나 작으면 함수의 기울기가 0에 가까워지고 경사 하강법이 느려질 수 있죠.
머신러닝에서 인기 있는 함수는 ReLU라고 불리는 정류 선형 유닛입니다. ReLU 함수는 이렇게 생겼습니다. 공식은 max(0, z)입니다. z가 양수일 때는 도함수가 1이고, z가 음수이면 도함수가 0이 됩니다. 엄밀하게는 z가 0일 때의 도함수는 정의되지 않았지만, 컴퓨터에서 구현하면 z가 정확히 0이 될 확률은 굉장히 낮기 때문에 걱정하지 않으셔도 됩니다. z가 0일 때 도함수가 1이나 0이라고 가정하셔도 잘 작동합니다. 미분 불가능해도 괜찮습니다.
활성화 함수 선택에 대한 조언을 드린다면, 이진 분류의 출력층에는 자연스럽게 시그모이드 함수를 사용합니다. 다른 경우에는 ReLU가 활성화 함수의 기본값으로 많이 사용됩니다. 은닉층에 어떤 함수를 써야 할지 모르겠다면 그냥 ReLU를 쓰는 게 좋습니다. 요즘에는 ReLU가 많이 쓰이지만, 가끔 tanh도 씁니다. ReLU의 단점 중 하나는 z가 음수일 때 도함수가 0이라는 것입니다. 실제로는 잘 되긴 하지만, 다른 버전인 leaky ReLU도 있습니다.
다음 슬라이드에 공식을 적겠지만, z가 음수일 때 도함수가 0인 대신 약간의 기울기를 줍니다. 이 함수는 leaky ReLU라고 부릅니다. 실제로는 많이 쓰이지 않지만, ReLU보다 좋은 결과를 보여줍니다. 아무거나 골라도 되지만 하나만 골라야 한다면 저는 보통 ReLU를 씁니다. ReLU와 leaky ReLU의 장점은 대부분의 z에 대해 기울기가 0과 매우 다르다는 것입니다. 따라서 시그모이드나 tanh 대신 ReLU 함수를 쓴다면 신경망은 훨씬 더 빠르게 학습할 수 있습니다. 학습을 느리게 하는 원인인 함수의 기울기가 0에 가까워지는 걸 막아주기 때문이죠. z의 절반에 대해 ReLU의 기울기가 0이지만, 실제로는 충분한 은닉 유닛의 z는 0보다 크기 때문에 실제로는 잘 동작합니다.
다양한 활성화 함수의 장단점을 복습해보죠. 여기 시그모이드 활성화 함수가 있습니다. 이진 분류의 출력층 외에는 거의 쓰지 않는 게 좋다고 말하고 싶습니다. 제가 쓰지 않는 이유는 tanh 함수가 거의 항상 더 좋기 때문입니다. tanh 활성화 함수의 식은 이와 같습니다. 가장 많이 쓰이는 활성화 함수는 ReLU 함수입니다. 뭘 써야 할지 모르면 이걸 쓰세요. leaky ReLU를 써보는 것도 좋습니다. a는 max(0.01z, z)라고 할 수 있겠네요. a는 0.01z와 z 중 최댓값이기 때문에 함수가 꺾이게 됩니다. 저 계수가 왜 0.01이라고 물어보실 수 있는데, 저 값도 학습 알고리즘의 변수로 넣을 수 있습니다. 몇몇 사람들은 더 좋아진다고 하지만 실제로 구현하는 사람들은 보지 못했습니다. 사용해보고 싶다면 시도해보고 어떻게 작동하는지 살펴보세요. 잘 작동한다면 계속 쓰셔도 됩니다.
신경망에 쓸 수 있는 활성화 함수에 대한 이해가 됐으면 좋겠네요. 딥러닝에서는 신경망을 만들 때 굉장히 많은 선택을 할 수 있습니다. 은닉층의 수, 활성화 함수와 나중에 다룰 가중치의 초기화 같은 선택이 많습니다. 풀 문제에 따라 좋은 결과를 내게 해줄 지침은 찾기 어렵습니다. 이 강의에서 어떤 게 더 인기 있는지 말씀드리지만, 각 구현의 특징에 따라 뭐가 제일 좋을지는 예측하기 힘듭니다. 어떤 활성화 함수가 좋을지 모르겠다면 모두 시도해 본 뒤, 나중에 다룰 검증나 개발 세트에 시도해보고 결과를 살펴보세요. 제일 좋은 결과가 나온 걸 선택하면 됩니다. 여러 선택지를 시도해본다면 여러분의 신경망에 무엇이 잘 맞는지 알게 될 것입니다. 항상 ReLU를 쓴다고 이야기했지만, 나중에 풀어야 하는 문제에 적용되지 않을 수도 있습니다.
활성화 함수를 고르는 법과 자주 쓰이는 활성화 함수들을 살펴봤습니다. 궁금할 수 있는 점은 왜 굳이 활성화 함수를 써야 하는지일 텐데, 다음 영상에선 왜 신경망이 비선형 활성화 함수를 필요로 하는지에 대해 다루겠습니다.
07 왜 비선형 활성화 함수를 써야 할까요?
왜 신경망은 비선형 활성화 함수가 필요할까요? 신경망이 흥미로운 함수를 계산하려면 비선형 활성화 함수가 필요합니다. 왜 그런지 살펴보죠.
여기 정방향 전파 식들이 있습니다. g를 없애고 z^[1]으로 대체해봅시다. 다른 말로는 g(z) = z라고 할 수 있겠네요. 가끔 이걸 선형 활성화 함수라고 부릅니다. 입력값을 출력값으로 내보내기 때문에 항등 함수라고 부르는 게 더 맞겠네요. 설명을 위해 a^[2]도 z^[2]와 같게 만들어 줍시다. 이렇게 한다면 이 모델은 ŷ을 입력 특성인 x에 대한 선형 함수로 계산하게 됩니다. 첫 두 식을 보면 a^[1] = z^[1] = W^[1] x + b^[1]이 됩니다. 다음엔 a^[2] = z^[2] = W^[2] a^[1] + b^[2]가 되죠. a^[1]을 두 번째 식에 대입한다면 a^[2] = W^[2] ( W^[1] x + b^[1] ) + b^[2]가 되고, 간소화하면 (W^[2] W^[1]) x + (W^[2] b^[1] + b^[2]) 가 됩니다. 앞의 값을 W'이라고 부르고 뒤 값을 b'라고 부르면 W' x + b'이 되겠죠.
선형 활성화 함수 또는 항등 활성화 함수를 쓴다면, 신경망은 입력의 선형식 만을 출력하게 됩니다. 나중에 층이 많은 신경망인 심층 신경망에 대해 다룰 텐데, 선형 활성화 함수나 활성화 함수가 없다면 층이 얼마나 많든 간에 신경망은 선형 활성화 함수만 계산하기 때문에 은닉층이 없는 것과 다름없습니다. 전에 간단히 말했듯이 여기에 선형 활성화 함수를 쓰고 여기 시그모이드 함수를 쓰면, 이 모델은 표준 로지스틱 회귀보다 더 나아지지 않습니다. 증명은 하지 않겠지만 원하시면 해보셔도 됩니다. 여기서 알아야 할 점은 선형 은닉층은 쓸모가 없다는 것입니다. 두 선형 함수의 조합은 하나의 선형 함수가 되기 때문이죠. 비선형식을 쓰지 않는다면 신경망이 깊어져도 흥미로운 계산을 할 수 없습니다.
선형 활성화 함수인 g(z) = z를 쓸 데가 하나 있는데, 회귀 문제에 대한 머신러닝을 할 때입니다. y가 실수일 때죠. 집값을 예측할 때 같은 경우입니다. y가 0이나 1이 아닌 0부터 집값이 비싸지는 대로 올라가겠죠. 백만 달러 정도나 데이터 세트에 있는 최고 값이 될 겁니다. y가 실수값이라면 선형 활성화 함수를 써도 괜찮을 수 있습니다. 출력값인 ŷ이 -∞부터 ∞까지의 실수 값이 되도록 말이죠. 하지만 은닉 유닛은 선형 활성화 함수가 아닌 ReLU, tanh, leaky ReLU나 다른 비선형 함수를 써야 하죠. 선형 활성화 함수를 쓸 수 있는 곳은 대부분 출력층입니다. 더 자세하게 다루지 않을 압축에 관련된 게 아니라면, 은닉층에서 선형 활성화 함수를 사용하는 경우는 굉장히 드뭅니다.
첫째 주 영상에서 본 것처럼 집값을 예측할 때에는 집값은 항상 양수이기 때문에 출력값인 ŷ가 양수가 되도록 ReLU 함수를 쓸 수 있습니다.
신경망에서 비선형 활성화 함수를 써야 하는 이유를 이해했으면 좋겠네요. 다음에는 경사 하강법에 대해 얘기할 텐데, 경사 하강법을 다루기 위해 다음 영상에서 각 활성화 함수의 도함수를 구하는 법을 다루겠습니다. 다음 영상에서 뵙죠.
08 활성화 함수의 미분
신경망의 역방향 전파를 구현하려면 활성화 함수의 도함수를 구해야 합니다. 저희가 고른 활성화 함수와 그 함수의 기울기를 어떻게 구하는지 살펴봅시다.
여기에 시그모이드 활성화 함수가 있습니다. 주어진 값 z에 대해 이 함수는 특정한 기울기를 가집니다. 선을 그었을 때 삼각형의 높이를 너비로 나눈 값이죠. g(z)가 시그모이드 함수일 때 함수의 기울기는 d/dz g(z)가 됩니다. 미적분에서 이 값은 z에서의 기울기가 되죠. 미적분을 사용해 시그모이드 함수의 도함수를 어떻게 구하는지 안다면, 여기 쓰여있는 공식과 같다는 걸 보일 수 있습니다. 과정은 보여주지 않겠지만 미적분을 안다면 잠시 멈춰서 증명해보셔도 좋습니다.
이 값은 g(z) (1 - g(z))와 같습니다. 이 공식이 맞는지 한번 확인해보죠. z가 굉장히 큰 값인 10일 때, g(z)는 1에 가까워집니다. 왼쪽의 공식에 따르면 d/dz g(z)는 1(1 - 1)과 비슷해지겠죠. 0과 가까워집니다. z가 커지면 기울기가 0에 가까워지기 때문에 맞는 공식임을 알 수 있습니다. z가 아주 작은 값인 -10이라면 g(z)는 0에 가까워지고, 왼쪽의 공식은 0(1 - 0)에 가까워집니다. 이 값도 0에 가까워지죠. z가 0이라고 가정하면, g(z)는 시그모이드 함수에서 볼 수 있듯이 1/2가 됩니다. 따라서 도함수는 1/2 (1 - 1/2)인 1/4가 됩니다. z가 0일 때의 기울기 혹은 도함수의 값과 정확히 일치합니다. 한 가지 표기법을 더 소개하면, 도함수를 표현하는 데 이 식을 쓰는 대신 g'(z)라고 쓸 수 있습니다. 미적분에서 g'(z)는 입력 변수 z에 대한 g의 도함수를 의미합니다. 신경망에서 a는 g(z)와 같고, 이 식과 같습니다. 따라서 이 식은 a(1 - a)로 간소화할 수 있습니다. 가끔 구현에서 g'(z)가 a(1 - a)라고 되어있는 걸 볼 수도 있을 겁니다. g'(z)가 여기 쓰인 a(1 - a)와 같기 때문이죠. 이 공식의 장점은 이미 a의 값을 계산했다면 이 공식을 써서 g'의 값을 빠르게 계산할 수 있다는 겁니다.
시그모이드 활성화 함수에 대해 살펴봤습니다. 이번엔 tanh 활성화 함수를 보죠. 전에 봤던 것처럼 d/dz g(z)는 z에서 g의 기울기입니다. tanh 함수의 공식을 살펴보고, 미적분을 안다면 도함수를 구해 이 식과 같다는 걸 보일 수 있습니다. 전에 쓴 것처럼 이 식을 g'(z)라고 하겠습니다. 이 공식이 말이 된다는 걸 확인해보죠. z가 10이라면 tanh(z)는 1에 가까워지고, tanh는 -1부터 1까지 값을 가집니다. 이 공식에 따르면 g'(z)는 1 - 1²인 0에 가까워집니다. z가 큰 값이면 기울기는 0에 가까워지죠. z가 매우 작은 값인 -10이라면 tanh(z)는 -1에 가까워집니다. g'(z)는 1에서 -1의 제곱을 뺀 0에 가까워지죠. z가 0이라면 tanh(z)는 0이 되고, 기울기는 1이 됩니다. 실제로도 기울기는 1이죠. 요약하자면 a가 g(z)인 tanh(z)일 때, 도함수인 g'(z)는 1 - a²가 됩니다. a의 값을 이미 계산했다면 이 공식을 사용하여 도함수도 빠르게 계산할 수 있습니다.
마지막으로 ReLU와 leaky ReLU의 도함수를 계산하는 법을 보겠습니다. ReLU에서 g(z)는 max(0, z)가 됩니다. 따라서 도함수는 z가 0보다 작을 때는 0이고, z가 0보다 크면 1이 됩니다. z가 0일 때 엄밀하게는 정의되지 않았습니다. 수학적으로 정확하지는 않지만, 실제로 z가 0일 때 도함수를 1이라고 해도 문제 없습니다. 0이라고 해도 전혀 문제없죠. 최적화에 익숙하다면, g'은 활성화 함수 g(z)의 서브 경사이기 때문에 경사 하강법이 잘 작동합니다. z가 정확히 0이 될 확률은 정말 작기 때문에, z가 0일 때 도함수를 뭐라고 하든지 상관없습니다. 따라서 실제로는 g'(z)를 이렇게 구현하죠. leaky ReLU 활성화 함수를 사용하여 신경망을 훈련한다면 g(z)는 max(0.01z, z)가 됩니다. g'(z)는 z가 0보다 작을 때 0.01이 되고, z가 0보다 클 때는 1이 됩니다. 여기서도 z가 정확히 0이라면 도함수가 정의되지 않았지만, 코드를 쓸 때에는 z가 0일 때 g'을 0.01이나 1 둘 중에 아무렇게나 설정해도 괜찮습니다.
이 공식을 가지고 활성화 함수의 기울기나 도함수를 계산할 수 있을 겁니다. 이 구성 요소들을 알고 있으니 신경망의 경사 하강법을 어떻게 구현하는지 봅시다. 다음 영상에서 뵙죠.
09 신경망 네트워크와 경사 하강법
좋습니다. 지금까지 흥미로운 내용이었죠. 이번 강의에서는 경사 하강법을 구현하는 방법을 알아보겠습니다. 한 개의 은닉층을 가진 신경망에 대해서 말이죠. 이 강의에서는 여러분이 구현해야 할 식들을 드릴 것입니다. 이것은 역전파를 계산하거나 경사 하강법을 적용하기 위한 식들입니다. 그리고 이 부분을 마친 후에는 저는 추가적인 설명을 해드릴 것인데요. 이 물리적인 식들이 왜 신경망에서 경사를 계산하는데 있어서 올바른 식인지 말이죠.
현재 여러분의 단일층 신경망은 w^[1], b^[1], w^[2], b^[2]의 변수들을 가집니다. 그리고 기억하시는 것처럼 n_x는 n^[0]를 입력 특성으로 가지고, n^[1]은 은닉 유닛으로 가지며, n^[2]를 출력 유닛으로 가집니다. 우리가 사용한 샘플에서는 n^[2]는 1이었습니다. 그러면 행렬 w^[1]은 (n^[1], n^[0]) 차원을 가집니다. 그리고 b^[1]은 n^[1] 차원을 가지는 벡터가 됩니다. (n^[1], 1) 차원이라고 적을 수 있겠죠. 따라서 행 벡터가 될 겁니다. w^[2]은 (n^[2], n^[1]) 차원이 되고, b^[2]는 (n^[2], 1) 차원이 됩니다. 이것은 한 개의 은닉 유닛을 가진 n^[2]가 1인 예시를 사용한 경우입니다. 그리고 여기에 신경망의 비용 함수가 있습니다. 여기서는 여러분이 이진 분류를 하고 있다고 가정하겠습니다. 이 경우에 각 변수들에 대한 비용은 다음과 같이 손실 함수의 평균이 됩니다. 여기서 L은 신경망이 예측한 ŷ값에 대한 손실입니다. 여기 부분은 실제로는 a^[2]가 되겠죠. y는 참 값을 말합니다. 여러분이 이진 분류를 하는 경우에 손실 함수는 로지스틱 회귀에서 사용한 것과 같을 것입니다.
이제 이 알고리즘에서 변수들을 훈련시키기 위해서 경사 하강법을 사용해야 합니다. 신경망을 훈련시킬 때 0이 아닌 값으로 변수를 초기화하는 것이 중요합니다. 이것에 대해서는 나중에 이야기하겠습니다. 변수를 어떤 값으로 초기화한 후 경사 하강법이 반복될 때마다 예측값을 계산합니다. 다시 말해서 ŷ^[i]를 계산하는 것이 됩니다. i는 1부터 m까지의 정수가 되죠.
그리고 이제 도함수를 계산해야 합니다. dw^[1]을 계산하는데, 이 값은 변수 w^[1]에 대한 비용 함수의 도함수를 의미합니다. 또한 db^[1]을 계산해야 하는데, 이 값은 b^[1]에 대한 비용 함수의 도함수가 되는 것이죠. w^[2]와 b^[2]에 대해서도 비슷하게 도함수를 계산합니다.
결국 경사 하강법은 w^[1]을 w^[1] - α dw^[1]으로 바꿉니다. 여기서 α는 학습률을 의미합니다. b^[1]은 b^[1] - α db^[1]으로 바꿉니다. w^[2]와 b^[2]에 대해서도 비슷하게 적용됩니다. 콜론이 붙은 등호 또는 일반 등호를 사용하는데, 두 표기법 모두 괜찮습니다. 이제 이것이 경사 하강법의 한 반복이 됩니다. 그리고 변수들이 수렴할 때까지 이것을 반복하게 되는 것이죠.
이전 강의에서 어떻게 예측값을 계산하는지 배웠습니다. 또 어떻게 벡터화된 방법으로 출력값을 계산하는지 배웠죠. 여기서 중요한 것은 어떻게 이 편미분을 계산하는지입니다. dw^[1]과 db^[1], 그리고 dw^[2], db^[2]까지 말이죠. 이제 제가 하려고 하는 것은 이 도함수를 계산할 수 있도록 몇 가지 식을 드리는 것입니다. 그리고 이 식들이 어떻게 나왔는지는 다음 강의에서 설명하는 것으로 하겠습니다.
여기에 정방향 전파에 대한 식들을 다시 정리해보겠습니다. z^[1]은 w^[1]X + b^[1]이 됩니다. 각 층에서의 활성화 함수인 A^[1]은 z^[1]에 대한 함수이므로 이렇게 됩니다. z^[2]는 w^[2] A^[1] + b^[2]가 되고, A^[2]는 g^2가 됩니다. 여기서는 이진 분류를 하고 있기 때문에 이 활성화 함수는 실제로는 시그모이드 함수가 될 것입니다. 이것이 정방향 전파에 대한 식입니다. 또는 신경망에서 왼쪽에서 오른쪽으로의 진행 과정이라고 할 수 있습니다.
이제 도함수를 계산해보겠습니다. 이것은 역전파 단계에 관한 것이죠. 먼저 dz^[2]를 계산하는데, 이것은 A^[2]에서 참 값 Y를 뺀 것과 같습니다. 여기선 모두 벡터화된 샘플을 사용하기 때문에 행렬 Y는 (1, m) 행렬이 됩니다. m가지의 모든 샘플을 가로로 나열하는 것이죠.
이제 dw^[2]는 이런 형태의 식이 됩니다. 이 세 개의 식은 로지스틱 회귀에서의 경사 하강법과 굉장히 비슷합니다. 여기서 axis=1이 되고 keepdims는 True가 됩니다.
np.sum은 파이썬 numpy 명령어인데, 행렬의 어떤 축 방향으로 덧셈을 계산할 때 사용합니다. 이 경우에는 가로로 더하는 것이죠. keepdims가 하는 역할은 파이썬이 잘못된 1차원 배열을 출력하지 않게 하는 것입니다. 여기에서는 (n, )의 차원을 가지게 됩니다. 따라서 keepdims를 True로 설정하면 db^[2]에 대한 파이썬의 출력값은 (n, 1) 벡터가 될 것입니다. 정확히는 (n^[2], 1)이 되겠죠. 그리고 이 경우에는 (1, 1)이 되기 때문에 영향은 미치지 않습니다. 하지만 나중에 이것이 중요한 경우가 있습니다.
지금까지 로지스틱 회귀와 굉장히 비슷한 것을 했습니다. 역전파 계산을 계속 진행해보겠습니다. 이것을 계산하면 w^[2]ᵗ dz^[2] * g^[1]'(z^[1])이 됩니다. 이 g^[1]'은 은닉층에서 사용했던 활성화 함수의 도함수입니다. 출력층에서는 시그모이드 함수를 이용해 이진 분류를 합니다. dz^[2]는 이미 앞서 구했습니다. 이 곱셈은 요소별 곱셈입니다. 이 부분은 (n^[1], m) 행렬이 되고, 이 부분 또한 (n^[1], m) 행렬이 됩니다. 따라서 이 곱셈은 두 행렬의 요소별 곱셈이 되어야 하는 것이죠.
그리고 마지막으로 dw^[1]은 이런 형태가 되고, db^[1]은 이렇게 됩니다. np.sum(dz^[1], axis=1, keepdims=True)가 되죠. 위에서 n^[2]가 1일 때 keepdims는 영향이 없다고 했습니다. 단순히 (1, 1) 행렬이기 때문이죠. 여기서 db^[1]은 (n^[1], 1) 벡터가 됩니다. 파이썬의 np.sum을 사용한다면 그 출력값도 같은 차원의 벡터가 되겠죠. 잘못된 형태의 1차원 배열은 아닐 것입니다. 그 1차원 배열은 나중에 계산을 어렵게 만들 수도 있습니다. keepdims 변수를 사용하지 않는 다른 방법은 reshape 함수를 호출하는 것입니다. 이것은 np.sum의 출력값을 다시 재배열해서 db에 맞는 차원으로 만들 수 있습니다.
여기까지 정방향 전파에는 네 개의 식을 사용했고, 역전파에는 여섯 개의 식을 사용했습니다. 제가 여기서는 식들을 써내려가기만 했지만, 다음 강의에서는 이것에 대해서 조금 더 설명하도록 하겠습니다. 역전파 알고리즘에서 어떻게 여섯 개의 식을 유도할 수 있는지 말이죠.
이 강의는 보셔도 좋고, 그렇지 않아도 좋습니다. 굳이 강의를 보지 않더라도 정방향 전파와 역전파 알고리즘을 올바르게 구현할 수 있습니다. 그리고 도함수를 계산하는 것도 가능합니다. 신경망의 변수를 학습시키기 위해 경사 하강법을 사용할 때 말이죠. 미적분학에 깊은 이해가 없이도 이 알고리즘을 구현할 수 있습니다. 이미 많은 성공적인 딥러닝 사용자들이 그렇게 하고 있죠. 하지만 다음 강의를 들으신다면, 이 식들을 어떻게 유도하는지에 대해 더 이해할 수 있을 겁니다.
10 역전파에 대한 이해
지난 시간에는 역전파에 관련된 식들에 대해 배웠습니다. 이번 강의에서는 계산 그래프를 이용해서 어떻게 이 식들이 유도되는지 알아보도록 하겠습니다. 이 강의는 추가적이기 때문에 보셔도 좋고 굳이 보지 않아도 과제를 할 수 있습니다.
로지스틱 회귀에 대해서 이야기했던 것을 다시 생각해 봅시다. 여기에 이런 정방향 패스가 있었습니다. z를 계산하고 a를 계산했습니다. 그리고 손실값을 계산했죠. 그리고 역방향 패스에서는 도함수들을 계산했습니다. 먼저 da를 계산했습니다. 그리고 dz를 계산하고 dw, db를 계산했습니다. 손실값 L(a, y)의 정의는 -y log a - (1 - y) log(1 - a)였습니다. 여러분이 미적분학을 잘 알고 있다면 이것을 a에 대한 도함수로 만들 수 있겠죠. 따라서 da에 대한 식을 얻을 수 있습니다. da는 이것과 같아집니다. 만약 미적분학을 잘 적용하면 이것이 -y / a + (1 - y) / (1 - a)임을 보일 수 있을 겁니다. 이것의 도함수를 구하기 위해서 약간의 미적분학을 사용한 것이죠.
이제 그 이전 단계로 넘어와서 dz를 계산합니다. 우리는 dz는 a - y라고 알고 있습니다. 이것이 왜 그런지는 설명하지 않았지만, 미적분학의 연쇄 법칙을 사용해서 계산하면 그 결과는 dz = da ∙ g'(z)가 됩니다. 여기서 g(z)는 시그모이드 함수입니다. 로지스틱 회귀에서 출력 유닛에 대한 활성화 함수이죠. x₁, x₂, x₃가 있는 로지스틱 회귀에 하나의 시그모이드 유닛이 있으면 이것은 바로 a가 ŷ값이 됩니다. 여기서 이 부분의 활성화 함수는 시그모이드 함수였죠. 연쇄 법칙에 익숙한 분들을 위해 이것에 대해서 더 설명하자면, a는 z에 대한 시그모이드 함수이고, z에 대한 L의 편미분은 a에 대한 L의 편미분에 da/dz를 곱한 것이기 때문입니다. a가 z의 시그모이드 함수이기 때문에 이 부분은 d/dz g(z)와 같습니다. 따라서 이 부분이 g'(z)가 되는 것입니다.
우리 코드에서는 여기가 dz이고, 이 부분이 da가 됩니다. 그리고 여기에 g'(z)를 곱하는 것이죠. 따라서 이 부분이 이것과 같아지게 됩니다. 미적분학과 연쇄 법칙에 익숙하다면 이 부분이 이해가 될 것입니다. 그렇지 않다고 해도 걱정하실 필요는 없습니다. 설명이 필요한 부분에서는 추가로 설명하도록 하겠습니다.
여기까지 로지스틱 회귀의 dz를 계산했습니다. 이제 dw를 계산할 것인데요. 한 가지 훈련 샘플을 사용할 때 이 값은 dz∙x가 되고 db는 dz가 됩니다. 이것은 로지스틱 회귀에 대한 것이었죠. 이제 우리가 할 것은 신경망에서 역전파를 계산할 때 필요한 여러 번의 계산입니다. 여기서는 이것을 두 번 만에 계산할 수 있었습니다. x가 바로 출력층으로 가는 것이 아니고, x가 은닉층을 거쳐서 출력층으로 가는 것이기 때문입니다. 여기서 한 단계 계산하는 것 대신에, 두 개의 층을 가진 신경망에서는 두 단계를 계산하게 됩니다. 입력층, 은닉층, 그리고 출력층이 존재합니다.
우리가 계산했던 단계를 기억해보면 z₁은 이 식을 사용했고, 그리고 a₁을 계산했습니다. z₂를 계산했고 마찬가지로 w^[2]와 b^[2]에 관한 식이 됩니다. z₂를 이용해 a를 계산하고, 마지막으로 손실값을 계산했습니다. 이제 역전파는 이것을 반대 방향으로 진행하는 것이죠. da^[2]를 계산하고 dz^[2]를 계산합니다. 또 뒤로 돌아가서 dw^[2]와 db^[2]를 계산해야 합니다. 그리고 da^[1], dz^[1], ...을 계산합니다. 입력값 x에 대한 도함수는 계산할 필요가 없습니다. 입력값 x는 지도 학습에서 고정된 값이기 때문입니다. 따라서 x를 계산할 필요가 없습니다. 지도 학습에서 x에 대한 도함수는 신경 쓸 필요가 없는 것이죠.
da^[2]를 계산하는 것은 생략하도록 하겠습니다. 원한다면 da^[2]를 계산할 수 있습니다. 그리고 이것을 이용해 dz^[2]를 계산할 수 있죠. 하지만 실제로는 이 두 단계를 한 단계로 계산할 수 있습니다. 이전과 같이 dz^[2]는 a^[2] - y이 됩니다. 그리고 dw^[2]와 db^[2]를 아래에 적어보면, dw^[2]는 dz^[2] a^[1]ᵀ가 되고 db^[2]는 dz^[2]가 됩니다. 이 과정은 로지스틱 회귀에서 했던 것과 상당히 비슷합니다. dw는 dz∙x였고 여기서는 x 자리에 a가 들어갔습니다. 그리고 전치된 행렬을 사용하는데, w 행렬과 우리가 사용하는 변수인 w의 관계 때문입니다. 로지스틱 회귀는 하나의 출력을 가지는 경우 w는 행 벡터가 됩니다. dw^[2]가 이런 식으로 되는데, 여기에선 w가 열 벡터이므로 a^[1]을 전치해주어야 하는 것입니다. 로지스틱 회귀에서는 x에 대해서 그렇게 하지 않았지만 말이죠.
여기까지 역전파의 절반을 끝냈습니다. 마찬가지로 da^[1]를 계산할 수 있지만, 실제로는 da^[1]과 dz^[1]을 한 단계로 계산할 수 있습니다. 따라서 계산해 보면 dz^[1]은 w^[2]ᵀ dz^[2]에 g^[1]'(z^[1])을 요소별 곱셈한 값이 됩니다. 여기서 차원이 올바르게 일치하는지 확인해 봅시다. 만약 이런 형태의 신경망이 있다고 할 때, 출력은 ŷ이라고 합시다. n_x = n^[0]으로 이것을 입력 특성으로 하고, 한 개의 은닉층이 있고, n^[2]가 있다고 할 때, 지금까지 n^[2]는 1의 값을 가지도록 설정했었죠. 이때 행렬 w^[2]는 (n^[2], n^[1]) 차원을 가집니다. 그리고 z^[2]와 dz^[2]는 (n^[2], 1)의 차원을 가지게 되죠. 그리고 이것은 실제로는 (1, 1)이 됩니다. 이진 분류에 한해서 말이죠. z^[1]과 dz^[1]은 (n^[1], 1) 차원을 가지는데, 여기서 foo라는 변수와 dfoo는 항상 같은 차원인 것을 알 수 있죠. 따라서 w와 dw도 같은 차원을 가지게 되는 것입니다. 마찬가지로 b와 db, z와 dz도 동일하게 적용됩니다.
이제 이 차원이 모두 일치하는지 확인하기 위해서, dz^[1]은 w^[2]ᵀ∙dz^[2]에 g^[1]'(z^[1])을 요소별로 곱셈한 값이기 때문에 여기서 차원이 일치하는지 확인해보겠습니다. 먼저 여기는 (n^[1], 1)이 됩니다. w^[2]는 (n^[1], n^[2]) 차원이고 dz^[2]는 (n^[2], 1)이 되죠. 그리고 이 부분은 z^[1]의 차원과 같아서 (n^[1], 1) 차원이 됩니다. 따라서 차원이 정확히 일치하는 것이죠. (n^[1], 1) 차원의 벡터는 (n^[1], n^[2]) 차원과 (n^[2], 1) 차원을 곱하게 되는데, 이 곱셈은 (n^[1], 1) 차원의 행렬을 결과로 가집니다. 따라서 이제 이 두 (n^[1], 1) 행렬의 요소별 곱셈을 계산하면 차원이 일치한다는 것을 알 수 있습니다. 역전파를 구현할 때 한 가지 팁을 드리자면, 차원이 정확히 일치하는지를 확인하는 것입니다. 여러 가지 행렬들의 차원을 보고, w^[1], w^[2], z^[1], z^[2], a^[1], a^[2] 등등에서 이 행렬들의 차원이 연산에서 일치하도록 하는 것입니다. 가끔은 이것으로 역전파에서 오류를 미리 제거할 수 있을 겁니다. 이제 dz^[1]을 계산했습니다. 마지막으로 dw^[1]와 db^[1]을 구할 것인데요. 이쪽에 적는 게 좋겠지만 공간이 부족하므로 슬라이드의 오른쪽에 적도록 하겠습니다.
dw^[1]과 db^[1]은 아래 식에 의해서 계산됩니다. dw^[1]은 dz^[1]∙xᵀ가 되고 db^[1]은 dz^[1]이 됩니다. 그러면 이 식이 여기 있는 식과 비슷하다는 것을 알 수 있습니다. 이것은 우연히 생긴 일이 아닙니다. 이 x가 a^[0]의 역할을 하기 때문이죠. 따라서 xᵀ는 a^[0]ᵀ와 같습니다. 그래서 이 식이 매우 비슷해 보이는 것입니다. 이제 이것은 어떻게 역전파의 식들이 유도되는지를 설명해줍니다. 우리는 여섯 개의 중요한 식을 만들었습니다: dz^[2], dw^[2], db^[2], dz^[1], dw^[1], db^[1]이죠. 이 여섯 개의 식을 그대로 가지고 다음 슬라이드로 가겠습니다. 지금까지 단일 훈련 샘플에서 역전파 식을 도출했습니다. 하지만 실제로는 단일 샘플만을 가지고 하는 것보다는 여러 훈련 샘플을 벡터화하여 처리하는 것이 좋습니다.
정방향 전파의 경우에 한 가지 샘플을 가지고 계산할 때 이런 형태의 식을 사용했었습니다. a^[1] = g^1과 같은 식도 있었죠. 이제 이것을 벡터화시키기 위해서 각 행의 z를 이렇게 나열할 수 있습니다. 이것을 대문자 Z라고 하겠습니다. 각 행을 이렇게 쌓아 올리게 되면, 우리는 이 식들을 각 대문자에 대해서 다시 정의할 수 있습니다. 따라서 Z^[1]은 w^[1]X + b^[1]이 됩니다. 그리고 A^[1]은 g^1이 되는 것이죠.
이 강의에서는 이 표현들을 굉장히 조심스럽게 정의했었죠. 이렇게 행렬의 각 행에 샘플들을 쌓아 올릴 때 이 식들을 모두 적용 가능하도록 하기 위해서였습니다. 따라서 이렇게 표현할 수 있는 것입니다.
이 수학적 방법을 이용하면 역전파에도 적용할 수 있습니다. 따라서 벡터화된 식들은 이런 형태가 될 것입니다. 먼저 이 dz에 여러 훈련 샘플을 적용하기 위해 행렬의 각 열에 대해 쌓아 올리면 이것들에 대해서도 동일하게 적용됩니다. 그러면 이렇게 벡터화된 형태를 얻을 수 있습니다. 이것은 dW^[2]를 어떻게 계산하는지를 보여줍니다. 여기엔 추가적으로 1/m이 들어갑니다. 비용 함수 J가 1부터 m까지의 손실 함수의 합을 m으로 나눈 것이기 때문입니다. 따라서 도함수를 계산할 때 추가적으로 1/m이 붙게 됩니다. 로지스틱 회귀에서 했던 것과 비슷한 것이죠. 이제 이 부분은 db^[2]에 대해 새롭게 바뀐 식이 됩니다. 동일하게 dZ 값들의 합을 m으로 나눈 것이 되겠죠. 그리고 dZ^[1]은 이렇게 됩니다. 이 부분은 다시 말씀드리자면 요소별 곱셈을 의미하는데, 이전과 비교해보자면 이전의 슬라이드에서는 (n^[1], 1) 차원을 가지는 벡터였고, 이제 이 부분은 (n^[1], m) 차원의 벡터가 될 것입니다. 그러면 이 부분도 마찬가지로 (n^[1], m) 차원이 되겠죠. 따라서 여기서도 마찬가지로 요소별 곱셈이 필요하죠.
마지막으로 나머지 두 개의 식은 특별한 부분 없이 이런 형태가 됩니다. 이제 어떻게 역전파 알고리즘을 만드는지 어느 정도 이해되었길 바랍니다. 머신러닝에서 역전파 알고리즘은 가장 수학적으로 복잡한 부분 중 하나라고 생각합니다. 선형 대수뿐만 아니라 행렬의 미분까지 알아야 하기 때문이죠. 첫 번째 식에서 마지막 부분까지 유도하기 위해서 말입니다. 행렬의 미적분학에 능숙하다면 이 알고리즘을 이 과정을 따라서 한 번 유도해보시길 바랍니다. 많은 딥러닝 사용자들도 이런 과정을 거쳤습니다. 여기서 이 강의에서 공부한 수준은 효과적으로 알고리즘을 구현하기 위한 모든 내용을 이미 담고 있죠.
만약 미적분학이 익숙하지 않다면 이 전체적 과정을 보기만 해도 좋습니다. 머신러닝에 관련된 것 중 가장 수학적으로 어려운 부분일 것입니다. 하지만 보기만 하더라도 이것이 작동하도록 구현할 수 있습니다. 저는 여러분이 이것이 잘 작동할 수 있도록 충분히 이해했을 것이라고 생각합니다. 이제 신경망을 구현하기 전에 필요한 마지막 한 가지 내용이 남아있습니다. 신경망의 가중치를 어떻게 초기화하는지에 대해서인데요. 각 변수를 0이 아닌 임의의 값으로 초기화하게 됩니다. 신경망을 훈련하는 데 있어서 굉장히 중요한 부분이죠. 다음 강의에서 이것에 대해 보도록 하겠습니다.
11 랜덤 초기화
신경망을 훈련시킬 때 변수를 임의값으로 초기화하는 것은 중요합니다. 로지스틱 회귀의 경우 모두 0으로 초기화하여도 괜찮지만, 신경망에서 모두 0으로 초기화하고 경사 하강법을 적용할 경우 올바르게 동작하지 않을 것입니다. 이제 그 이유를 살펴보겠습니다.
여기에서는 두 개의 입력 특성을 가지는데요. 따라서 n^[0]는 2가 됩니다. 그리고 두 개의 은닉층이 있고 n^[1]은 2의 값을 가집니다. 이 은닉층에 관한 행렬 w^[1]은 (2, 2) 행렬이 될 것입니다. 이 값을 모두 0으로 초기화했다고 해보겠습니다. 모두 0인 (2, 2) 행렬이 됩니다. b^[1] 또한 0으로 초기화하겠습니다. b를 0으로 초기화하는 것은 실제로는 괜찮습니다. 하지만 w까지 모두 0으로 초기화하는 것이 문제가 될 수 있죠.
여기서 발생하는 문제는 어떤 샘플의 경우에도 a^[1]_1과 a^[1]_2가 같은 값을 가진다는 것입니다. 따라서 이 두 개의 활성이 같은 것이 됩니다. 두 은닉 유닛 모두 정확히 같은 함수를 계산하기 때문이죠. 그리고 역전파를 계산할 때 dz^[1]_1과 dz^[1]_2 또한 같은 결과를 가지게 됩니다. 대칭적인 결과를 가지는 것이죠. 두 은닉 유닛이 같은 값으로 초기화되기 때문에 가중치의 결과값이 항상 같다는 것이죠.
w^[2]는 [0 0]가 될 것입니다. 이 신경망을 이렇게 초기화하는 경우에 이 유닛과 이 유닛은 완전히 같은 것이 됩니다. 따라서 이것을 완전 대칭이라고 말할 수 있습니다. 완전히 같은 함수를 계산한다는 것이죠.
수학적 귀납법을 사용하면 각 훈련의 반복마다 두 은닉 유닛은 항상 같은 함수를 계산한다는 것을 알 수 있습니다. dw가 이런 형태의 행렬이라고 하겠습니다. 각 열이 모두 같은 값을 가집니다. 이제 가중치를 계산하면 w^[1] = w^[1]-αdw가 됩니다. 그러면 각 반복 이후의 w^[1]은 첫 번째 열이 두 번째 열과 같아지게 됩니다.
따라서 수학적 귀납법에 의해 w의 값을 모두 0으로 초기화할 경우 두 은닉 유닛이 같은 함수를 계산하는 것으로 시작하기 때문에 두 은닉 유닛이 출력 유닛에 항상 같은 영향을 주게 되고 첫 번째 반복 이후에 같은 상태가 계속해서 반복됩니다. 두 은닉 유닛은 계속 대칭적이므로 수학적 귀납에 의해서 두 번째, 세 번째, ... 반복에 대해서 신경망이 얼마나 많은 훈련을 하는지에 상관 없이 두 은닉 유닛은 항상 같은 함수를 가지는 것이죠. 따라서 이 경우에 은닉 유닛이 실제로 한 개라고 할 수 있습니다. 항상 같은 것을 계산하기 때문이죠.
또한 더 큰 규모를 가지는 신경망의 경우를 보면 세 개의 입력 특성이 있고 매우 많은 은닉 유닛이 있는 경우에도 비슷한 논리가 적용됩니다. 이런 형태의 신경망이 있을 때 모든 간선을 그리지는 않겠습니다. 모든 값을 0으로 초기화한다면 모든 은닉 유닛은 대칭이 되고 경사 하강법을 얼마나 적용시키는지에 상관 없이 모든 유닛은 항상 같은 함수를 계산하게 될 것입니다. 따라서 이것은 쓸모 없게 되는 것이죠. 다른 함수를 계산하기 위한 각각 다른 유닛이 필요하기 때문입니다.
이것의 해결 방법은 변수를 임의로 초기화하는 것입니다. 이제 이것을 어떻게 하는지 설명하도록 하겠습니다. w^[1]을 np.random.randn으로 설정합니다. 이 함수는 가우시안 랜덤 변수를 생성합니다. (2, 2) 행렬로 설정합니다. 일반적으로 이 값에 0.01과 같은 굉장히 작은 수를 곱해줍니다. 따라서 굉장히 작은 임의의 수를 초기값으로 만들어낼 수 있습니다.
b^[1]의 경우 방금과 같은 대칭의 문제를 가지지 않습니다. 이 문제를 대칭 회피 문제라고 부릅니다. 따라서 b^[1]을 0으로 초기화하는 것은 괜찮습니다. w가 이미 임의의 값으로 초기화되었기 때문에 다른 은닉 유닛에서 계산은 다른 결과를 만들어 낼 것이고, 여기엔 더 이상 대칭 회피 문제가 존재하지 않기 때문이죠.
비슷한 방법으로 w^[2]도 임의의 값으로 초기화할 수 있습니다. b^[2]도 마찬가지로 0으로 초기화하면 됩니다.
그렇다면 이 값은 어떻게 나온 값일까요? 왜 0.01이라는 값을 사용하는 것일까요? 왜 100이나 1000과 같은 숫자를 사용하지 않는 것일까요?
여기서는 가중치의 초기값을 매우 작은 값으로 정하는 것이 좋습니다. 만약 tanh 함수 또는 시그모이드 활성 함수를 사용하거나 출력층에서 시그모이드 형태를 사용한다고 했을 때, 가중치가 너무 큰 값을 가지는 경우 활성값을 계산하면 z^[1]은 w^[1]x + b^[1]이고 활성 함수 a^[1]은 z^[1]에 대한 값이 됩니다. w가 큰 값을 가지는 경우에 z도 굉장히 큰 값을 가지거나 몇몇 값이 굉장히 크거나 굉장히 작은 상태가 될 수 있습니다.
tanh이나 시그모이드 함수에서 이 두꺼운 부분은 경사의 기울기가 매우 낮기 때문에 경사 하강법 또한 매우 느리게 적용됩니다. 따라서 학습 속도가 느려지게 되는 것이죠. 따라서 결론적으로 w가 너무 큰 값을 가지면 매우 큰 값의 z를 이용해 훈련을 시작하게 되고 tanh나 시그모이드 활성 함수가 너무 큰 값을 가지게 되므로 학습의 속도가 느려진다는 것입니다.
시그모이드 또는 tanh 활성 함수를 신경망에 적용하지 않는 경우에는 이 문제는 적어지겠지만, 이진 분류의 경우에는 출력 유닛이 시그모이드 함수이므로 이 때는 초기 변수들이 너무 큰 값을 가지지 않도록 해야 합니다. 이것이 바로 여기에 0.01을 곱하는 이유입니다. 이외에 다른 작은 수를 곱해도 괜찮습니다. w^[2]에 대해서도 똑같이 할 수 있죠. np.random.randn((1,2)) * 0.01이 될 것입니다. 여기 s가 하나 빠졌군요.
가끔은 0.01보다 더 나은 수를 곱하는 경우도 있을 것입니다. 한 개의 은닉층을 가지는 신경망을 훈련시킬 때, 적은 은닉층을 가진 상대적으로 얕은 신경망에서는 0.01을 곱하는 것도 괜찮지만, 매우 깊은 깊이의 신경망을 훈련시키는 경우에는 0.01과는 다른 수를 선택할 수도 있을 것입니다. 다음 주 강의에서 이야기 할 것은 어떤 경우에 0.01이외의 어떤 값을 선택하는지에 대한 것입니다. 보통은 상대적으로 작은 숫자가 되도록 하는 것이죠.
여기까지가 이번 주 강의 내용이었습니다. 신경망의 은닉층을 설정하는 방법과 변수를 초기화하는 것을 배웠습니다. 그리고 정방향 전파를 이용해 예측값을 계산하고 역전파에서 사용하는 경사 하강법에서 도함수를 계산하는 것까지 배웠습니다. 이제 이번 주의 연습과 퀴즈를 충분히 할 수 있을 것입니다. 연습을 재미있게 해보길 바라고 다음 주 강의에서 다시 만나죠.