강에서 표현력은 병목이 아니라는 것을 봤습니다. 진짜 병목은 최적화이고, 그 병목의 정체가 이 강의의 주제입니다.
강에서 그 곱이 신호를 줄이거나 키운다는 것을 크기로 봤습니다. 그런데 크기만 보면 놓치는 것이 있습니다.
이 강의는 야코비의 특이값을 직접 재서 무엇이 무너지는지 보고, 그것을 막는 방법들을 견줍니다.
문제. 곱을 봅니다.
() 무엇이 곱해지는지 정리하세요.
() 특이값이 어떻게 변하는지 보세요.
() 크기와 방향을 가르세요.
() 가중치 크기의 갈림길을 찾으세요.
생각의 실마리. 한 층의 야코비는 가중치와 활성 도함수의 곱입니다. 층을 지나면 그것들이 행렬로 곱해집니다. 행렬 곱이 무엇을 하는지는 강의 특이값이 말합니다.
풀이. () 정리합니다.
| 무엇 | 무엇인가 | 어디서 |
|---|---|---|
| 한 층의 야코비 | 가중치와 활성 도함수 | 둘의 곱 |
| 층을 지나면 | 야코비가 곱해짐 | 강 문제 |
| 깊이 이면 | 야코비 개의 곱 | 행렬 곱 |
| 무엇이 정하나 | 그 곱의 특이값 | 강 |
강에서 크기만 봤는데 여기서는 방향까지 봅니다.
() 특이값이 어떻게 변하는지 봅니다. 너비 이고 정착 초기화입니다.
| 층 수 | 계수 | 가장 큰 특이값 | 계수 안 가장 작은 것 | 둘의 비 |
|---|---|---|---|---|
첫 층의 마스크가 개만 켜서 계수가 곧바로 로 떨어집니다. 뒤 층을 아무리 곱해도 이 계수를 되찾을 수 없습니다.
살아남은 아홉 방향 안에서도 가장 큰 것과 가장 작은 것이 계속 벌어집니다.
다섯째 열이 에서 로 층 수에 따라 지수로 커집니다.
가장 큰 특이값은 에서 사이에 머뭅니다. 크기가 유지돼도 방향마다 다르게 늘어나거나 줄어듭니다.
() 크기와 방향을 가릅니다. 무작위 방향의 단위벡터를 넣습니다.
| 층 수 | 노름 평균 | 노름 표준편차 | 표준편차 나누기 평균 |
|---|---|---|---|
평균은 에서 사이로 크게 안 변합니다.
넷째 열은 에서 로 세 배 커집니다. 방향에 따른 편차가 커집니다.
평균만 봐서는 이것이 안 보입니다. 강에서 잰 것이 이 평균이었습니다.
() 가중치 크기의 갈림길을 찾습니다. 깊이 입니다.
| 표준편차 | 정착 대비 | 야코비 노름 | 무엇이 일어나나 |
|---|---|---|---|
| 사라짐 | |||
| 버팀 | |||
| 버팀 | |||
| 터짐 | |||
| 터짐 |
정착 대비 에서 로 세 배 바뀌는 사이 노름이 에서 으로 열다섯 자릿수 움직입니다.
정착 초기화 근처가 갈림길입니다. 조금만 작아도 사라지고 조금만 커도 터집니다.
이 문제에서 배우는 것. 그래디언트 소실과 폭발은 평균 크기의 문제만이 아닙니다. 크기가 유지돼도 계수가 깎이고 조건수가 지수로 커지며, 그러면 일부 방향의 정보가 사실상 사라집니다.
확인 1-1. 층을 지나며 무엇이 곱해지는지 쓰세요.
답. 가중치와 활성 도함수의 곱인 야코비입니다.
확인 1-2. 검산에서 층 과 층 의 특이값 비를 쓰세요.
답. 과 입니다.
확인 1-3. 검산에서 정착 대비 와 의 야코비 노름을 쓰세요.
답. 와 입니다.
문제. 마스크를 봅니다.
() 무엇이 달라지는지 정리하세요.
() 켜진 비율이 노름에 어떻게 드는지 보세요.
() 입력마다 야코비가 다른 것을 보세요.
() 죽은 단위가 계수를 깎는지 보세요.
생각의 실마리. 정류 선형의 도함수는 ** 또는 **입니다. 그것을 야코비에 곱하는 것은 일부 행을 지우는 일입니다.
풀이. () 정리합니다.
| 무엇 | 무엇인가 | 덧붙임 |
|---|---|---|
| 활성 도함수 | 또는 | 마스크 |
| 평균적으로 | 절반이 켜짐 | 분산이 절반 |
| 그래서 | 가중치를 두 배로 | 정착 초기화 |
| 입력마다 | 마스크가 다름 | 같은 신경망도 다른 야코비 |
() 켜진 비율을 바꿔 가며 깊이 의 노름을 봅니다.
| 켜진 비율 | 곱하기 비율 | 야코비 노름 | 무엇이 일어나나 |
|---|---|---|---|
| 사라짐 | |||
| 버팀 | |||
| 버팀 | |||
| 터짐 | |||
| 터짐 |
곱하기 켜진 비율이 근처일 때만 노름이 버팁니다.
정류 선형이 평균 절반을 켜므로 그 자리가 정확히 절반입니다.
그래서 정착 초기화가 나누기 너비를 씁니다. 강에서 이것을 유도합니다.
() 같은 신경망도 입력마다 야코비가 다른 것을 봅니다. 깊이 입니다.
| 입력 | 켜진 단위 비율 | 야코비 노름 | 첫 입력과의 차이 |
|---|---|---|---|
켜진 비율은 에서 로 비슷한데 야코비의 차이는 노름 자체만큼 큽니다.
강에서 조각마다 선형이라 했는데, 조각마다 야코비가 다른 것입니다.
그래서 신경망은 전체로는 비선형입니다. 한 점에서의 선형화가 다른 점에서는 안 통합니다.
() 죽은 단위가 계수를 깎는지 봅니다. 층마다 일부를 강제로 끕니다.
| 층마다 죽은 단위 수 | 야코비 계수 | 최대 계수 대비 |
|---|---|---|
죽은 단위 수만큼 계수가 정확히 깎입니다.
계수가 줄면 그 방향의 정보가 아예 안 지나갑니다. 작아지는 것이 아니라 ****입니다.
강 문제 의 죽은 단위가 여기서 계수 손실로 나타납니다.
이 문제에서 배우는 것. 정류 선형의 마스크는 양날입니다. 켜진 비율이 절반이라 정착 초기화가 성립하지만, 동시에 야코비의 계수를 절반으로 깎습니다.
확인 2-1. 정착 초기화가 나누기 너비인 이유를 쓰세요.
답. 정류 선형이 평균 절반을 켜서 그것을 되돌려야 하기 때문입니다.
확인 2-2. 검산에서 켜진 비율 와 의 야코비 노름을 쓰세요.
답. 과 입니다.
확인 2-3. 검산에서 층마다 개 죽었을 때의 야코비 계수를 쓰세요.
답. 입니다.
문제. 방법들을 견줍니다.
() 정리하세요.
() 잔차 연결이 야코비를 어떻게 바꾸는지 보세요.
() 직교 초기화를 쓰세요.
() 실제 학습에서 견주세요.
생각의 실마리. 문제 에서 최소 특이값이 으로 갔습니다. 그것을 막으려면 야코비에 이 될 수 없는 무엇을 넣어야 합니다.
풀이. () 정리합니다.
| 무엇 | 무엇을 하나 | 어디서 |
|---|---|---|
| 초기화 | 특이값을 근처로 | 강 |
| 정규화 | 층마다 크기를 되돌림 | 강과 강 |
| 잔차 연결 | 항등 경로를 더함 | 강 |
| 직교 초기화 | 특이값을 정확히 로 | 이 강의 문제 |
() 잔차 연결이 야코비를 어떻게 바꾸는지 봅니다.
| 층 수 | 보통 노름 | 잔차 노름 | 보통 최소 특이값 | 잔차 최소 특이값 |
|---|---|---|---|---|
보통 신경망은 최소 특이값이 층 에서 이미 정확히 입니다.
잔차 연결이 있으면 층 에서도 로 남아 있습니다. 아주 작지만 은 아닙니다.
단위행렬이 더해져 있어 완전히 사라질 수 없기 때문입니다.
다만 잔차 노름이 으로 터집니다. 순전파도 같이 커지므로 스케일을 함께 다뤄야 합니다.
() 직교 초기화를 씁니다. 가중치를 직교행렬로 두면 특이값이 모두 입니다.
| 층 수 | 정규 초기화 노름 | 직교 초기화 노름 | 직교의 최소 특이값 |
|---|---|---|---|
직교 초기화는 노름을 에서 사이로 잘 지킵니다. 정규 초기화가 층에서 로 떨어지는 것과 대조됩니다.
그런데 최소 특이값은 여전히 입니다. 정류 선형이 절반을 끄면 그만큼 계수가 깎이기 때문입니다.
그래서 직교 초기화는 탄젠트 계열에서 더 잘 듭니다. 거기서는 마스크가 없어 계수가 안 깎입니다.
() 실제 학습에서 견줍니다. 학습률은 네 개 격자에서 최선을 고릅니다. 잔차 분기에는 을 곱합니다.
| 깊이 | 정규 초기화 | 직교 초기화 | 잔차 연결 | 스케일 없는 잔차 |
|---|---|---|---|---|
스케일 없는 잔차는 깊이 부터 모든 학습률에서 터집니다. 정류 선형이 위로 안 막혀 층마다 값이 쌓이기 때문입니다.
스케일을 넣으면 깊이 에서 으로 가장 좋습니다.
깊이 에서는 정규 초기화가 가장 좋았는데 에서 순서가 뒤집힙니다.
이 문제에서 배우는 것. 잔차 연결은 야코비에 단위행렬을 넣어 최소 특이값을 지킵니다. 다만 그것만으로는 순전파가 터지므로, 스케일이나 정규화를 함께 써야 합니다.
확인 3-1. 잔차 연결이 최소 특이값을 지키는 이유를 쓰세요.
답. 야코비가 단위행렬 더하기 무엇이라 완전히 사라질 수 없기 때문입니다.
확인 3-2. 검산에서 층 일 때 보통과 잔차의 최소 특이값을 쓰세요.
답. 과 입니다.
확인 3-3. 검산에서 깊이 일 때 잔차 연결과 스케일 없는 잔차의 정확도를 쓰세요.
답. 과 입니다.
문제. 진단합니다.
() 무엇을 재야 하는지 정리하세요.
() 층별 노름을 재세요.
() 학습 중 변화를 보세요.
() 갱신 크기를 보세요.
생각의 실마리. 문제 부터 까지는 무작위 가중치로 봤습니다. 실제 학습에서도 같은지 확인해야 합니다.
풀이. () 정리합니다.
| 무엇 | 무엇을 보나 | 기준 |
|---|---|---|
| 층별 노름 | 어디서 사라지는지 | 가장 먼저 |
| 층별 노름의 비 | 층 사이 균형 | 이면 좋음 |
| 가중치 대비 갱신 크기 | 얼마나 움직이나 | 근처가 표준 |
| 학습 중 변화 | 초기와 나중 | 시간에 따라 |
() 깊이 신경망의 학습 시작 시점입니다.
| 층 | 그래디언트 노름 | 가중치 노름 | 비 |
|---|---|---|---|
마지막 층의 비가 이고 나머지는 대입니다.
넷째 열이 층마다 세 자릿수 다릅니다.
같은 학습률을 쓰면 뒤쪽만 움직이고 앞쪽은 거의 안 움직입니다.
() 학습이 진행되며 어떻게 변하는지 봅니다.
| 걸음 | 첫 층 노름 | 마지막 층 노름 | 첫 층 나누기 마지막 |
|---|---|---|---|
비가 에서 로 백육십 배 변합니다.
처음에는 뒤쪽이 크고 나중에는 앞쪽이 큽니다. 뒤쪽 층이 먼저 맞춰지고 나면 앞쪽이 움직이기 시작합니다.
초기 상태만 보고 판단하면 안 됩니다.
() 갱신 크기를 봅니다. 학습률 으로 한 걸음입니다.
| 층 | 갱신 노름 | 가중치 노름 | 상대 갱신 | 판정 |
|---|---|---|---|---|
| 너무 작음 | ||||
| 너무 작음 | ||||
| 너무 작음 | ||||
| 너무 작음 | ||||
| 너무 큼 |
앞쪽 네 층이 모두 대이고 마지막 층만 입니다.
같은 걸음에서 어떤 층은 안 움직이고 어떤 층은 퍼센트씩 움직입니다.
층마다 학습률을 달리 두거나 정규화를 넣어야 하는 이유입니다.
이 문제에서 배우는 것. 그래디언트 진단은 노름 하나가 아니라 가중치 대비 상대 크기로 봐야 합니다. 그리고 학습 중에 계속 변하므로 한 시점만 보면 안 됩니다.
확인 4-1. 그래디언트를 진단할 때 무엇으로 재는지 쓰세요.
답. 가중치 노름 대비 상대 갱신 크기로 잽니다.
확인 4-2. 검산에서 걸음 과 의 첫 층 나누기 마지막을 쓰세요.
답. 와 입니다.
확인 4-3. 검산에서 층 과 층 의 상대 갱신을 쓰세요.
답. 와 입니다.
문제. 실제로 고칩니다.
() 진단 순서를 정리하세요.
() 그래디언트 자르기를 넣으세요.
() 무엇이 진짜 답인지 견주세요.
() 이 단원을 한 장으로 모으세요.
생각의 실마리. 문제 에서 층마다 갱신 크기가 세 자릿수 달랐습니다. 그것을 어떻게 고칠까요.
풀이. () 정리합니다.
| 순서 | 무엇을 하나 |
|---|---|
| 먼저 | 층별 그래디언트 노름을 찍습니다 |
| 사라지면 | 초기화와 활성함수를 봅니다 |
| 터지면 | 학습률을 낮추고 자르기를 넣습니다 |
| 층 사이가 안 맞으면 | 정규화나 잔차 연결을 넣습니다 |
| 그래도 안 되면 | 깊이를 줄입니다 |
() 그래디언트 자르기를 넣습니다. 깊이 입니다.
| 학습률 | 자르기 없이 | 자르기 |
|---|---|---|
학습률 에서만 자르기가 도움이 됩니다. 가 가 됩니다.
과 에서는 자르기가 있어도 못 배웁니다.
자르기만으로 학습이 좋아지지는 않습니다. 강 심화 에서 증상을 자르는 것이라 한 이유입니다.
() 무엇이 진짜 답인지 견줍니다. 깊이 이고 학습률은 네 개 격자에서 최선을 고릅니다.
| 무엇을 했나 | 따로 뗀 정확도 |
|---|---|
| 아무것도 없이 | |
| 그래디언트 자르기 | |
| 직교 초기화 | |
| 잔차 연결 |
학습률을 제대로 고르면 깊이 도 아무것도 없이 까지 갑니다.
강 문제 에서 본 대로 학습률 하나가 크게 듭니다.
그 위에서 잔차 연결이 으로 가장 좋습니다.
자르기와 직교 초기화는 오히려 조금 낮습니다. 증상을 늦출 뿐이기 때문입니다.
() 이 단원을 한 장으로 모읍니다.
| 강 | 무엇을 물었나 | 한 줄 답 |
|---|---|---|
| 층이 무엇을 하나 | 새 좌표를 만듭니다 | |
| 어느 활성함수인가 | 도함수 최댓값이 인 것 | |
| 층 하나로 되나 | 됩니다. 다만 찾는 것은 다른 문제입니다 | |
| 왜 깊으면 어려운가 | 야코비의 곱이 방향마다 벌어집니다 |
네 강의가 하나의 물음에 답합니다. 무엇을 쌓을 것인가입니다.
이 문제에서 배우는 것. 깊이 문제의 처방에는 순서가 있습니다. 학습률을 먼저 제대로 고르고, 그 위에서 구조를 바꾸는 것이 듣습니다. 자르기 같은 사후 처치는 마지막입니다.
확인 5-1. 그래디언트 자르기가 무엇을 하는지 쓰세요.
답. 큰 학습률에서 발산을 막을 뿐 학습을 좋게 하지는 않습니다.
확인 5-2. 검산에서 깊이 에서 아무것도 없이와 잔차 연결의 정확도를 쓰세요.
답. 과 입니다.
확인 5-3. 깊이 문제의 처방 순서를 쓰세요.
답. 학습률을 먼저 고르고 그 위에서 구조를 바꿉니다.
| 유형 | 무엇을 묻나 | 어디를 보나 |
|---|---|---|
| 야코비의 곱 | 특이값이 벌어짐 | 문제 |
| 계수 손실 | 첫 마스크가 정함 | 문제 |
| 평균 대 방향 | 평균은 안 보여 줌 | 문제 |
| 초기화 갈림길 | 세 배가 열다섯 자릿수 | 문제 |
| 마스크와 정착 초기화 | 곱하기 절반이 | 문제 |
| 입력마다 다른 야코비 | 조각마다 다름 | 문제 |
| 잔차와 최소 특이값 | 이 안 됨 | 문제 |
| 잔차의 순전파 폭발 | 스케일이 필요 | 문제 |
| 층별 상대 갱신 | 세 자릿수 차이 | 문제 |
| 처방 순서 | 학습률 먼저 | 문제 |
핵심 식을 한자리에 모읍니다.
| 무엇이 무너지나 | 무엇이 막나 |
|---|---|
| 노름이 사라지거나 터짐 | 초기화 |
| 계수가 깎임 | 활성함수 선택 |
| 최소 특이값이 | 잔차 연결 |
| 층 사이 불균형 | 정규화 |
| 한 걸음이 너무 큼 | 자르기 |
문제 6. 층을 지나며 무엇이 곱해지는지 쓰세요.
답. 가중치와 활성 도함수의 곱인 야코비입니다.
문제 7. 검산에서 층 과 층 의 특이값 비를 쓰세요.
답. 과 입니다.
문제 8. 검산에서 정착 대비 와 의 야코비 노름을 쓰세요.
답. 와 입니다.
문제 9. 정착 초기화가 나누기 너비인 이유를 쓰세요.
답. 정류 선형이 평균 절반을 켜서 그것을 되돌려야 하기 때문입니다.
문제 10. 검산에서 켜진 비율 와 의 야코비 노름을 쓰세요.
답. 과 입니다.
문제 11. 검산에서 층마다 개 죽었을 때의 야코비 계수를 쓰세요.
답. 입니다.
문제 12. 잔차 연결이 최소 특이값을 지키는 이유를 쓰세요.
답. 야코비가 단위행렬 더하기 무엇이라 완전히 사라질 수 없기 때문입니다.
문제 13. 검산에서 층 일 때 보통과 잔차의 최소 특이값을 쓰세요.
답. 과 입니다.
문제 14. 검산에서 깊이 일 때 잔차 연결과 스케일 없는 잔차의 정확도를 쓰세요.
답. 과 입니다.
문제 15. 검산에서 걸음 과 의 첫 층 나누기 마지막을 쓰세요.
답. 와 입니다.
문제 16. 검산에서 층 과 층 의 상대 갱신을 쓰세요.
답. 와 입니다.
문제 17. 검산에서 깊이 에서 아무것도 없이와 잔차 연결의 정확도를 쓰세요.
답. 과 입니다.
문제 18. 깊이 문제의 처방 순서를 쓰세요.
답. 학습률을 먼저 고르고 그 위에서 구조를 바꿉니다.
심화 1. 동적 등척성을 정리하세요.
| 무엇 | 왜 |
|---|---|
| 노름만 | 부족함 |
| 모든 특이값이 | 모든 방향이 살아남음 |
| 직교 초기화 | 그것을 노림 |
| 정류 선형에서 | 마스크 때문에 안 됨 |
넷째 줄이 문제 의 ()입니다. 직교 초기화로 특이값을 다 로 두어도 마스크가 절반을 지웁니다.
탄젠트 계열에서 직교 초기화가 더 잘 드는 이유입니다.
심화 2. 잔차 연결의 야코비를 전개하세요.
| 항 | 무엇 |
|---|---|
| 아무 층도 안 지난 길 | |
| 한 층만 지난 길 | |
| 두 개 곱 | 두 층 지난 길 |
| 전부 곱 | 층 다 지난 길 |
첫 항이 항상 있어 최소 특이값이 이 안 됩니다.
깊이 인 잔차 신경망은 길이가 다른 개 경로의 앙상블로 볼 수 있습니다. 대부분의 그래디언트가 짧은 경로로 흐릅니다.
심화 3. 정규화가 야코비에 무엇을 하는지 미리 봅니다.
| 무엇 | 어떻게 |
|---|---|
| 층 출력을 정규화 | 분산을 로 되돌림 |
| 야코비에 | 크기 조정 항이 들어감 |
| 결과 | 층마다 스케일이 초기화됨 |
곱이 쌓이는 것을 층마다 끊습니다. 문제 의 지수적 발산이 층마다 재설정됩니다.
강과 강에서 정확한 야코비를 유도합니다.
심화 4. 층별 적응 학습률을 정리하세요.
| 무엇 | 왜 |
|---|---|
| 상대 갱신을 맞춤 | 문제 의 () |
| 라스 최적화기 | 이 규칙을 씀 |
| 큰 배치에서 특히 | 강 심화 |
문제 에서 층마다 상대 갱신이 세 자릿수 달랐습니다. 이 규칙은 그것을 강제로 같게 만듭니다.
아담도 비슷한 효과를 냅니다. 좌표마다 그래디언트 크기로 나누기 때문입니다.
심화 5. 순환 신경망에서 왜 더 심한지 정리하세요.
| 무엇 | 왜 |
|---|---|
| 같은 가중치를 반복 | 같은 야코비의 거듭제곱 |
| 고윳값이 정함 | 절댓값이 이 아니면 |
| 시각이 길면 | 지수로 발산하거나 소실 |
| 해법 | 게이트 구조 |
둘째 줄이 핵심입니다. 층마다 다른 가중치면 무작위 곱이라 어느 정도 상쇄되는데, 같은 가중치를 반복하면 정확히 거듭제곱입니다.
강에서 이 문제를 다룹니다.
심화 6. 이 강의가 다음 단원으로 어떻게 이어지는지 정리하세요.
| 이 강의에서 | 다음에서 |
|---|---|
| 나누기 너비 | 강 초기화 유도 |
| 층마다 스케일 재설정 | 강 배치 정규화 |
| 잔차 연결 | 강 |
| 소실과 폭발 | 강 |
| 층별 적응 학습률 | 강 아담 |
단원 전체가 이 강의에서 본 문제들의 처방입니다. 무엇이 무너지는지를 봤으니 이제 어떻게 막는지를 봅니다.
정답.
| 기호 | 읽는 법 | 뜻 |
|---|---|---|
| 야코비 | Jacobian | 벡터를 벡터로 미분한 행렬입니다 |
| 특이값 | singular value | 행렬이 각 방향을 늘이는 배수입니다 |
| 계수 | rank | 정보가 지나갈 수 있는 방향의 수입니다 |
| 마스크 | mask | 정류 선형의 도함수인 과 의 벡터입니다 |
| 정착 초기화 | He initialization | 분산을 나누기 입력차원으로 두는 방법입니다 |
| 직교 초기화 | orthogonal initialization | 가중치를 직교행렬로 두는 방법입니다 |
| 동적 등척성 | dynamical isometry | 모든 특이값이 근처인 상태입니다 |
| 잔차 연결 | residual connection | 층 출력에 입력을 더합니다 |
| 상대 갱신 | relative update | 갱신 노름을 가중치 노름으로 나눈 값입니다 |
| 그래디언트 자르기 | gradient clipping | 노름이 크면 방향만 두고 줄입니다 |
여기까지가 단원 신경망의 구성입니다. 네 강의로 무엇을 쌓을 것인가에 답했습니다. 단원부터는 그 쌓기를 안정시키는 도구들을 봅니다. 이 강의에서 본 소실·폭발·계수 손실·층 사이 불균형이 각각 어떻게 처방되는지가 다음 다섯 강의입니다.
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 relu(z):
return np.maximum(z, 0.0)
print("=" * 78)
print("242강 층을 지나는 그래디언트 코드 검산")
print("=" * 78)
print()
print("문제 1. 야코비의 곱으로 보기")
print()
print(" (1) 무엇이 곱해지는지 정리합니다")
rows = [
("한 층의 야코비", "가중치와 활성 도함수", "둘의 곱"),
("층을 지나면", "야코비가 곱해짐", "231강 문제 2"),
("깊이 L 이면", "야코비 L 개의 곱", "행렬 곱"),
("무엇이 정하나", "그 곱의 특이값", "84강"),
]
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(" 233강에서 크기만 봤는데 여기서는 방향까지 봅니다")
print()
print(" (2) 곱의 특이값이 어떻게 변하는지 봅니다")
rng = np.random.default_rng(20242)
d = 32
print(" 너비 %d 인 층의 야코비를 곱해 갑니다" % d)
print(" 가중치는 표준편차 2 나누기 너비의 제곱근으로 둡니다")
sc = np.sqrt(2.0 / d)
print(" " + rl("층 수", 8) + " " + rl("계수", 8) + " " + rl("가장 큰 특이값", 18) + " " + rl("계수 안 가장 작은 것", 22) + " " + rl("둘의 비", 14))
J = np.eye(d)
Wl = [rng.normal(0, sc, (d, d)) for _ in range(40)]
masks = [(rng.normal(0, 1, d) > 0).astype(float) for _ in range(40)]
for L in range(1, 41):
J = (Wl[L - 1] * masks[L - 1][:, None]) @ J
if L in (1, 2, 5, 10, 20, 40):
sv = np.linalg.svd(J, compute_uv=False)
rk = int((sv > sv[0] * 1e-10).sum())
print(" " + rl("%d" % L, 8) + " " + rl("%d" % rk, 8) + " " + rl("%.4e" % float(sv[0]), 18) + " " + rl("%.4e" % float(sv[rk - 1]), 22) + " " + rl("%.4e" % float(sv[0] / sv[rk - 1]), 14))
print(" 첫 층의 마스크가 %d 개만 켜서 계수가 곧바로 %d 로 떨어집니다" % (int(masks[0].sum()), int(masks[0].sum())))
print(" 뒤 층을 아무리 곱해도 이 계수를 되찾을 수 없습니다")
print(" 살아남은 방향들 안에서도 가장 큰 것과 가장 작은 것이 계속 벌어집니다")
print(" 다섯째 열이 층 수에 따라 지수로 커집니다")
print(" 크기가 유지돼도 방향마다 다르게 늘어나거나 줄어듭니다")
print()
print(" (3) 크기와 방향을 갈라 봅니다")
print(" 무작위 방향의 벡터를 넣어 노름이 어떻게 되는지 봅니다")
print(" " + rl("층 수", 8) + " " + rl("노름 평균", 16) + " " + rl("노름 표준편차", 16) + " " + rl("표준편차 나누기 평균", 22))
J = np.eye(d)
for L in range(1, 41):
J = (Wl[L - 1] * masks[L - 1][:, None]) @ J
if L in (1, 2, 5, 10, 20, 40):
vs = rng.normal(0, 1, (400, d))
vs = vs / np.linalg.norm(vs, axis=1, keepdims=True)
nn = np.linalg.norm(vs @ J.T, axis=1)
print(" " + rl("%d" % L, 8) + " " + rl("%.4e" % float(nn.mean()), 16) + " " + rl("%.4e" % float(nn.std()), 16) + " " + rl("%.6f" % float(nn.std() / nn.mean()), 22))
print(" 층이 깊어질수록 방향에 따른 편차가 커집니다")
print(" 어떤 방향은 크게 늘고 어떤 방향은 거의 사라집니다")
print(" 평균만 봐서는 이것이 안 보입니다")
print()
print(" (4) 가중치 크기를 바꿔 갈림길을 찾습니다")
print(" 표준편차를 바꿔 가며 깊이 30 의 야코비 노름을 봅니다")
print(" " + rl("표준편차", 12) + " " + rl("정착 대비", 12) + " " + rl("야코비 노름", 16) + " " + "무엇이 일어나나")
for q in [0.5, 0.8, 1.0, 1.2, 1.5]:
s0 = q * np.sqrt(2.0 / d)
rr = np.random.default_rng(7)
J = np.eye(d)
for L in range(30):
Wl2 = rr.normal(0, s0, (d, d))
ml = (rr.normal(0, 1, d) > 0).astype(float)
J = (Wl2 * ml[:, None]) @ J
nrm = float(np.linalg.norm(J, 2))
txt = "사라짐" if nrm < 1e-3 else ("터짐" if nrm > 1e3 else "버팀")
print(" " + rl("%.6f" % s0, 12) + " " + rl("%.4f" % q, 12) + " " + rl("%.4e" % nrm, 16) + " " + txt)
print(" 정착 초기화 근처가 갈림길입니다")
print(" 조금만 작아도 사라지고 조금만 커도 터집니다")
print(" 243강에서 왜 2 나누기 너비인지 유도합니다")
print()
print("문제 2. 정류 선형의 마스크가 하는 일")
print()
print(" (1) 무엇이 달라지는지 정리합니다")
rows = [
("활성 도함수", "0 또는 1", "마스크"),
("평균적으로", "절반이 켜짐", "분산이 절반"),
("그래서", "가중치를 두 배로", "정착 초기화"),
("입력마다", "마스크가 다름", "같은 신경망도 다른 야코비"),
]
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) 켜진 비율이 노름에 어떻게 드는지 봅니다")
print(" 마스크에서 켜진 비율을 바꿔 가며 깊이 20 의 노름을 봅니다")
print(" " + rl("켜진 비율", 12) + " " + rl("2 곱하기 비율", 16) + " " + rl("야코비 노름", 16) + " " + "무엇이 일어나나")
for p_on in [0.25, 0.4, 0.5, 0.75, 1.0]:
rr = np.random.default_rng(11)
J = np.eye(d)
for L in range(20):
Wm = rr.normal(0, np.sqrt(2.0 / d), (d, d))
ml = (rr.uniform(0, 1, d) < p_on).astype(float)
J = (Wm * ml[:, None]) @ J
nrm = float(np.linalg.norm(J, 2))
txt = "사라짐" if nrm < 0.1 else ("터짐" if nrm > 10 else "버팀")
print(" " + rl("%.4f" % p_on, 12) + " " + rl("%.4f" % (2 * p_on), 16) + " " + rl("%.4e" % nrm, 16) + " " + txt)
print(" 2 곱하기 켜진 비율이 1 근처일 때만 노름이 버팁니다")
print(" 정류 선형이 평균 절반을 켜므로 그 자리가 정확히 절반입니다")
print(" 그래서 정착 초기화가 2 나누기 너비를 씁니다")
print()
print(" (3) 같은 신경망도 입력마다 야코비가 다른 것을 봅니다")
r2g = np.random.default_rng(30242)
L2 = 8
Ws2 = [r2g.normal(0, np.sqrt(2.0 / d), (d, d)) for _ in range(L2)]
bs2 = [np.zeros(d) for _ in range(L2)]
def jac_at(x):
J = np.eye(d)
h = x.copy()
for i in range(L2):
z = Ws2[i].T @ h
m = (z > 0).astype(float)
J = (Ws2[i].T * m[:, None]) @ J
h = relu(z)
return J
xs = r2g.normal(0, 1, (5, d))
Js = [jac_at(x) for x in xs]
print(" 깊이 %d 신경망에서 서로 다른 입력 다섯 개의 야코비를 봅니다" % L2)
print(" " + rl("입력", 8) + " " + rl("켜진 단위 비율", 16) + " " + rl("야코비 노름", 16) + " " + rl("첫 입력과의 차이", 20))
for i, Ji in enumerate(Js):
h = xs[i].copy()
on = []
for k in range(L2):
z = Ws2[k].T @ h
on.append(float((z > 0).mean()))
h = relu(z)
print(" " + rl("%d" % (i + 1), 8) + " " + rl("%.6f" % float(np.mean(on)), 16) + " " + rl("%.4e" % float(np.linalg.norm(Ji, 2)), 16) + " " + rl("%.4e" % float(np.linalg.norm(Ji - Js[0], 2)), 20))
print(" 켜진 비율이 입력마다 다르고 야코비도 다릅니다")
print(" 239강에서 조각마다 선형이라 했는데 조각마다 야코비가 다른 것입니다")
print(" 그래서 신경망은 전체로는 비선형입니다")
print()
print(" (4) 죽은 단위가 야코비의 계수를 깎는지 봅니다")
print(" 층마다 일부 단위를 강제로 끄고 야코비 계수를 봅니다")
print(" " + rl("층마다 죽은 단위 수", 22) + " " + rl("야코비 계수", 14) + " " + rl("최대 계수 대비", 16))
for dead in [0, 4, 8, 16, 24]:
rr = np.random.default_rng(13)
J = np.eye(d)
for L in range(6):
Wl3 = rr.normal(0, np.sqrt(2.0 / d), (d, d))
ml = np.ones(d)
if dead > 0:
ml[rr.permutation(d)[:dead]] = 0.0
J = (Wl3 * ml[:, None]) @ J
print(" " + rl("%d" % dead, 22) + " " + rl("%d" % int(np.linalg.matrix_rank(J, tol=1e-8)), 14) + " " + rl("%.6f" % (int(np.linalg.matrix_rank(J, tol=1e-8)) / float(d)), 16))
print(" 죽은 단위가 늘면 야코비의 계수가 줄어듭니다")
print(" 계수가 줄면 그 방향의 정보가 아예 안 지나갑니다")
print(" 240강 문제 3 의 죽은 단위가 여기서 계수 손실로 나타납니다")
print()
print("문제 3. 무엇이 깊이를 견디게 하는가")
print()
print(" (1) 방법들을 정리합니다")
rows = [
("초기화", "특이값을 1 근처로", "243강"),
("정규화", "층마다 크기를 되돌림", "244강과 245강"),
("잔차 연결", "항등 경로를 더함", "245강"),
("직교 초기화", "특이값을 정확히 1 로", "이 강의 문제 4"),
]
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) 잔차 연결이 야코비를 어떻게 바꾸는지 봅니다")
print(" 잔차가 있으면 층의 야코비가 단위행렬 더하기 무엇이 됩니다")
print(" " + rl("층 수", 8) + " " + rl("보통 노름", 16) + " " + rl("잔차 노름", 16) + " " + rl("보통 최소 특이값", 20) + " " + rl("잔차 최소 특이값", 20))
for L in [2, 5, 10, 20, 40]:
rr = np.random.default_rng(17)
Jp = np.eye(d)
Jr = np.eye(d)
for k in range(L):
Wl = rr.normal(0, np.sqrt(2.0 / d), (d, d))
ml = (rr.normal(0, 1, d) > 0).astype(float)
A = Wl * ml[:, None]
Jp = A @ Jp
Jr = (np.eye(d) + A) @ Jr
sp = np.linalg.svd(Jp, compute_uv=False)
sr = np.linalg.svd(Jr, compute_uv=False)
print(" " + rl("%d" % L, 8) + " " + rl("%.4e" % float(sp[0]), 16) + " " + rl("%.4e" % float(sr[0]), 16) + " " + rl("%.4e" % float(sp[-1]), 20) + " " + rl("%.4e" % float(sr[-1]), 20))
print(" 보통 신경망은 최소 특이값이 급격히 0 으로 갑니다")
print(" 잔차 연결이 있으면 최소 특이값이 훨씬 잘 버팁니다")
print(" 단위행렬이 더해져 있어 완전히 사라질 수 없기 때문입니다")
print()
print(" (3) 직교 초기화를 써 봅니다")
def rand_orth(n, rr):
A = rr.normal(0, 1, (n, n))
Q, R = np.linalg.qr(A)
return Q * np.sign(np.diag(R))
print(" 가중치를 직교행렬로 두면 특이값이 모두 1 입니다")
print(" " + rl("층 수", 8) + " " + rl("정규 초기화 노름", 20) + " " + rl("직교 초기화 노름", 20) + " " + rl("직교의 최소 특이값", 22))
for L in [5, 10, 20, 40]:
rr = np.random.default_rng(19)
Jn = np.eye(d)
Jo = np.eye(d)
for k in range(L):
Wn = rr.normal(0, np.sqrt(2.0 / d), (d, d))
Wo = rand_orth(d, rr) * np.sqrt(2.0)
ml = (rr.normal(0, 1, d) > 0).astype(float)
Jn = (Wn * ml[:, None]) @ Jn
Jo = (Wo * ml[:, None]) @ Jo
print(" " + rl("%d" % L, 8) + " " + rl("%.4e" % float(np.linalg.norm(Jn, 2)), 20) + " " + rl("%.4e" % float(np.linalg.norm(Jo, 2)), 20) + " " + rl("%.4e" % float(np.linalg.svd(Jo, compute_uv=False)[-1]), 22))
print(" 직교 초기화도 마스크 때문에 완전히는 못 지킵니다")
print(" 정류 선형이 절반을 끄면 그만큼 계수가 깎이기 때문입니다")
print(" 그래서 직교 초기화는 탄젠트 계열에서 더 잘 듭니다")
print()
print(" (4) 실제 학습에서 견줍니다")
r3 = np.random.default_rng(40242)
n3 = 3000
X3 = r3.uniform(-3, 3, (n3, 2))
z3 = np.sin(1.8 * X3[:, 0]) * np.cos(1.8 * X3[:, 1])
lab3 = (z3 > 0).astype(int)
Y3 = np.zeros((n3, 2))
Y3[np.arange(n3), lab3] = 1.0
tr3 = np.arange(2200)
te3 = np.arange(2200, n3)
def softmax(Z):
E = np.exp(Z - Z.max(axis=1, keepdims=True))
return E / E.sum(axis=1, keepdims=True)
def make_net(sizes, rr, kind="normal"):
Ws, bs = [], []
for i in range(len(sizes) - 1):
if kind == "orth" and sizes[i] == sizes[i + 1]:
W = rand_orth(sizes[i], rr) * np.sqrt(2.0)
else:
W = rr.normal(0, np.sqrt(2.0 / sizes[i]), (sizes[i], sizes[i + 1]))
if i == len(sizes) - 2:
W = W * 0.05
Ws.append(W)
bs.append(np.zeros(sizes[i + 1]))
return Ws, bs
def fwd(Ws, bs, X, res=False, alpha=1.0):
pre, ins, outs = [], [], [X]
H = X
for i in range(len(Ws)):
Z = H @ Ws[i] + bs[i]
pre.append(Z)
ins.append(H)
if i < len(Ws) - 1:
A = relu(Z)
H = alpha * A + H if (res and A.shape == H.shape) else A
else:
H = Z
outs.append(H)
return pre, ins, outs
def bwd(Ws, bs, X, Y, res=False, alpha=1.0):
m = len(X)
pre, ins, outs = fwd(Ws, bs, X, res, alpha)
L = len(Ws)
D = (softmax(pre[-1]) - Y) / m
gW = [None] * L
gb = [None] * L
for i in range(L - 1, -1, -1):
Dz = D if i == L - 1 else alpha * D * (pre[i] > 0)
gW[i] = ins[i].T @ Dz
gb[i] = Dz.sum(axis=0)
Dn = Dz @ Ws[i].T
if res and i < L - 1 and ins[i].shape == outs[i + 1].shape:
Dn = Dn + D
D = Dn
return gW, gb
def train(Ws, bs, X, Y, lr, steps, rr, res=False, alpha=1.0):
vW = [np.zeros_like(x) for x in Ws]
vb = [np.zeros_like(x) for x in bs]
m = len(X)
for t in range(steps):
idx = rr.integers(0, m, 64)
gW, gb = bwd(Ws, bs, X[idx], Y[idx], res, alpha)
for i in range(len(Ws)):
vW[i] = 0.9 * vW[i] + gW[i]
vb[i] = 0.9 * vb[i] + gb[i]
Ws[i] -= lr * vW[i]
bs[i] -= lr * vb[i]
return Ws, bs
def acc(Ws, bs, X, lab, res=False, alpha=1.0):
_, _, o = fwd(Ws, bs, X, res, alpha)
return float((o[-1].argmax(axis=1) == lab).mean())
def best_acc(sizes, kind, res, seed=21, alpha=1.0):
best = 0.0
old = np.seterr(over='ignore', invalid='ignore')
for lr in [0.003, 0.01, 0.03, 0.1]:
W, b = make_net(sizes, np.random.default_rng(seed), kind)
W, b = train(W, b, X3[tr3], Y3[tr3], lr, 6000, np.random.default_rng(23), res=res, alpha=alpha)
if np.all([np.all(np.isfinite(x)) for x in W]):
a = acc(W, b, X3[te3], lab3[te3], res=res, alpha=alpha)
if a > best:
best = a
np.seterr(**old)
return best
print(" 깊이를 늘려 가며 세 방식을 견줍니다")
print(" 학습률은 네 개 격자에서 최선을 고릅니다")
print(" 잔차 분기에는 1 나누기 깊이의 제곱근을 곱합니다. 안 그러면 순전파가 터집니다")
print(" " + rl("깊이", 8) + " " + rl("정규 초기화", 14) + " " + rl("직교 초기화", 14) + " " + rl("잔차 연결", 14) + " " + rl("스케일 없는 잔차", 18))
for L in [4, 8, 16, 24]:
sizes = [2] + [24] * (L - 1) + [2]
al = 1.0 / np.sqrt(L)
print(" " + rl("%d" % L, 8) + " " + rl("%.6f" % best_acc(sizes, "normal", False), 14) + " " + rl("%.6f" % best_acc(sizes, "orth", False), 14) + " " + rl("%.6f" % best_acc(sizes, "normal", True, 21, al), 14) + " " + rl("%.6f" % best_acc(sizes, "normal", True, 21, 1.0), 18))
print(" 스케일 없는 잔차는 깊이 16 부터 모든 학습률에서 터집니다")
print(" 정류 선형이 위로 안 막혀 층마다 값이 쌓이기 때문입니다")
print(" 스케일을 넣으면 깊이 24 까지 버팁니다")
print()
print("문제 4. 실제 학습에서 그래디언트를 재기")
print()
print(" (1) 무엇을 재야 하는지 정리합니다")
rows = [
("층별 노름", "어디서 사라지는지", "가장 먼저"),
("층별 노름의 비", "층 사이 균형", "1 이면 좋음"),
("가중치 대비 갱신 크기", "얼마나 움직이나", "0.001 근처가 표준"),
("학습 중 변화", "초기와 나중", "시간에 따라"),
]
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) 층별 그래디언트 노름을 재 봅니다")
L = 16
sizes = [2] + [24] * (L - 1) + [2]
Wn, bn = make_net(sizes, np.random.default_rng(31))
gW, gb = bwd(Wn, bn, X3[tr3][:256], Y3[tr3][:256])
print(" 깊이 %d 신경망의 학습 시작 시점입니다" % L)
print(" " + rl("층", 8) + " " + rl("그래디언트 노름", 18) + " " + rl("가중치 노름", 16) + " " + rl("비", 14))
for i in [0, 3, 7, 11, 15]:
gn = float(np.linalg.norm(gW[i]))
wn = float(np.linalg.norm(Wn[i]))
print(" " + rl("%d" % (i + 1), 8) + " " + rl("%.4e" % gn, 18) + " " + rl("%.4e" % wn, 16) + " " + rl("%.4e" % (gn / wn), 14))
print(" 뒤쪽 층의 그래디언트가 훨씬 큽니다")
print(" 넷째 열이 층마다 몇 자릿수 다릅니다")
print(" 같은 학습률을 쓰면 뒤쪽만 움직이고 앞쪽은 거의 안 움직입니다")
print()
print(" (3) 학습이 진행되며 어떻게 변하는지 봅니다")
Wn, bn = make_net(sizes, np.random.default_rng(31))
rr = np.random.default_rng(33)
vW = [np.zeros_like(x) for x in Wn]
vb = [np.zeros_like(x) for x in bn]
snap = {}
for t in range(1, 6001):
idx = rr.integers(0, len(tr3), 64)
gW, gb = bwd(Wn, bn, X3[tr3][idx], Y3[tr3][idx])
if t in (1, 100, 1000, 6000):
snap[t] = (float(np.linalg.norm(gW[0])), float(np.linalg.norm(gW[-1])))
for i in range(len(Wn)):
vW[i] = 0.9 * vW[i] + gW[i]
vb[i] = 0.9 * vb[i] + gb[i]
Wn[i] -= 0.03 * vW[i]
bn[i] -= 0.03 * vb[i]
print(" " + rl("걸음", 10) + " " + rl("첫 층 노름", 16) + " " + rl("마지막 층 노름", 18) + " " + rl("첫 층 나누기 마지막", 22))
for t in [1, 100, 1000, 6000]:
a, b = snap[t]
print(" " + rl("%d" % t, 10) + " " + rl("%.4e" % a, 16) + " " + rl("%.4e" % b, 18) + " " + rl("%.4e" % (a / b), 22))
print(" 학습이 진행되며 비가 달라집니다")
print(" 초기 상태만 보고 판단하면 안 됩니다")
print()
print(" (4) 갱신 크기가 가중치 대비 얼마인지 봅니다")
Wn2, bn2 = make_net(sizes, np.random.default_rng(31))
gW, gb = bwd(Wn2, bn2, X3[tr3][:256], Y3[tr3][:256])
print(" 학습률 0.03 으로 한 걸음 갈 때의 상대 갱신 크기입니다")
print(" " + rl("층", 8) + " " + rl("갱신 노름", 16) + " " + rl("가중치 노름", 16) + " " + rl("상대 갱신", 16) + " " + "판정")
for i in [0, 3, 7, 11, 15]:
up = 0.03 * float(np.linalg.norm(gW[i]))
wn = float(np.linalg.norm(Wn2[i]))
rel = up / wn
txt = "너무 작음" if rel < 1e-4 else ("너무 큼" if rel > 1e-2 else "적당")
print(" " + rl("%d" % (i + 1), 8) + " " + rl("%.4e" % up, 16) + " " + rl("%.4e" % wn, 16) + " " + rl("%.4e" % rel, 16) + " " + txt)
print(" 앞쪽 층은 상대 갱신이 너무 작아 사실상 안 움직입니다")
print(" 층마다 학습률을 달리 두거나 정규화를 넣어야 하는 이유입니다")
print()
print("문제 5. 실무에서 쓰기")
print()
print(" (1) 진단 순서를 정리합니다")
rows = [
("먼저", "층별 그래디언트 노름을 찍습니다"),
("사라지면", "초기화와 활성함수를 봅니다"),
("터지면", "학습률을 낮추고 자르기를 넣습니다"),
("층 사이가 안 맞으면", "정규화나 잔차 연결을 넣습니다"),
("그래도 안 되면", "깊이를 줄입니다"),
]
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) 그래디언트 자르기를 넣어 봅니다")
def train_clip(Ws, bs, X, Y, lr, steps, rr, clip=None):
vW = [np.zeros_like(x) for x in Ws]
vb = [np.zeros_like(x) for x in bs]
m = len(X)
for t in range(steps):
idx = rr.integers(0, m, 64)
gW, gb = bwd(Ws, bs, X[idx], Y[idx])
if clip is not None:
tot = np.sqrt(sum(float((g ** 2).sum()) for g in gW) + sum(float((g ** 2).sum()) for g in gb))
if tot > clip:
f = clip / tot
gW = [g * f for g in gW]
gb = [g * f for g in gb]
for i in range(len(Ws)):
vW[i] = 0.9 * vW[i] + gW[i]
vb[i] = 0.9 * vb[i] + gb[i]
Ws[i] -= lr * vW[i]
bs[i] -= lr * vb[i]
return Ws, bs
print(" 깊이 16 에서 학습률을 올려 가며 자르기가 있고 없고를 봅니다")
print(" " + rl("학습률", 12) + " " + rl("자르기 없이", 16) + " " + rl("자르기 1.0", 16))
old = np.seterr(over='ignore', invalid='ignore')
for lr in [0.03, 0.1, 0.3, 1.0]:
W1c, b1c = make_net(sizes, np.random.default_rng(41))
W1c, b1c = train_clip(W1c, b1c, X3[tr3], Y3[tr3], lr, 4000, np.random.default_rng(43))
a1 = acc(W1c, b1c, X3[te3], lab3[te3]) if np.all([np.all(np.isfinite(x)) for x in W1c]) else float('nan')
W2c, b2c = make_net(sizes, np.random.default_rng(41))
W2c, b2c = train_clip(W2c, b2c, X3[tr3], Y3[tr3], lr, 4000, np.random.default_rng(43), clip=1.0)
a2 = acc(W2c, b2c, X3[te3], lab3[te3]) if np.all([np.all(np.isfinite(x)) for x in W2c]) else float('nan')
t1 = "%.6f" % a1 if np.isfinite(a1) else "발산"
t2 = "%.6f" % a2 if np.isfinite(a2) else "발산"
print(" " + rl("%.4f" % lr, 12) + " " + rl(t1, 16) + " " + rl(t2, 16))
np.seterr(**old)
print(" 자르기가 큰 학습률에서 발산을 막습니다")
print(" 다만 자르기만으로 학습이 좋아지지는 않습니다")
print(" 233강 심화 3 에서 증상을 자르는 것이라 한 이유입니다")
print()
print(" (3) 무엇이 진짜 답인지 견줍니다")
print(" 깊이 24 에서 네 가지를 견줍니다")
print(" " + rw("무엇을 했나", 24) + " " + rl("따로 뗀 정확도", 16))
sizes24 = [2] + [24] * 23 + [2]
print(" 학습률은 네 개 격자에서 최선을 고릅니다")
old2 = np.seterr(over='ignore', invalid='ignore')
bc = 0.0
for lr in [0.003, 0.01, 0.03, 0.1]:
W, b = make_net(sizes24, np.random.default_rng(51))
W, b = train_clip(W, b, X3[tr3], Y3[tr3], lr, 6000, np.random.default_rng(53), clip=1.0)
if np.all([np.all(np.isfinite(x)) for x in W]):
bc = max(bc, acc(W, b, X3[te3], lab3[te3]))
np.seterr(**old2)
print(" " + rw("아무것도 없이", 24) + " " + rl("%.6f" % best_acc(sizes24, "normal", False, 51), 16))
print(" " + rw("그래디언트 자르기", 24) + " " + rl("%.6f" % bc, 16))
print(" " + rw("직교 초기화", 24) + " " + rl("%.6f" % best_acc(sizes24, "orth", False, 51), 16))
print(" " + rw("잔차 연결", 24) + " " + rl("%.6f" % best_acc(sizes24, "normal", True, 51, 1.0 / np.sqrt(24)), 16))
print(" 학습률을 제대로 고르면 깊이 24 도 아무것도 없이 0.9263 까지 갑니다")
print(" 237강 문제 5 에서 본 대로 학습률 하나가 크게 듭니다")
print(" 그 위에서 잔차 연결이 0.9425 로 가장 좋습니다")
print(" 자르기와 직교 초기화는 오히려 조금 낮습니다. 증상을 늦출 뿐이기 때문입니다")
print()
print(" (4) 이 단원을 한 장으로 모읍니다")
rows = [
("239", "층이 무엇을 하나", "새 좌표를 만듭니다"),
("240", "어느 활성함수인가", "도함수 최댓값이 1 인 것"),
("241", "층 하나로 되나", "됩니다. 다만 찾는 것은 다른 문제입니다"),
("242", "왜 깊으면 어려운가", "야코비의 곱이 방향마다 벌어집니다"),
]
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(" 04 단원부터는 그 쌓기를 안정시키는 도구들을 봅니다")
print()
print("=" * 78)
print("검산 끝")
print("=" * 78)
# ==============================================================================
# 242강 층을 지나는 그래디언트 코드 검산
# ==============================================================================
#
# 문제 1. 야코비의 곱으로 보기
#
# (1) 무엇이 곱해지는지 정리합니다
# 무엇 무엇인가 어디서
# 한 층의 야코비 가중치와 활성 도함수 둘의 곱
# 층을 지나면 야코비가 곱해짐 231강 문제 2
# 깊이 L 이면 야코비 L 개의 곱 행렬 곱
# 무엇이 정하나 그 곱의 특이값 84강
# 그래디언트가 얼마나 줄거나 커지는지는 곱의 특이값이 정합니다
# 233강에서 크기만 봤는데 여기서는 방향까지 봅니다
#
# (2) 곱의 특이값이 어떻게 변하는지 봅니다
# 너비 32 인 층의 야코비를 곱해 갑니다
# 가중치는 표준편차 2 나누기 너비의 제곱근으로 둡니다
# 층 수 계수 가장 큰 특이값 계수 안 가장 작은 것 둘의 비
# 1 9 2.0378e+00 8.7305e-01 2.3341e+00
# 2 9 2.1694e+00 2.7649e-01 7.8461e+00
# 5 9 2.6695e+00 1.1609e-01 2.2995e+01
# 10 9 4.2188e+00 1.0745e-02 3.9262e+02
# 20 9 4.6459e+00 1.1506e-06 4.0377e+06
# 40 9 8.9129e-01 2.1510e-10 4.1435e+09
# 첫 층의 마스크가 9 개만 켜서 계수가 곧바로 9 로 떨어집니다
# 뒤 층을 아무리 곱해도 이 계수를 되찾을 수 없습니다
# 살아남은 방향들 안에서도 가장 큰 것과 가장 작은 것이 계속 벌어집니다
# 다섯째 열이 층 수에 따라 지수로 커집니다
# 크기가 유지돼도 방향마다 다르게 늘어나거나 줄어듭니다
#
# (3) 크기와 방향을 갈라 봅니다
# 무작위 방향의 벡터를 넣어 노름이 어떻게 되는지 봅니다
# 층 수 노름 평균 노름 표준편차 표준편차 나누기 평균
# 1 7.1889e-01 1.6404e-01 0.228179
# 2 6.2152e-01 1.9334e-01 0.311067
# 5 6.8413e-01 2.3643e-01 0.345598
# 10 7.7767e-01 3.7065e-01 0.476620
# 20 6.9046e-01 4.6968e-01 0.680237
# 40 1.5261e-01 8.6472e-02 0.566637
# 층이 깊어질수록 방향에 따른 편차가 커집니다
# 어떤 방향은 크게 늘고 어떤 방향은 거의 사라집니다
# 평균만 봐서는 이것이 안 보입니다
#
# (4) 가중치 크기를 바꿔 갈림길을 찾습니다
# 표준편차를 바꿔 가며 깊이 30 의 야코비 노름을 봅니다
# 표준편차 정착 대비 야코비 노름 무엇이 일어나나
# 0.125000 0.5000 9.3139e-09 사라짐
# 0.200000 0.8000 1.2380e-02 버팀
# 0.250000 1.0000 1.0001e+01 버팀
# 0.300000 1.2000 2.3739e+03 터짐
# 0.375000 1.5000 1.9176e+06 터짐
# 정착 초기화 근처가 갈림길입니다
# 조금만 작아도 사라지고 조금만 커도 터집니다
# 243강에서 왜 2 나누기 너비인지 유도합니다
#
# 문제 2. 정류 선형의 마스크가 하는 일
#
# (1) 무엇이 달라지는지 정리합니다
# 무엇 무엇인가 덧붙임
# 활성 도함수 0 또는 1 마스크
# 평균적으로 절반이 켜짐 분산이 절반
# 그래서 가중치를 두 배로 정착 초기화
# 입력마다 마스크가 다름 같은 신경망도 다른 야코비
#
# (2) 켜진 비율이 노름에 어떻게 드는지 봅니다
# 마스크에서 켜진 비율을 바꿔 가며 깊이 20 의 노름을 봅니다
# 켜진 비율 2 곱하기 비율 야코비 노름 무엇이 일어나나
# 0.2500 0.5000 3.5748e-03 사라짐
# 0.4000 0.8000 3.7868e-01 버팀
# 0.5000 1.0000 2.3018e+00 버팀
# 0.7500 1.5000 4.4328e+02 터짐
# 1.0000 2.0000 6.3333e+03 터짐
# 2 곱하기 켜진 비율이 1 근처일 때만 노름이 버팁니다
# 정류 선형이 평균 절반을 켜므로 그 자리가 정확히 절반입니다
# 그래서 정착 초기화가 2 나누기 너비를 씁니다
#
# (3) 같은 신경망도 입력마다 야코비가 다른 것을 봅니다
# 깊이 8 신경망에서 서로 다른 입력 다섯 개의 야코비를 봅니다
# 입력 켜진 단위 비율 야코비 노름 첫 입력과의 차이
# 1 0.554688 5.0371e+00 0.0000e+00
# 2 0.539062 4.0405e+00 5.5075e+00
# 3 0.542969 5.8721e+00 6.6777e+00
# 4 0.574219 4.8654e+00 5.3086e+00
# 5 0.550781 7.0176e+00 8.0146e+00
# 켜진 비율이 입력마다 다르고 야코비도 다릅니다
# 239강에서 조각마다 선형이라 했는데 조각마다 야코비가 다른 것입니다
# 그래서 신경망은 전체로는 비선형입니다
#
# (4) 죽은 단위가 야코비의 계수를 깎는지 봅니다
# 층마다 일부 단위를 강제로 끄고 야코비 계수를 봅니다
# 층마다 죽은 단위 수 야코비 계수 최대 계수 대비
# 0 32 1.000000
# 4 28 0.875000
# 8 24 0.750000
# 16 16 0.500000
# 24 8 0.250000
# 죽은 단위가 늘면 야코비의 계수가 줄어듭니다
# 계수가 줄면 그 방향의 정보가 아예 안 지나갑니다
# 240강 문제 3 의 죽은 단위가 여기서 계수 손실로 나타납니다
#
# 문제 3. 무엇이 깊이를 견디게 하는가
#
# (1) 방법들을 정리합니다
# 무엇 무엇을 하나 어디서
# 초기화 특이값을 1 근처로 243강
# 정규화 층마다 크기를 되돌림 244강과 245강
# 잔차 연결 항등 경로를 더함 245강
# 직교 초기화 특이값을 정확히 1 로 이 강의 문제 4
#
# (2) 잔차 연결이 야코비를 어떻게 바꾸는지 봅니다
# 잔차가 있으면 층의 야코비가 단위행렬 더하기 무엇이 됩니다
# 층 수 보통 노름 잔차 노름 보통 최소 특이값 잔차 최소 특이값
# 2 2.9435e+00 4.3308e+00 0.0000e+00 3.4135e-02
# 5 4.9759e+00 2.2091e+01 0.0000e+00 1.0902e-03
# 10 3.5481e+00 1.5504e+02 1.9166e-53 6.6980e-06
# 20 1.3284e+00 4.2751e+03 0.0000e+00 3.4854e-10
# 40 7.1148e-01 3.8191e+06 0.0000e+00 2.0009e-11
# 보통 신경망은 최소 특이값이 급격히 0 으로 갑니다
# 잔차 연결이 있으면 최소 특이값이 훨씬 잘 버팁니다
# 단위행렬이 더해져 있어 완전히 사라질 수 없기 때문입니다
#
# (3) 직교 초기화를 써 봅니다
# 가중치를 직교행렬로 두면 특이값이 모두 1 입니다
# 층 수 정규 초기화 노름 직교 초기화 노름 직교의 최소 특이값
# 5 4.5442e+00 2.9335e+00 0.0000e+00
# 10 3.2788e+00 3.9908e+00 0.0000e+00
# 20 1.7286e+00 4.8480e+00 0.0000e+00
# 40 3.5667e-01 4.4707e+00 6.1311e-50
# 직교 초기화도 마스크 때문에 완전히는 못 지킵니다
# 정류 선형이 절반을 끄면 그만큼 계수가 깎이기 때문입니다
# 그래서 직교 초기화는 탄젠트 계열에서 더 잘 듭니다
#
# (4) 실제 학습에서 견줍니다
# 깊이를 늘려 가며 세 방식을 견줍니다
# 학습률은 네 개 격자에서 최선을 고릅니다
# 잔차 분기에는 1 나누기 깊이의 제곱근을 곱합니다. 안 그러면 순전파가 터집니다
# 깊이 정규 초기화 직교 초기화 잔차 연결 스케일 없는 잔차
# 4 0.940000 0.930000 0.925000 0.910000
# 8 0.931250 0.928750 0.928750 0.935000
# 16 0.927500 0.923750 0.931250 0.000000
# 24 0.921250 0.901250 0.941250 0.000000
# 스케일 없는 잔차는 깊이 16 부터 모든 학습률에서 터집니다
# 정류 선형이 위로 안 막혀 층마다 값이 쌓이기 때문입니다
# 스케일을 넣으면 깊이 24 까지 버팁니다
#
# 문제 4. 실제 학습에서 그래디언트를 재기
#
# (1) 무엇을 재야 하는지 정리합니다
# 무엇 무엇을 보나 기준
# 층별 노름 어디서 사라지는지 가장 먼저
# 층별 노름의 비 층 사이 균형 1 이면 좋음
# 가중치 대비 갱신 크기 얼마나 움직이나 0.001 근처가 표준
# 학습 중 변화 초기와 나중 시간에 따라
#
# (2) 층별 그래디언트 노름을 재 봅니다
# 깊이 16 신경망의 학습 시작 시점입니다
# 층 그래디언트 노름 가중치 노름 비
# 1 5.5101e-03 6.3548e+00 8.6708e-04
# 4 1.4848e-02 6.9404e+00 2.1394e-03
# 8 7.7235e-03 7.2263e+00 1.0688e-03
# 12 5.8614e-03 7.0763e+00 8.2831e-04
# 16 1.9764e-01 1.1921e-01 1.6580e+00
# 뒤쪽 층의 그래디언트가 훨씬 큽니다
# 넷째 열이 층마다 몇 자릿수 다릅니다
# 같은 학습률을 쓰면 뒤쪽만 움직이고 앞쪽은 거의 안 움직입니다
#
# (3) 학습이 진행되며 어떻게 변하는지 봅니다
# 걸음 첫 층 노름 마지막 층 노름 첫 층 나누기 마지막
# 1 8.1094e-03 2.6204e-01 3.0947e-02
# 100 1.3554e-01 1.6577e-01 8.1762e-01
# 1000 1.8911e-01 1.2191e-01 1.5512e+00
# 6000 5.2882e-01 1.0738e-01 4.9247e+00
# 학습이 진행되며 비가 달라집니다
# 초기 상태만 보고 판단하면 안 됩니다
#
# (4) 갱신 크기가 가중치 대비 얼마인지 봅니다
# 학습률 0.03 으로 한 걸음 갈 때의 상대 갱신 크기입니다
# 층 갱신 노름 가중치 노름 상대 갱신 판정
# 1 1.6530e-04 6.3548e+00 2.6012e-05 너무 작음
# 4 4.4545e-04 6.9404e+00 6.4183e-05 너무 작음
# 8 2.3171e-04 7.2263e+00 3.2064e-05 너무 작음
# 12 1.7584e-04 7.0763e+00 2.4849e-05 너무 작음
# 16 5.9293e-03 1.1921e-01 4.9740e-02 너무 큼
# 앞쪽 층은 상대 갱신이 너무 작아 사실상 안 움직입니다
# 층마다 학습률을 달리 두거나 정규화를 넣어야 하는 이유입니다
#
# 문제 5. 실무에서 쓰기
#
# (1) 진단 순서를 정리합니다
# 순서 무엇을 하나
# 먼저 층별 그래디언트 노름을 찍습니다
# 사라지면 초기화와 활성함수를 봅니다
# 터지면 학습률을 낮추고 자르기를 넣습니다
# 층 사이가 안 맞으면 정규화나 잔차 연결을 넣습니다
# 그래도 안 되면 깊이를 줄입니다
#
# (2) 그래디언트 자르기를 넣어 봅니다
# 깊이 16 에서 학습률을 올려 가며 자르기가 있고 없고를 봅니다
# 학습률 자르기 없이 자르기 1.0
# 0.0300 0.872500 0.868750
# 0.1000 0.482500 0.562500
# 0.3000 0.517500 0.517500
# 1.0000 0.482500 0.482500
# 자르기가 큰 학습률에서 발산을 막습니다
# 다만 자르기만으로 학습이 좋아지지는 않습니다
# 233강 심화 3 에서 증상을 자르는 것이라 한 이유입니다
#
# (3) 무엇이 진짜 답인지 견줍니다
# 깊이 24 에서 네 가지를 견줍니다
# 무엇을 했나 따로 뗀 정확도
# 학습률은 네 개 격자에서 최선을 고릅니다
# 아무것도 없이 0.926250
# 그래디언트 자르기 0.912500
# 직교 초기화 0.898750
# 잔차 연결 0.942500
# 학습률을 제대로 고르면 깊이 24 도 아무것도 없이 0.9263 까지 갑니다
# 237강 문제 5 에서 본 대로 학습률 하나가 크게 듭니다
# 그 위에서 잔차 연결이 0.9425 로 가장 좋습니다
# 자르기와 직교 초기화는 오히려 조금 낮습니다. 증상을 늦출 뿐이기 때문입니다
#
# (4) 이 단원을 한 장으로 모읍니다
# 강 무엇을 물었나 한 줄 답
# 239 층이 무엇을 하나 새 좌표를 만듭니다
# 240 어느 활성함수인가 도함수 최댓값이 1 인 것
# 241 층 하나로 되나 됩니다. 다만 찾는 것은 다른 문제입니다
# 242 왜 깊으면 어려운가 야코비의 곱이 방향마다 벌어집니다
# 네 강의가 하나의 물음에 답합니다. 무엇을 쌓을 것인가입니다
# 04 단원부터는 그 쌓기를 안정시키는 도구들을 봅니다
#
# ==============================================================================
# 검산 끝
# ==============================================================================