에서 무엇이 잘 맞는가를 물었고 강에서 경사하강으로 답했습니다. 그런데 거기서 다룬 것은 대개 계수 벡터 하나였습니다.
신경망의 파라미터는 행렬이고, 층마다 하나씩 있습니다. 행렬로 미분한다는 것이 무슨 뜻인지부터 정해야 합니다.
이 규칙 하나와 모양 맞추기만으로 신경망의 모든 그래디언트를 손으로 유도할 수 있습니다. 이 강의는 그 규칙을 세우고 수치 미분으로 하나하나 검산합니다.
문제. 미분의 뜻부터 정합니다.
() 모양을 정리하세요.
() 그래디언트가 입력과 같은 모양임을 확인하세요.
() 대칭이 아니면 어떻게 되는지 보세요.
생각의 실마리. 에서 가 스칼라가 아니거나 가 스칼라가 아니면 결과가 여러 개입니다. 그것들을 어떤 모양으로 담을지를 먼저 약속해야 합니다.
풀이. () 정리합니다.
| 무엇을 무엇으로 | 결과의 모양 | 이름 |
|---|---|---|
| 스칼라를 스칼라로 | 스칼라 | 보통 미분 |
| 스칼라를 벡터로 | 벡터 | 그래디언트 |
| 벡터 을 벡터 으로 | 행렬 | 야코비 |
| 스칼라를 행렬로 | 같은 모양 행렬 | 행렬 그래디언트 |
| 스칼라를 벡터로 두 번 | 행렬 | 헤세 |
규칙은 하나입니다. 결과의 모양은 분자의 모양 곱하기 분모의 모양입니다.
딥러닝에서 손실은 언제나 스칼라입니다. 그래서 둘째 줄과 넷째 줄만 씁니다.
() 그래디언트가 입력과 같은 모양임을 확인합니다. 입력은 차원 벡터입니다.
| 함수 | 입력 모양 | 그래디언트 모양 | 손 계산과의 차 |
|---|---|---|---|
네 경우 모두 입력과 같은 모양입니다. 그리고 손으로 유도한 식과 수치 미분이 소수점 아래 여덟 자리까지 같습니다.
() 가 대칭이 아니면 어떻게 되는지 봅니다.
| 무엇으로 계산 | 수치 미분과의 차 |
|---|---|
는 완전히 틀립니다. 차이가 입니다.
가 대칭일 때만 이 가 되어 두 식이 같아집니다. 를 볼 때는 가 대칭인지 먼저 확인해야 합니다.
이 문제에서 배우는 것. 벡터 미분은 새로운 미적분이 아니라 담는 그릇의 약속입니다. 그리고 자주 쓰는 공식들은 대칭 같은 조건이 숨어 있어 그 조건이 깨지면 조용히 틀립니다.
확인 1-1. 결과의 모양을 정하는 규칙을 쓰세요.
답. 분자의 모양 곱하기 분모의 모양입니다.
확인 1-2. 검산에서 와 의 수치 미분과의 차를 쓰세요.
답. 과 입니다.
확인 1-3. 의 그래디언트를 쓰세요.
답. 입니다.
문제. 층을 지납니다.
() 어느 쪽에서 곱하는지 정리하세요.
() 한 층을 직접 확인하세요.
() 두 층을 이으세요.
() 야코비를 직접 만들지 않는 이유를 보세요.
생각의 실마리. 에서 에 대한 그래디언트를 구할 때 를 왼쪽에서 곱할지 오른쪽에서 곱할지, 전치할지 말지 헷갈립니다. 모양이 답을 정해 줍니다.
풀이. () 정리합니다.
| 앞으로 가는 식 | 무엇에 대한 | 뒤로 가는 식 |
|---|---|---|
| 에 대한 그래디언트 | 곱하기 위쪽 그래디언트 | |
| 에 대한 그래디언트 | 위쪽 그래디언트 곱하기 x^ | |
| 에 대한 그래디언트 | 위쪽 그래디언트 그대로 | |
| 원소별 함수 | 에 대한 그래디언트 | 도함수와 원소별 곱 |
| 에 대한 그래디언트 | 와 원소별 곱 |
모양만 맞춰 보면 어느 쪽에서 곱할지 저절로 정해집니다.
가 이고 위쪽 그래디언트가 차원이면, 결과가 차원이어야 하므로 을 왼쪽에서 곱하는 수밖에 없습니다.
() 한 층을 직접 확인합니다. 손실을 로 두면 위쪽 그래디언트가 입니다.
| 무엇에 대한 | 모양 | 손 계산 식 | 수치 미분과의 차 |
|---|---|---|---|
| 를 열로 를 행으로 | |||
| 그대로 | |||
의 그래디언트가 바깥곱이라는 것이 핵심입니다.
가 차원이고 가 차원이니 바깥곱이 가 되어 와 같은 모양입니다.
강의 바깥곱이 여기서 학습 규칙이 됩니다. 뒤에서 온 신호와 앞에서 온 값을 곱해 놓는 것, 그것이 가중치가 배우는 방식입니다.
() 두 층을 잇습니다. 사이에 를 넣습니다.
| 무엇에 대한 | 모양 | 수치 미분과의 차 |
|---|---|---|
계산은 두 줄입니다.
뒤에서 앞으로 오는 신호가 층마다 을 만나고 활성 도함수와 원소별로 곱해집니다.
이 두 단계가 강 역전파의 전부입니다. 층이 백 개여도 같은 두 줄을 백 번 반복할 뿐입니다.
() 야코비를 직접 만들지 않는 이유를 봅니다.
| 층 크기 | 야코비 원소 수 | 벡터 하나의 원소 수 | 몇 배 |
|---|---|---|---|
크기 층 하나의 야코비가 만 개입니다. 벡터 하나보다 배 큽니다.
그런데 실제로 필요한 것은 야코비 곱하기 벡터 하나뿐입니다. 그 곱은 야코비를 안 만들고 바로 계산할 수 있습니다. 가 정확히 그것입니다.
이 문제에서 배우는 것. 연쇄법칙을 행렬로 쓸 때 외울 것은 없고 맞출 모양만 있습니다. 그리고 자동미분이 빠른 이유는 야코비를 만들지 않고 야코비-벡터 곱만 계산하기 때문입니다.
확인 2-1. 에서 의 그래디언트를 구하는 식을 쓰세요.
답. 을 위쪽 그래디언트에 왼쪽에서 곱합니다.
확인 2-2. 검산에서 의 그래디언트가 어떤 연산인지 쓰세요.
답. 위쪽 그래디언트와 입력의 바깥곱입니다.
확인 2-3. 검산에서 층의 야코비 원소 수를 쓰세요.
답. 개입니다.
문제. 공식을 모읍니다.
() 표로 정리하세요.
() 여섯 식을 수치로 확인하세요.
() 소프트맥스와 교차엔트로피를 합치세요.
() 지수와 로그를 따로 계산하면 어떻게 되는지 보세요.
생각의 실마리. 매번 유도할 수는 없습니다. 몇 개만 외우고 나머지는 연쇄법칙으로 만드는 것이 실무의 방식입니다.
풀이. () 정리합니다.
| 함수 | 미분 | 어디에 쓰나 |
|---|---|---|
| 선형 | ||
| 이차형식 | ||
| 합성 | ||
| A^ | 행렬 미분 | |
| X^ | 가우시안 우도 | |
| 소프트맥스 | \mathrm{diag}(p)-pp^ | 분류 |
() 여섯 식을 모두 수치로 확인합니다.
| 함수 | 수치 미분과의 차 |
|---|---|
| 소프트맥스 야코비 |
여섯 식 모두 수치 미분과 소수점 아래 일곱 자리까지 같습니다.
다섯째 식은 강의 가우시안 우도에서 그대로 쓰입니다. 공분산 행렬을 추정할 때 항의 미분이 바로 이것입니다.
() 소프트맥스와 교차엔트로피를 합칩니다. 정답이 셋째 갈래인 한 점을 봅니다.
| 무엇 | 수치 미분과의 차 |
|---|---|
| 야코비를 거쳐 계산 | |
| 로 바로 |
두 계산이 같습니다. 야코비를 거칠 필요가 없습니다.
| 갈래 | 소프트맥스 값 | 정답 | 그래디언트 |
|---|---|---|---|
강에서 유도한 교차엔트로피의 그래디언트가 예측 빼기 정답입니다.
정답 갈래만 음수이고 나머지는 양수입니다. 정답 쪽 로짓은 올리고 나머지는 내리라는 뜻입니다.
() 그러면 왜 두 연산을 따로 두면 안 되는지 봅니다. 로짓이 커질 때 소프트맥스를 먼저 구하고 로그를 취해 봅니다.
| 가장 큰 로짓 | 먼저 지수 뒤 로그 | 빼고 계산 | 맞는가 |
|---|---|---|---|
| 예 | |||
| 예 | |||
| 예 | |||
| nan | 아니오 |
로짓이 이 되면 지수가 넘쳐 값이 사라집니다.
가장 큰 값을 먼저 빼면 지수의 최댓값이 이 되어 안전합니다.
같은 수식인데 계산 순서만 다릅니다. 그것이 결과를 정합니다. 실제 구현이 소프트맥스와 교차엔트로피를 한 연산으로 묶는 이유가 이것입니다.
이 문제에서 배우는 것. 공식은 몇 개만 외우면 되고 나머지는 조합입니다. 다만 수식이 맞는 것과 계산이 되는 것은 다른 문제여서, 지수와 로그가 섞이면 순서를 바꿔야 합니다.
확인 3-1. 의 미분을 쓰세요.
답. 입니다.
확인 3-2. 검산에서 정답 갈래의 소프트맥스 값과 그래디언트를 쓰세요.
답. 와 입니다.
확인 3-3. 검산에서 로짓이 일 때 먼저 지수를 취한 결과를 쓰세요.
답. nan이 나옵니다.
문제. 표본 여러 개를 한 번에 봅니다.
() 모양을 정리하세요.
() 배치로 계산한 것과 하나씩 더한 것을 견주세요.
() 편향의 그래디언트를 보세요.
() 브로드캐스트가 뒤로 갈 때 어떻게 되는지 보세요.
생각의 실마리. 표본이 개면 손실도 개입니다. 그런데 가중치는 하나뿐입니다. 그러면 개의 기여를 어떻게 합칠지가 정해져야 합니다.
풀이. () 정리합니다.
| 무엇 | 모양 | 배치를 어떻게 |
|---|---|---|
| 입력 | 배치 입력차원 | 행마다 한 표본 |
| 가중치 | 입력차원 출력차원 | 표본과 무관 |
| 출력 | 배치 출력차원 | 행마다 한 표본 |
| 출력 그래디언트 | 배치 출력차원 | 행마다 한 표본 |
| 가중치 그래디언트 | 입력차원 출력차원 | 표본을 다 더함 |
마지막 줄만 다릅니다. 가중치는 모든 표본이 함께 쓰므로 기여가 더해집니다.
() 배치로 계산한 것과 하나씩 더한 것을 견줍니다. 표본 개입니다.
| 어떻게 계산 | 모양 | 첫 원소 |
|---|---|---|
| 배치로 한 번에 | ||
| 표본마다 더함 |
두 결과의 차이는 입니다.
하나가 바깥곱 개를 더한 것과 같습니다. 강의 행렬 곱이 곧 바깥곱의 합이라는 사실이 여기서 쓰입니다.
() 편향의 그래디언트를 봅니다.
| 차원 | 열의 합 | 표본마다 더한 값 |
|---|---|---|
편향은 모든 표본에 같은 값이 더해지므로 그래디언트도 더해집니다. 열마다 합하면 끝입니다.
손실을 배치 평균으로 두면 여기에 배치 크기로 나눕니다. 강에서 이 나눗셈이 그래디언트의 분산을 정합니다.
() 브로드캐스트가 뒤로 갈 때 어떻게 되는지 봅니다. 앞으로 갈 때 한 벡터가 모든 행에 복사됩니다.
| 무엇 | 수치 미분과의 차 |
|---|---|
| 열마다 더한 값 | |
| 더하지 않고 한 행만 |
앞에서 복사한 것은 뒤에서 더해야 합니다. 한 행만 쓰면 만큼 틀립니다.
복사와 합은 서로의 전치입니다. 이것이 브로드캐스트 역전파의 규칙 전부입니다.
이 문제에서 배우는 것. 배치는 새 개념이 아니라 축 하나가 늘어난 것입니다. 규칙은 둘뿐입니다. 표본별로 독립인 것은 그대로 두고, 표본이 공유하는 것은 더합니다.
확인 4-1. 가중치 그래디언트에서 배치를 어떻게 처리하는지 쓰세요.
답. 표본마다의 기여를 모두 더합니다.
확인 4-2. 검산에서 배치로 계산한 것과 표본마다 더한 것의 차이를 쓰세요.
답. 입니다.
확인 4-3. 검산에서 브로드캐스트를 더하지 않고 한 행만 썼을 때의 오차를 쓰세요.
답. 입니다.
문제. 손 계산이 맞는지 확인합니다.
() 절차를 정리하세요.
() 앞차분과 중앙차분을 견주세요.
() 상대 오차로 판정하세요.
() 이 강의를 한 장으로 모으세요.
생각의 실마리. 유도한 식이 맞는지는 수치 미분과 견주면 압니다. 그런데 수치 미분 자체가 오차를 가지므로 어느 정도 차이까지 봐줄지를 정해야 합니다.
풀이. () 정리합니다.
| 무엇을 | 어떻게 |
|---|---|
| 모양부터 본다 | 그래디언트는 입력과 같은 모양 |
| 수치 미분과 견준다 | 중앙 차분을 씁니다 |
| 상대 오차로 본다 | 값이 크면 절대 오차가 큽니다 |
| 한 원소씩 본다 | 어디가 틀렸는지 보입니다 |
| 작게 만들어 본다 | 차원을 이하로 줄입니다 |
() 앞차분과 중앙차분을 견줍니다. 이차형식만 쓰면 중앙차분이 정확히 맞아 차이가 안 보이므로 지수 항을 더해 차 도함수가 살아 있게 합니다.
| 간격 | 앞차분 오차 | 중앙차분 오차 | 몇 배 정확한가 |
|---|---|---|---|
| 10^ | |||
| 10^ | |||
| 10^ | |||
| 10^ |
앞차분은 간격에 비례해 줄고 중앙차분은 간격의 제곱에 비례해 줍니다. 에서 로 갈 때 앞차분은 분의 , 중앙차분은 만분의 이 됩니다.
그런데 간격을 너무 줄이면 다시 나빠집니다. 에서 중앙차분 오차가 로 일 때보다 커졌습니다.
빼기에서 자릿수가 사라지기 때문입니다. 가까운 두 수를 빼면 유효숫자가 날아갑니다. 중앙차분에 간격 근처가 실무의 기본값인 이유입니다.
() 상대 오차로 판정합니다. 같은 식을 크기만 바꿔 잽니다.
| 값의 크기 | 절대 오차 | 상대 오차 |
|---|---|---|
| 10^ | ||
| 10^ | ||
| 10^ |
절대 오차는 값이 커지면 함께 커집니다. 에서 으로 가면 절대 오차가 눈에 띄게 커집니다.
상대 오차는 크기와 무관하게 작게 유지됩니다.
그래서 판정은 상대 오차 이하 같은 기준으로 합니다.
() 이 강의를 한 장으로 모읍니다.
| 무엇 | 한 줄로 |
|---|---|
| 결과의 모양 | 분자 모양 곱하기 분모 모양 |
| 손실은 스칼라 | 그래디언트는 언제나 입력과 같은 모양 |
| 의 그래디언트 | 위쪽 그래디언트와 입력의 바깥곱 |
| 의 그래디언트 | 을 왼쪽에서 곱함 |
| 복사는 합으로 | 브로드캐스트의 역은 더하기 |
| 소프트맥스와 교차엔트로피 | 합치면 예측 빼기 정답 |
| 검산 | 중앙차분과 상대 오차 |
이 문제에서 배우는 것. 손으로 유도한 식은 반드시 수치로 검산합니다. 그리고 검산 자체에도 규칙이 있어서, 중앙차분과 상대 오차를 쓰고 간격을 근처로 둡니다.
확인 5-1. 중앙차분을 쓰는 이유를 쓰세요.
답. 오차가 간격의 제곱에 비례해 줄기 때문입니다.
확인 5-2. 검산에서 간격 과 의 중앙차분 오차를 쓰세요.
답. 와 입니다.
확인 5-3. 검산에서 값의 크기가 일 때 절대 오차와 상대 오차를 쓰세요.
답. 와 입니다.
| 유형 | 무엇을 묻나 | 어디를 보나 |
|---|---|---|
| 모양 규칙 | 분자 곱하기 분모 | 문제 |
| 이차형식 | 대칭이 아니면 틀림 | 문제 |
| 모양 맞추기 | 어느 쪽에서 곱하나 | 문제 |
| 가중치 그래디언트 | 바깥곱 | 문제 |
| 야코비를 안 만듦 | 곱만 필요함 | 문제 |
| 공식 여섯 개 | 수치로 검산 | 문제 |
| 소프트맥스와 교차엔트로피 | 예측 빼기 정답 | 문제 |
| 계산 순서 | 큰 값을 먼저 뺌 | 문제 |
| 배치 | 공유하는 것은 더함 | 문제 |
| 브로드캐스트 | 복사의 역은 합 | 문제 |
| 검산 | 중앙차분과 상대 오차 | 문제 |
한 층의 앞뒤를 한자리에 모읍니다.
| 앞으로 | 뒤로 |
|---|---|
| \nabla_W=\delta_z x^ | |
| (배치) | |
| 의 열 합 |
문제 6. 결과의 모양을 정하는 규칙을 쓰세요.
답. 분자의 모양 곱하기 분모의 모양입니다.
문제 7. 검산에서 와 의 수치 미분과의 차를 쓰세요.
답. 과 입니다.
문제 8. 의 그래디언트를 쓰세요.
답. 입니다.
문제 9. 에서 의 그래디언트를 구하는 식을 쓰세요.
답. 을 위쪽 그래디언트에 왼쪽에서 곱합니다.
문제 10. 검산에서 의 그래디언트가 어떤 연산인지 쓰세요.
답. 위쪽 그래디언트와 입력의 바깥곱입니다.
문제 11. 검산에서 층의 야코비 원소 수를 쓰세요.
답. 개입니다.
문제 12. 의 미분을 쓰세요.
답. 입니다.
문제 13. 검산에서 정답 갈래의 소프트맥스 값과 그래디언트를 쓰세요.
답. 와 입니다.
문제 14. 검산에서 로짓이 일 때 먼저 지수를 취한 결과를 쓰세요.
답. nan이 나옵니다.
문제 15. 검산에서 배치로 계산한 것과 표본마다 더한 것의 차이를 쓰세요.
답. 입니다.
문제 16. 검산에서 브로드캐스트를 더하지 않고 한 행만 썼을 때의 오차를 쓰세요.
답. 입니다.
문제 17. 검산에서 간격 과 의 중앙차분 오차를 쓰세요.
답. 와 입니다.
문제 18. 검산에서 값의 크기가 일 때 절대 오차와 상대 오차를 쓰세요.
답. 와 입니다.
심화 1. 분자 배치와 분모 배치를 정리하세요.
같은 미분을 두 가지 모양으로 적을 수 있습니다.
| 배치 | 의 모양 | 누가 쓰나 |
|---|---|---|
| 분자 배치 | 수학 교재 | |
| 분모 배치 | 통계 교재 |
서로 전치 관계입니다. 이 강의는 딥러닝 관례를 따라 그래디언트를 입력과 같은 모양으로 둡니다.
혼동을 피하는 방법은 하나뿐입니다. 식을 보고 모양이 맞는지 직접 세어 보는 것입니다.
심화 2. 야코비-벡터 곱과 벡터-야코비 곱을 가르세요.
| 무엇 | 무엇을 계산 | 언제 |
|---|---|---|
| 벡터-야코비 곱 | 역전파 | |
| 야코비-벡터 곱 | 순방향 미분 |
출력이 하나이고 입력이 많으면 역전파가 유리합니다. 손실이 스칼라이므로 가 하나면 되고, 한 번 뒤로 가면 모든 입력의 그래디언트를 얻습니다.
입력이 하나이고 출력이 많으면 반대입니다. 신경망은 언제나 앞의 경우여서 역전파를 씁니다.
심화 3. 헤세를 만들지 않고 쓰는 법을 정리하세요.
| 무엇 | 비용 |
|---|---|
| 헤세를 만듦 | 자리 |
| 헤세-벡터 곱 | 그래디언트 두 번 |
강의 뉴턴법이 신경망에서 안 쓰이는 이유가 첫 줄입니다. 파라미터가 백만 개면 헤세가 조 개입니다.
그런데 헤세-벡터 곱만 필요한 알고리즘은 쓸 수 있습니다. 켤레기울기법이 그 예입니다.
심화 4. 원소별 함수의 야코비가 왜 대각인지 보이세요.
이면 는 에만 달렸습니다.
대각행렬을 곱하는 것은 원소별 곱과 같습니다. 그래서 활성함수의 역전파가 곱셈 한 번으로 끝납니다.
소프트맥스는 예외입니다. 가 모든 에 달렸으므로 야코비가 꽉 찹니다. 문제 의 이 그것입니다.
심화 5. 그래디언트가 아니라 미분형식으로 유도하는 법을 정리하세요.
| 단계 | 무엇을 하나 |
|---|---|
| 첫째 | 의 미분을 취합니다 |
| 둘째 | 트레이스 안으로 모읍니다 |
| 셋째 | 앞의 것이 그래디언트입니다 |
모양을 세지 않고도 답이 나옵니다. 복잡한 식에서는 이쪽이 훨씬 빠르고, 를 반복해 쓰는 것이 요령입니다.
심화 6. 이 강의가 다음 강의로 어떻게 이어지는지 정리하세요.
| 이 강의 | 다음 강의에서 |
|---|---|
| 층 하나의 앞뒤 | 강 그래프의 마디 하나 |
| 손으로 이은 두 층 | 강 자동으로 잇기 |
| 바깥곱과 전치 | 강 벡터화된 유도 |
| 배치 축 | 강 미니배치 분산 |
| 계산 순서 | 강 그래디언트 소실 |
강은 이 규칙들을 그래프 위에 올려 자동으로 적용합니다. 이 강의에서 손으로 한 일을 기계가 하게 만드는 것이 다음 세 강의입니다.
정답.
| 기호 | 읽는 법 | 뜻 |
|---|---|---|
| 그래디언트 | gradient | 스칼라를 벡터로 미분한 것입니다 |
| 야코비 | Jacobian | 벡터를 벡터로 미분한 행렬입니다 |
| 헤세 | Hessian | 스칼라를 벡터로 두 번 미분한 행렬입니다 |
| 바깥곱 | outer product | 열벡터와 행벡터를 곱해 행렬을 만듭니다 |
| 아다마르 곱 | Hadamard product | 같은 자리끼리 곱합니다 |
| 브로드캐스트 | broadcasting | 작은 배열을 큰 모양에 맞춰 늘립니다 |
| 벡터-야코비 곱 | vector-Jacobian product | 역전파가 실제로 계산하는 것입니다 |
| 중앙 차분 | central difference | 양쪽으로 흔들어 나눈 근사입니다 |
| 상대 오차 | relative error | 크기로 나눠 재는 오차입니다 |
| 로그섬엑스프 | log-sum-exp | 큰 값을 먼저 빼 안전하게 계산합니다 |
다음은 232강 계산 그래프입니다. 이 강의에서 층 두 개를 손으로 이었습니다. 다음 강의는 그 연결을 그래프로 그리고, 강은 그 그래프를 거꾸로 훑는 알고리즘을 세웁니다.
import numpy as np
def pw(s):
return sum(2 if ord(c) > 0x1100 else 1 for c in str(s))
def rw(s, w):
return str(s) + ' ' * max(0, w - pw(s))
def rl(s, w):
return ' ' * max(0, w - pw(s)) + str(s)
def numgrad(f, x, h=1e-6):
g = np.zeros_like(x, dtype=float)
it = np.nditer(x, flags=['multi_index'])
while not it.finished:
i = it.multi_index
a = x.copy(); a[i] += h
b = x.copy(); b[i] -= h
g[i] = (f(a) - f(b)) / (2 * h)
it.iternext()
return g
def maxdiff(a, b):
return float(np.abs(a - b).max())
print("=" * 78)
print("231강 벡터와 행렬에 대한 미분 규칙 코드 검산")
print("=" * 78)
print()
print("문제 1. 무엇에 대해 미분하는가")
print()
print(" (1) 모양을 정리합니다")
rows = [
("스칼라를 스칼라로", "스칼라", "보통 미분"),
("스칼라를 벡터로", "벡터 n", "그래디언트"),
("벡터 m 을 벡터 n 으로", "행렬 m 곱하기 n", "야코비"),
("스칼라를 행렬로", "같은 모양 행렬", "행렬 그래디언트"),
("스칼라를 벡터로 두 번", "행렬 n 곱하기 n", "헤세"),
]
w = [max(pw(r[i]) for r in rows + [("무엇을 무엇으로", "결과의 모양", "이름")]) for i in range(3)]
print(" " + rw("무엇을 무엇으로", w[0]) + " " + rw("결과의 모양", w[1]) + " " + "이름")
for r in rows:
print(" " + rw(r[0], w[0]) + " " + rw(r[1], w[1]) + " " + r[2])
print(" 규칙은 하나입니다. 결과의 모양은 분자의 모양 곱하기 분모의 모양입니다")
print(" 딥러닝에서 손실은 언제나 스칼라이므로 둘째 줄과 넷째 줄만 씁니다")
print()
print(" (2) 그래디언트가 입력과 같은 모양임을 확인합니다")
r = np.random.default_rng(20231)
x = r.normal(0, 1, 5)
A = r.normal(0, 1, (5, 5))
A = A + A.T
b = r.normal(0, 1, 5)
cases = [
("f = b 전치 x", lambda v: float(b @ v), lambda v: b),
("f = x 전치 x", lambda v: float(v @ v), lambda v: 2 * v),
("f = x 전치 A x", lambda v: float(v @ A @ v), lambda v: 2 * A @ v),
("f = 노름 제곱근", lambda v: float(np.sqrt(v @ v)), lambda v: v / np.sqrt(v @ v)),
]
w0 = max(pw(c[0]) for c in cases + [("함수", 0, 0)])
print(" " + rw("함수", w0) + " " + rl("입력 모양", 12) + " " + rl("그래디언트 모양", 16) + " " + rl("손 계산과의 차", 16))
for nm, f, g in cases:
gn = numgrad(f, x)
ga = g(x)
print(" " + rw(nm, w0) + " " + rl(str(x.shape), 12) + " " + rl(str(np.array(ga).shape), 16) + " " + rl("%.10f" % maxdiff(gn, ga), 16))
print(" 네 경우 모두 입력과 같은 모양입니다")
print(" 손으로 유도한 식과 수치 미분이 소수점 아래 여덟 자리까지 같습니다")
print()
print(" (3) A 가 대칭이 아니면 어떻게 되는지 봅니다")
B = r.normal(0, 1, (5, 5))
f2 = lambda v: float(v @ B @ v)
gn = numgrad(f2, x)
print(" " + rw("무엇으로 계산", 24) + " " + rl("수치 미분과의 차", 18))
print(" " + rw("2 곱하기 B x", 24) + " " + rl("%.10f" % maxdiff(gn, 2 * B @ x), 18))
print(" " + rw("B 더하기 B 전치 곱 x", 24) + " " + rl("%.10f" % maxdiff(gn, (B + B.T) @ x), 18))
print(" 대칭이 아니면 2 A x 가 틀립니다. 옳은 식은 A 더하기 A 전치를 x 에 곱한 것입니다")
print(" 대칭일 때만 두 식이 같아집니다")
print(" A 가 대칭이면 A 더하기 A 전치가 2 A 이기 때문입니다")
print()
print("문제 2. 연쇄법칙을 행렬로")
print()
print(" (1) 어느 쪽에서 곱하는지 정리합니다")
rows = [
("z = W x", "x 에 대한 그래디언트", "W 전치 곱하기 위쪽 그래디언트"),
("z = W x", "W 에 대한 그래디언트", "위쪽 그래디언트 곱하기 x 전치"),
("z = x + b", "b 에 대한 그래디언트", "위쪽 그래디언트 그대로"),
("z = 원소별 함수", "x 에 대한 그래디언트", "도함수와 원소별 곱"),
("z = x 아다마르 y", "x 에 대한 그래디언트", "y 와 원소별 곱"),
]
w = [max(pw(r[i]) for r in rows + [("앞으로 가는 식", "무엇에 대한", "뒤로 가는 식")]) for i in range(3)]
print(" " + rw("앞으로 가는 식", w[0]) + " " + rw("무엇에 대한", w[1]) + " " + "뒤로 가는 식")
for r in rows:
print(" " + rw(r[0], w[0]) + " " + rw(r[1], w[1]) + " " + r[2])
print(" 모양만 맞춰 보면 어느 쪽에서 곱할지 저절로 정해집니다")
print(" W 가 m 곱하기 n 이고 위쪽 그래디언트가 m 이면 W 전치를 왼쪽에서 곱해야 n 이 됩니다")
print()
print(" (2) 한 층을 직접 확인합니다")
r2g = np.random.default_rng(30231)
nin, nout = 4, 3
W = r2g.normal(0, 1, (nout, nin))
bb = r2g.normal(0, 1, nout)
xv = r2g.normal(0, 1, nin)
u = r2g.normal(0, 1, nout)
loss = lambda Wm, bv, xv_: float(u @ (Wm @ xv_ + bv))
gW = numgrad(lambda Wm: loss(Wm, bb, xv), W)
gb = numgrad(lambda bv: loss(W, bv, xv), bb)
gx = numgrad(lambda v: loss(W, bb, v), xv)
print(" 손실을 u 전치 곱하기 W x 더하기 b 로 두면 위쪽 그래디언트가 u 입니다")
print(" " + rw("무엇에 대한", 16) + " " + rl("모양", 12) + " " + rl("손 계산 식", 24) + " " + rl("수치 미분과의 차", 18))
print(" " + rw("W", 16) + " " + rl(str(W.shape), 12) + " " + rl("u 를 열로 x 를 행으로", 24) + " " + rl("%.10f" % maxdiff(gW, np.outer(u, xv)), 18))
print(" " + rw("b", 16) + " " + rl(str(bb.shape), 12) + " " + rl("u 그대로", 24) + " " + rl("%.10f" % maxdiff(gb, u), 18))
print(" " + rw("x", 16) + " " + rl(str(xv.shape), 12) + " " + rl("W 전치 곱하기 u", 24) + " " + rl("%.10f" % maxdiff(gx, W.T @ u), 18))
print(" W 의 그래디언트가 바깥곱이라는 것이 핵심입니다")
print(" 66강의 바깥곱이 여기서 학습 규칙이 됩니다")
print()
print(" (3) 두 층을 이어 봅니다")
r3 = np.random.default_rng(40231)
W1 = r3.normal(0, 0.7, (5, 4))
W2 = r3.normal(0, 0.7, (3, 5))
x2 = r3.normal(0, 1, 4)
u2 = r3.normal(0, 1, 3)
def tanh(v):
return np.tanh(v)
def fwd(W1m, W2m, xv_):
h = tanh(W1m @ xv_)
return float(u2 @ (W2m @ h))
h = tanh(W1 @ x2)
d2 = u2
gW2a = np.outer(d2, h)
d1 = (W2.T @ d2) * (1 - h ** 2)
gW1a = np.outer(d1, x2)
print(" 두 층 사이에 하이퍼볼릭 탄젠트를 넣습니다")
print(" " + rw("무엇에 대한", 16) + " " + rl("모양", 12) + " " + rl("수치 미분과의 차", 18))
print(" " + rw("W2", 16) + " " + rl(str(W2.shape), 12) + " " + rl("%.10f" % maxdiff(numgrad(lambda M: fwd(W1, M, x2), W2), gW2a), 18))
print(" " + rw("W1", 16) + " " + rl(str(W1.shape), 12) + " " + rl("%.10f" % maxdiff(numgrad(lambda M: fwd(M, W2, x2), W1), gW1a), 18))
print(" " + rw("x", 16) + " " + rl(str(x2.shape), 12) + " " + rl("%.10f" % maxdiff(numgrad(lambda v: fwd(W1, W2, v), x2), W1.T @ d1), 18))
print(" 뒤에서 앞으로 오는 신호가 층마다 W 전치를 만나고 활성 도함수와 원소별로 곱해집니다")
print(" 이 두 단계가 233강 역전파의 전부입니다")
print()
print(" (4) 야코비를 직접 만들지 않는 이유를 봅니다")
sizes = [(64, 64), (256, 256), (1024, 1024), (4096, 4096)]
print(" " + rl("층 크기", 14) + " " + rl("야코비 원소 수", 18) + " " + rl("벡터 하나의 원소 수", 22) + " " + rl("몇 배", 12))
for a, b_ in sizes:
print(" " + rl("%d 곱하기 %d" % (a, b_), 14) + " " + rl("%d" % (a * b_), 18) + " " + rl("%d" % a, 22) + " " + rl("%d" % (a * b_ // a), 12))
print(" 야코비는 층 크기의 제곱만큼 자리를 차지합니다")
print(" 그런데 실제로 필요한 것은 야코비 곱하기 벡터 하나뿐입니다")
print(" 그 곱은 야코비를 안 만들고 바로 계산할 수 있습니다")
print()
print("문제 3. 자주 쓰는 식들")
print()
print(" (1) 표로 모읍니다")
rows = [
("a 전치 x", "a", "선형"),
("x 전치 A x", "A 더하기 A 전치 곱 x", "이차형식"),
("W x 의 제곱합", "2 W 전치 W x", "합성"),
("트레이스 A X", "A 전치", "행렬 미분"),
("로그 행렬식 X", "X 역행렬 전치", "가우시안 우도"),
("소프트맥스", "대각 p 빼기 p 바깥곱 p", "분류"),
]
w = [max(pw(r[i]) for r in rows + [("함수", "미분", "어디에 쓰나")]) for i in range(3)]
print(" " + rw("함수", w[0]) + " " + rw("미분", w[1]) + " " + "어디에 쓰나")
for r in rows:
print(" " + rw(r[0], w[0]) + " " + rw(r[1], w[1]) + " " + r[2])
print()
print(" (2) 여섯 식을 모두 수치로 확인합니다")
r4 = np.random.default_rng(50231)
xv2 = r4.normal(0, 1, 4)
av = r4.normal(0, 1, 4)
Am = r4.normal(0, 1, (4, 4))
Wm = r4.normal(0, 1, (3, 4))
Xm = r4.normal(0, 1, (4, 4))
Sm = Xm @ Xm.T + 4.0 * np.eye(4)
def softmax(v):
e = np.exp(v - v.max())
return e / e.sum()
checks = [
("a 전치 x", lambda v: float(av @ v), av, xv2),
("x 전치 A x", lambda v: float(v @ Am @ v), (Am + Am.T) @ xv2, xv2),
("W x 의 제곱합", lambda v: float((Wm @ v) @ (Wm @ v)), 2 * Wm.T @ Wm @ xv2, xv2),
("트레이스 A X", lambda M: float(np.trace(Am @ M)), Am.T, Xm),
("로그 행렬식 X", lambda M: float(np.log(np.linalg.det(M))), np.linalg.inv(Sm).T, Sm),
]
w0 = max(pw(c[0]) for c in checks + [("함수", 0, 0, 0)])
print(" " + rw("함수", w0) + " " + rl("수치 미분과의 차", 18))
for nm, f, ga, pt in checks:
print(" " + rw(nm, w0) + " " + rl("%.10f" % maxdiff(numgrad(f, pt), ga), 18))
zv = r4.normal(0, 1, 4)
pv = softmax(zv)
Jn = np.zeros((4, 4))
hh = 1e-6
for j in range(4):
a = zv.copy(); a[j] += hh
b_ = zv.copy(); b_[j] -= hh
Jn[:, j] = (softmax(a) - softmax(b_)) / (2 * hh)
Ja = np.diag(pv) - np.outer(pv, pv)
print(" " + rw("소프트맥스 야코비", w0) + " " + rl("%.10f" % maxdiff(Jn, Ja), 18))
print(" 여섯 식 모두 수치 미분과 소수점 아래 일곱 자리까지 같습니다")
print(" 다섯째 식은 156강의 가우시안 우도에서 그대로 쓰입니다")
print()
print(" (3) 소프트맥스와 교차엔트로피를 합치면 무엇이 되는지 봅니다")
yv = np.zeros(4)
yv[2] = 1.0
ce = lambda v: float(-(yv * np.log(softmax(v))).sum())
gn = numgrad(ce, zv)
print(" 정답이 셋째 갈래인 한 점을 봅니다")
print(" " + rw("무엇", 22) + " " + rl("수치 미분과의 차", 18))
print(" " + rw("야코비를 거쳐 계산", 22) + " " + rl("%.10f" % maxdiff(gn, Ja @ (-yv / pv)), 18))
print(" " + rw("p 빼기 y 로 바로", 22) + " " + rl("%.10f" % maxdiff(gn, pv - yv), 18))
print(" 두 계산이 같습니다. 야코비를 거칠 필요가 없습니다")
print(" " + rl("갈래", 8) + " " + rl("소프트맥스 값", 14) + " " + rl("정답", 8) + " " + rl("그래디언트", 12))
for j in range(4):
print(" " + rl("%d" % (j + 1), 8) + " " + rl("%.6f" % pv[j], 14) + " " + rl("%.4f" % yv[j], 8) + " " + rl("%.6f" % (pv[j] - yv[j]), 12))
print(" 203강에서 유도한 교차엔트로피의 그래디언트가 예측 빼기 정답입니다")
print(" 두 연산을 따로 두면 나눗셈 때문에 자릿수가 깨집니다")
print()
print(" (4) 지수와 로그를 따로 계산하면 무너지는 것을 봅니다")
print(" 로짓이 커질 때 소프트맥스를 먼저 구하고 로그를 취해 봅니다")
print(" " + rl("가장 큰 로짓", 14) + " " + rl("먼저 지수 뒤 로그", 20) + " " + rl("빼고 계산", 16) + " " + rl("맞는가", 10))
old_err = np.seterr(over='ignore', divide='ignore', invalid='ignore')
for M in [10.0, 100.0, 500.0, 1000.0]:
zz = np.array([M, M - 1.0, M - 2.0, M - 3.0])
e = np.exp(zz)
naive = float(np.log(e[0] / e.sum()))
mx = zz.max()
stable = float(zz[0] - (mx + np.log(np.exp(zz - mx).sum())))
ok = "예" if np.isfinite(naive) and abs(naive - stable) < 1e-9 else "아니오"
ns = "%.10f" % naive if np.isfinite(naive) else str(naive)
print(" " + rl("%.1f" % M, 14) + " " + rl(ns, 20) + " " + rl("%.10f" % stable, 16) + " " + rl(ok, 10))
np.seterr(**old_err)
print(" 로짓이 500 을 넘으면 지수가 넘쳐 값이 사라집니다")
print(" 가장 큰 값을 먼저 빼면 지수의 최댓값이 1 이 되어 안전합니다")
print(" 같은 수식인데 계산 순서만 다릅니다. 그것이 결과를 정합니다")
print(" 실제 구현이 소프트맥스와 교차엔트로피를 한 연산으로 묶는 이유가 이것입니다")
print("문제 4. 배치를 함께 다루기")
print()
print(" (1) 모양을 정리합니다")
rows = [
("입력", "배치 곱하기 입력차원", "행마다 한 표본"),
("가중치", "입력차원 곱하기 출력차원", "표본과 무관"),
("출력", "배치 곱하기 출력차원", "행마다 한 표본"),
("출력 그래디언트", "배치 곱하기 출력차원", "행마다 한 표본"),
("가중치 그래디언트", "입력차원 곱하기 출력차원", "표본을 다 더함"),
]
w = [max(pw(r[i]) for r in rows + [("무엇", "모양", "배치를 어떻게")]) for i in range(3)]
print(" " + rw("무엇", w[0]) + " " + rw("모양", w[1]) + " " + "배치를 어떻게")
for r in rows:
print(" " + rw(r[0], w[0]) + " " + rw(r[1], w[1]) + " " + r[2])
print(" 마지막 줄만 다릅니다. 가중치는 모든 표본이 함께 쓰므로 기여가 더해집니다")
print()
print(" (2) 배치로 계산한 것과 하나씩 더한 것을 견줍니다")
r5 = np.random.default_rng(60231)
B, din, dout = 6, 4, 3
Xb = r5.normal(0, 1, (B, din))
Wb = r5.normal(0, 1, (din, dout))
Ub = r5.normal(0, 1, (B, dout))
gW_batch = Xb.T @ Ub
gW_loop = np.zeros((din, dout))
for i in range(B):
gW_loop += np.outer(Xb[i], Ub[i])
print(" " + rw("어떻게 계산", 22) + " " + rl("모양", 12) + " " + rl("첫 원소", 14))
print(" " + rw("배치로 한 번에", 22) + " " + rl(str(gW_batch.shape), 12) + " " + rl("%.6f" % gW_batch[0, 0], 14))
print(" " + rw("표본마다 더함", 22) + " " + rl(str(gW_loop.shape), 12) + " " + rl("%.6f" % gW_loop[0, 0], 14))
print(" 두 결과의 차이는 %.10f 입니다" % maxdiff(gW_batch, gW_loop))
print(" X 전치 곱하기 U 하나가 바깥곱 6 개를 더한 것과 같습니다")
print(" 90강의 행렬 곱이 곧 바깥곱의 합이라는 사실이 여기서 쓰입니다")
print()
print(" (3) 편향의 그래디언트를 봅니다")
gb_batch = Ub.sum(axis=0)
print(" 편향은 모든 표본에 같은 값이 더해지므로 그래디언트도 더해집니다")
print(" " + rl("차원", 8) + " " + rl("열의 합", 14) + " " + rl("표본마다 더한 값", 18))
for j in range(dout):
print(" " + rl("%d" % (j + 1), 8) + " " + rl("%.6f" % gb_batch[j], 14) + " " + rl("%.6f" % sum(Ub[i, j] for i in range(B)), 18))
print(" 손실을 배치 평균으로 두면 여기에 배치 크기로 나눕니다")
print(" 235강에서 이 나눗셈이 그래디언트의 분산을 정합니다")
print()
print(" (4) 브로드캐스트가 뒤로 갈 때 합이 되는 것을 봅니다")
r6 = np.random.default_rng(70231)
bvec = r6.normal(0, 1, dout)
f3 = lambda v: float((Ub * (Xb @ Wb + v)).sum())
gnum = numgrad(f3, bvec)
print(" 앞으로 갈 때 한 벡터가 모든 행에 복사됩니다")
print(" " + rw("무엇", 26) + " " + rl("수치 미분과의 차", 18))
print(" " + rw("열마다 더한 값", 26) + " " + rl("%.10f" % maxdiff(gnum, Ub.sum(axis=0)), 18))
print(" " + rw("더하지 않고 한 행만", 26) + " " + rl("%.10f" % maxdiff(gnum, Ub[0]), 18))
print(" 앞에서 복사한 것은 뒤에서 더해야 합니다")
print(" 복사와 합은 서로의 전치입니다. 이것이 브로드캐스트 역전파의 규칙 전부입니다")
print()
print("문제 5. 검산하는 법")
print()
print(" (1) 절차를 정리합니다")
rows = [
("모양부터 본다", "그래디언트는 입력과 같은 모양"),
("수치 미분과 견준다", "중앙 차분을 씁니다"),
("상대 오차로 본다", "값이 크면 절대 오차가 큽니다"),
("한 원소씩 본다", "어디가 틀렸는지 보입니다"),
("작게 만들어 본다", "차원을 3 이하로 줄입니다"),
]
w = [max(pw(r[i]) for r in rows + [("무엇을", "어떻게")]) for i in range(2)]
print(" " + rw("무엇을", w[0]) + " " + "어떻게")
for r in rows:
print(" " + rw(r[0], w[0]) + " " + r[1])
print()
print(" (2) 앞차분과 중앙차분을 견줍니다")
r7 = np.random.default_rng(80231)
xq = r7.normal(0, 1, 3)
Aq = r7.normal(0, 1, (3, 3))
Aq = Aq + Aq.T
fq = lambda v: float(v @ Aq @ v + np.exp(v).sum())
ga = 2 * Aq @ xq + np.exp(xq)
print(" 이차형식만 쓰면 중앙차분이 정확히 맞아 차이가 안 보입니다")
print(" 지수 항을 더해 3 차 도함수가 살아 있게 합니다")
print(" " + rl("간격", 12) + " " + rl("앞차분 오차", 16) + " " + rl("중앙차분 오차", 16) + " " + rl("몇 배 정확한가", 16))
for hh2 in [1e-2, 1e-4, 1e-6, 1e-8]:
gf = np.zeros(3)
gc = np.zeros(3)
for j in range(3):
a = xq.copy(); a[j] += hh2
b_ = xq.copy(); b_[j] -= hh2
gf[j] = (fq(a) - fq(xq)) / hh2
gc[j] = (fq(a) - fq(b_)) / (2 * hh2)
ef, ec = maxdiff(gf, ga), maxdiff(gc, ga)
print(" " + rl("%.0e" % hh2, 12) + " " + rl("%.10f" % ef, 16) + " " + rl("%.10f" % ec, 16) + " " + rl("%.2f" % (ef / max(ec, 1e-15)), 16))
print(" 앞차분은 간격에 비례해 줄고 중앙차분은 간격의 제곱에 비례해 줍니다")
print(" 그런데 간격을 너무 줄이면 빼기에서 자릿수가 사라져 다시 나빠집니다")
print(" 중앙차분에 간격 1e-6 근처가 실무의 기본값입니다")
print()
print(" (3) 상대 오차로 판정합니다")
r8 = np.random.default_rng(90231)
scales = [1e-3, 1.0, 1e3]
print(" 같은 식을 크기만 바꿔 잽니다")
print(" " + rl("값의 크기", 12) + " " + rl("절대 오차", 16) + " " + rl("상대 오차", 16))
for sc in scales:
xs = r8.normal(0, 1, 3) * sc
As = r8.normal(0, 1, (3, 3))
As = As + As.T
fs = lambda v: float(v @ As @ v)
gnum2 = numgrad(fs, xs, 1e-6 * max(sc, 1.0))
gana = 2 * As @ xs
den = np.maximum(np.abs(gnum2) + np.abs(gana), 1e-12)
print(" " + rl("%.0e" % sc, 12) + " " + rl("%.10f" % maxdiff(gnum2, gana), 16) + " " + rl("%.10f" % float((np.abs(gnum2 - gana) / den).max()), 16))
print(" 절대 오차는 값이 커지면 함께 커집니다")
print(" 상대 오차는 크기와 무관하게 작게 유지됩니다")
print(" 그래서 판정은 상대 오차 1e-5 이하 같은 기준으로 합니다")
print()
print(" (4) 이 강의를 한 장으로 모읍니다")
rows = [
("결과의 모양", "분자 모양 곱하기 분모 모양"),
("손실은 스칼라", "그래디언트는 언제나 입력과 같은 모양"),
("W 의 그래디언트", "위쪽 그래디언트와 입력의 바깥곱"),
("x 의 그래디언트", "W 전치를 왼쪽에서 곱함"),
("복사는 합으로", "브로드캐스트의 역은 더하기"),
("소프트맥스와 교차엔트로피", "합치면 예측 빼기 정답"),
("검산", "중앙차분과 상대 오차"),
]
w = [max(pw(r[i]) for r in rows + [("무엇", "한 줄로")]) for i in range(2)]
print(" " + rw("무엇", w[0]) + " " + "한 줄로")
for r in rows:
print(" " + rw(r[0], w[0]) + " " + r[1])
print(" 232강은 이 규칙들을 그래프 위에 올려 자동으로 적용합니다")
print()
print("=" * 78)
print("검산 끝")
print("=" * 78)
# ==============================================================================
# 231강 벡터와 행렬에 대한 미분 규칙 코드 검산
# ==============================================================================
#
# 문제 1. 무엇에 대해 미분하는가
#
# (1) 모양을 정리합니다
# 무엇을 무엇으로 결과의 모양 이름
# 스칼라를 스칼라로 스칼라 보통 미분
# 스칼라를 벡터로 벡터 n 그래디언트
# 벡터 m 을 벡터 n 으로 행렬 m 곱하기 n 야코비
# 스칼라를 행렬로 같은 모양 행렬 행렬 그래디언트
# 스칼라를 벡터로 두 번 행렬 n 곱하기 n 헤세
# 규칙은 하나입니다. 결과의 모양은 분자의 모양 곱하기 분모의 모양입니다
# 딥러닝에서 손실은 언제나 스칼라이므로 둘째 줄과 넷째 줄만 씁니다
#
# (2) 그래디언트가 입력과 같은 모양임을 확인합니다
# 함수 입력 모양 그래디언트 모양 손 계산과의 차
# f = b 전치 x (5,) (5,) 0.0000000002
# f = x 전치 x (5,) (5,) 0.0000000003
# f = x 전치 A x (5,) (5,) 0.0000000012
# f = 노름 제곱근 (5,) (5,) 0.0000000002
# 네 경우 모두 입력과 같은 모양입니다
# 손으로 유도한 식과 수치 미분이 소수점 아래 여덟 자리까지 같습니다
#
# (3) A 가 대칭이 아니면 어떻게 되는지 봅니다
# 무엇으로 계산 수치 미분과의 차
# 2 곱하기 B x 5.3715854007
# B 더하기 B 전치 곱 x 0.0000000003
# 대칭이 아니면 2 A x 가 틀립니다. 옳은 식은 A 더하기 A 전치를 x 에 곱한 것입니다
# 대칭일 때만 두 식이 같아집니다
# A 가 대칭이면 A 더하기 A 전치가 2 A 이기 때문입니다
#
# 문제 2. 연쇄법칙을 행렬로
#
# (1) 어느 쪽에서 곱하는지 정리합니다
# 앞으로 가는 식 무엇에 대한 뒤로 가는 식
# z = W x x 에 대한 그래디언트 W 전치 곱하기 위쪽 그래디언트
# z = W x W 에 대한 그래디언트 위쪽 그래디언트 곱하기 x 전치
# z = x + b b 에 대한 그래디언트 위쪽 그래디언트 그대로
# z = 원소별 함수 x 에 대한 그래디언트 도함수와 원소별 곱
# z = x 아다마르 y x 에 대한 그래디언트 y 와 원소별 곱
# 모양만 맞춰 보면 어느 쪽에서 곱할지 저절로 정해집니다
# W 가 m 곱하기 n 이고 위쪽 그래디언트가 m 이면 W 전치를 왼쪽에서 곱해야 n 이 됩니다
#
# (2) 한 층을 직접 확인합니다
# 손실을 u 전치 곱하기 W x 더하기 b 로 두면 위쪽 그래디언트가 u 입니다
# 무엇에 대한 모양 손 계산 식 수치 미분과의 차
# W (3, 4) u 를 열로 x 를 행으로 0.0000000005
# b (3,) u 그대로 0.0000000003
# x (4,) W 전치 곱하기 u 0.0000000009
# W 의 그래디언트가 바깥곱이라는 것이 핵심입니다
# 66강의 바깥곱이 여기서 학습 규칙이 됩니다
#
# (3) 두 층을 이어 봅니다
# 두 층 사이에 하이퍼볼릭 탄젠트를 넣습니다
# 무엇에 대한 모양 수치 미분과의 차
# W2 (3, 5) 0.0000000002
# W1 (5, 4) 0.0000000003
# x (4,) 0.0000000001
# 뒤에서 앞으로 오는 신호가 층마다 W 전치를 만나고 활성 도함수와 원소별로 곱해집니다
# 이 두 단계가 233강 역전파의 전부입니다
#
# (4) 야코비를 직접 만들지 않는 이유를 봅니다
# 층 크기 야코비 원소 수 벡터 하나의 원소 수 몇 배
# 64 곱하기 64 4096 64 64
# 256 곱하기 256 65536 256 256
# 1024 곱하기 1024 1048576 1024 1024
# 4096 곱하기 4096 16777216 4096 4096
# 야코비는 층 크기의 제곱만큼 자리를 차지합니다
# 그런데 실제로 필요한 것은 야코비 곱하기 벡터 하나뿐입니다
# 그 곱은 야코비를 안 만들고 바로 계산할 수 있습니다
#
# 문제 3. 자주 쓰는 식들
#
# (1) 표로 모읍니다
# 함수 미분 어디에 쓰나
# a 전치 x a 선형
# x 전치 A x A 더하기 A 전치 곱 x 이차형식
# W x 의 제곱합 2 W 전치 W x 합성
# 트레이스 A X A 전치 행렬 미분
# 로그 행렬식 X X 역행렬 전치 가우시안 우도
# 소프트맥스 대각 p 빼기 p 바깥곱 p 분류
#
# (2) 여섯 식을 모두 수치로 확인합니다
# 함수 수치 미분과의 차
# a 전치 x 0.0000000000
# x 전치 A x 0.0000000007
# W x 의 제곱합 0.0000000063
# 트레이스 A X 0.0000000002
# 로그 행렬식 X 0.0000000006
# 소프트맥스 야코비 0.0000000000
# 여섯 식 모두 수치 미분과 소수점 아래 일곱 자리까지 같습니다
# 다섯째 식은 156강의 가우시안 우도에서 그대로 쓰입니다
#
# (3) 소프트맥스와 교차엔트로피를 합치면 무엇이 되는지 봅니다
# 정답이 셋째 갈래인 한 점을 봅니다
# 무엇 수치 미분과의 차
# 야코비를 거쳐 계산 0.0000000002
# p 빼기 y 로 바로 0.0000000002
# 두 계산이 같습니다. 야코비를 거칠 필요가 없습니다
# 갈래 소프트맥스 값 정답 그래디언트
# 1 0.282952 0.0000 0.282952
# 2 0.355653 0.0000 0.355653
# 3 0.017992 1.0000 -0.982008
# 4 0.343403 0.0000 0.343403
# 203강에서 유도한 교차엔트로피의 그래디언트가 예측 빼기 정답입니다
# 두 연산을 따로 두면 나눗셈 때문에 자릿수가 깨집니다
#
# (4) 지수와 로그를 따로 계산하면 무너지는 것을 봅니다
# 로짓이 커질 때 소프트맥스를 먼저 구하고 로그를 취해 봅니다
# 가장 큰 로짓 먼저 지수 뒤 로그 빼고 계산 맞는가
# 10.0 -0.4401896986 -0.4401896986 예
# 100.0 -0.4401896986 -0.4401896986 예
# 500.0 -0.4401896986 -0.4401896986 예
# 1000.0 nan -0.4401896986 아니오
# 로짓이 500 을 넘으면 지수가 넘쳐 값이 사라집니다
# 가장 큰 값을 먼저 빼면 지수의 최댓값이 1 이 되어 안전합니다
# 같은 수식인데 계산 순서만 다릅니다. 그것이 결과를 정합니다
# 실제 구현이 소프트맥스와 교차엔트로피를 한 연산으로 묶는 이유가 이것입니다
# 문제 4. 배치를 함께 다루기
#
# (1) 모양을 정리합니다
# 무엇 모양 배치를 어떻게
# 입력 배치 곱하기 입력차원 행마다 한 표본
# 가중치 입력차원 곱하기 출력차원 표본과 무관
# 출력 배치 곱하기 출력차원 행마다 한 표본
# 출력 그래디언트 배치 곱하기 출력차원 행마다 한 표본
# 가중치 그래디언트 입력차원 곱하기 출력차원 표본을 다 더함
# 마지막 줄만 다릅니다. 가중치는 모든 표본이 함께 쓰므로 기여가 더해집니다
#
# (2) 배치로 계산한 것과 하나씩 더한 것을 견줍니다
# 어떻게 계산 모양 첫 원소
# 배치로 한 번에 (4, 3) -0.120033
# 표본마다 더함 (4, 3) -0.120033
# 두 결과의 차이는 0.0000000000 입니다
# X 전치 곱하기 U 하나가 바깥곱 6 개를 더한 것과 같습니다
# 90강의 행렬 곱이 곧 바깥곱의 합이라는 사실이 여기서 쓰입니다
#
# (3) 편향의 그래디언트를 봅니다
# 편향은 모든 표본에 같은 값이 더해지므로 그래디언트도 더해집니다
# 차원 열의 합 표본마다 더한 값
# 1 -0.617771 -0.617771
# 2 0.920823 0.920823
# 3 -0.612040 -0.612040
# 손실을 배치 평균으로 두면 여기에 배치 크기로 나눕니다
# 235강에서 이 나눗셈이 그래디언트의 분산을 정합니다
#
# (4) 브로드캐스트가 뒤로 갈 때 합이 되는 것을 봅니다
# 앞으로 갈 때 한 벡터가 모든 행에 복사됩니다
# 무엇 수치 미분과의 차
# 열마다 더한 값 0.0000000002
# 더하지 않고 한 행만 0.6578568788
# 앞에서 복사한 것은 뒤에서 더해야 합니다
# 복사와 합은 서로의 전치입니다. 이것이 브로드캐스트 역전파의 규칙 전부입니다
#
# 문제 5. 검산하는 법
#
# (1) 절차를 정리합니다
# 무엇을 어떻게
# 모양부터 본다 그래디언트는 입력과 같은 모양
# 수치 미분과 견준다 중앙 차분을 씁니다
# 상대 오차로 본다 값이 크면 절대 오차가 큽니다
# 한 원소씩 본다 어디가 틀렸는지 보입니다
# 작게 만들어 본다 차원을 3 이하로 줄입니다
#
# (2) 앞차분과 중앙차분을 견줍니다
# 이차형식만 쓰면 중앙차분이 정확히 맞아 차이가 안 보입니다
# 지수 항을 더해 3 차 도함수가 살아 있게 합니다
# 간격 앞차분 오차 중앙차분 오차 몇 배 정확한가
# 1e-02 0.0355156417 0.0000577222 615.29
# 1e-04 0.0003545835 0.0000000058 61449.30
# 1e-06 0.0000035442 0.0000000014 2489.06
# 1e-08 0.0000001355 0.0000000467 2.90
# 앞차분은 간격에 비례해 줄고 중앙차분은 간격의 제곱에 비례해 줍니다
# 그런데 간격을 너무 줄이면 빼기에서 자릿수가 사라져 다시 나빠집니다
# 중앙차분에 간격 1e-6 근처가 실무의 기본값입니다
#
# (3) 상대 오차로 판정합니다
# 같은 식을 크기만 바꿔 잽니다
# 값의 크기 절대 오차 상대 오차
# 1e-03 0.0000000000 0.0000000000
# 1e+00 0.0000000002 0.0000000000
# 1e+03 0.0000033319 0.0000000001
# 절대 오차는 값이 커지면 함께 커집니다
# 상대 오차는 크기와 무관하게 작게 유지됩니다
# 그래서 판정은 상대 오차 1e-5 이하 같은 기준으로 합니다
#
# (4) 이 강의를 한 장으로 모읍니다
# 무엇 한 줄로
# 결과의 모양 분자 모양 곱하기 분모 모양
# 손실은 스칼라 그래디언트는 언제나 입력과 같은 모양
# W 의 그래디언트 위쪽 그래디언트와 입력의 바깥곱
# x 의 그래디언트 W 전치를 왼쪽에서 곱함
# 복사는 합으로 브로드캐스트의 역은 더하기
# 소프트맥스와 교차엔트로피 합치면 예측 빼기 정답
# 검산 중앙차분과 상대 오차
# 232강은 이 규칙들을 그래프 위에 올려 자동으로 적용합니다
#
# ==============================================================================
# 검산 끝
# ==============================================================================