강 문제 에서 드문 칸을 어디서 자를지가 손잡이로 남았습니다. 이 강의는 그런 손잡이를 어떻게 고르는지를 봅니다.
계수는 손실을 최소로 하면 나옵니다. 그런데 벌점 세기를 같은 방식으로 정하면 언제나 이 나옵니다. 벌점이 없을수록 학습 손실이 작기 때문입니다.
이 강의는 무엇을 고를지·어떻게 찾을지·무엇을 기준으로 정할지·예산을 어떻게 아낄지 넷을 봅니다.
문제. 무엇이 손잡이인지 봅니다.
() 배우는 것과 정해 주는 것을 가르세요.
() 학습 손실로 고르면 어떻게 되는지 보세요.
() 손잡이가 얼마나 듣는지 재세요.
생각의 실마리. 리지 회귀는 을 최소로 합니다. 와 를 함께 최소화하면 어떻게 될까요.
풀이. () 가릅니다.
| 무엇 | 어떻게 정하나 | 기준 |
|---|---|---|
| 계수 | 자료에서 배움 | 손실을 최소로 |
| 벌점 세기 | 정해 줌 | 교차검증으로 |
| 나무 깊이 | 정해 줌 | 교차검증으로 |
| 이웃 수 | 정해 줌 | 교차검증으로 |
| 학습률 | 정해 줌 | 교차검증으로 |
| 갈래 문턱 | 정해 줌 | 비용으로 |
첫 줄만 자료가 정하고 나머지는 우리가 정합니다.
왜 못 배웁니까. 학습 손실을 최소로 하면 벌점은 이 되기 때문입니다. 깊이도 마찬가지로 무한히 깊어집니다.
() 표본 개에 변수 개인 자료로 확인합니다.
| 벌점 세기 | 학습 오차 | 교차검증 오차 | 따로 뗀 오차 |
|---|---|---|---|
학습 오차는 벌점이 작을수록 좋아져 에서 가장 작습니다. 그래서 학습 손실로 고르면 벌점 을 고르고 **따로 뗀 오차는 **입니다.
교차검증으로 고르면 벌점 을 고르고 따로 뗀 오차는 입니다. 그리고 이것이 실제로 가장 좋은 값입니다.
손잡이는 반드시 학습에 안 쓴 자료로 골라야 합니다.
() 손잡이 하나가 얼마나 듣는지 잽니다.
| 무엇 | 벌점 세기 | 따로 뗀 오차 |
|---|---|---|
| 가장 나쁜 값 | ||
| 기본값 | ||
| 가장 좋은 값 |
가장 나쁜 값과 가장 좋은 값의 차이가 배입니다.
기본값 을 그냥 쓰면 입니다. 고르면 이 됩니다. 모형을 바꾸는 것보다 손잡이 하나가 크게 듭니다.
이 문제에서 배우는 것. 손잡이는 자료가 정해 줄 수 없는 값입니다. 같은 목적함수 안에서 함께 최적화하면 언제나 벌점이 이고 깊이가 무한한 쪽으로 가기 때문입니다.
확인 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. 검산에서 후보가 개와 개일 때의 실제 따로 뗀 오차를 쓰세요.
답. 와 입니다.
| 유형 | 무엇을 묻나 | 어디를 보나 |
|---|---|---|
| 왜 못 배우나 | 벌점이 이 됨 | 문제 |
| 손잡이가 듣는 크기 | 배 | 문제 |
| 격자 대 무작위 | 몇 개가 듣느냐 | 문제 |
| 로그 눈금 | 배수로 듣는 손잡이 | 문제 |
| 바닥이 평평함 | 겹만 바꿔도 갈림 | 문제 |
| 한 표준오차 규칙 | 간단한 쪽 | 문제 |
| 겹 수 | 겹부터 제자리 | 문제 |
| 작은 자료로 거르기 | 순위만 씀 | 문제 |
| 연속 반감 | 배 쌈 | 문제 |
| 벌점 경로 | 분해 한 번 | 문제 |
| 고른 이득 | 점수가 부풀림 | 문제 |
어느 손잡이를 어떤 눈금에서 찾을지 모읍니다.
| 손잡이 | 어느 눈금 | 흔한 범위 |
|---|---|---|
| 벌점 세기 | 로그 | 에서 10^ |
| 학습률 | 로그 | 에서 10^ |
| 나무 깊이 | 고르게 | 에서 |
| 이웃 수 | 고르게 또는 로그 | 에서 \sqrt |
| 나무 개수 | 안 찾음 | 많을수록 좋음 |
| 드문 칸 문턱 | 고르게 | 에서 |
다섯째 줄이 예외입니다. 강에서 봤듯 나무 개수는 많을수록 좋고 평평해질 뿐이라 교차검증으로 찾을 손잡이가 아닙니다.
문제 6. 벌점 세기를 학습 손실로 정하면 어떤 값이 나오는지 쓰세요.
답. 이 나옵니다.
문제 7. 검산에서 학습 손실로 고른 값과 교차검증으로 고른 값의 따로 뗀 오차를 쓰세요.
답. 와 입니다.
문제 8. 검산에서 가장 나쁜 값과 가장 좋은 값의 배수를 쓰세요.
답. 배입니다.
문제 9. 손잡이가 넷이고 예산이 일 때 격자가 한 축에 몇 개를 쓰는지 쓰세요.
답. 두 개입니다.
문제 10. 검산에서 고르게 뽑았을 때와 로그 눈금으로 뽑았을 때 찾은 값의 중앙값을 쓰세요.
답. 와 입니다.
문제 11. 교차검증 곡선의 바닥이 평평하면 무엇이 일어나는지 쓰세요.
답. 겹을 나누는 방식만 바꿔도 고르는 값이 갈립니다.
문제 12. 검산에서 한 표준오차 규칙이 고른 벌점과 그때 따로 뗀 오차를 쓰세요.
답. 과 입니다.
문제 13. 검산에서 겹 와 겹 의 적합 횟수를 쓰세요.
답. 와 입니다.
문제 14. 검산에서 표본 개와 개의 순위 상관을 쓰세요.
답. 과 입니다.
문제 15. 검산에서 연속 반감과 전체 계산의 적합 표본 수를 쓰세요.
답. 과 입니다.
문제 16. 검산에서 벌점 일 때 두 해의 차이를 쓰세요.
답. 입니다.
문제 17. 검산에서 고를 때 본 점수와 다시 잰 점수의 차이를 쓰세요.
답. 입니다.
문제 18. 검산에서 후보가 개와 개일 때의 실제 따로 뗀 오차를 쓰세요.
답. 와 입니다.
심화 1. 무작위 탐색이 왜 유리한지 확률로 보이세요.
참 최적이 어느 축에서 상위 퍼센트 구간에 있다고 합시다.
| 확률 | |
|---|---|
번이면 퍼센트입니다. 그리고 이 계산에 손잡이 개수가 안 들어갑니다.
중요한 축에서만 상위 퍼센트에 들면 되기 때문입니다. 격자는 손잡이가 늘면 한 축의 개수가 로 줄지만, 무작위는 모든 축에서 개의 서로 다른 값을 씁니다.
심화 2. 연속 반감의 예산 배분을 유도하세요.
후보 개를 분의 로 줄이며 자원을 배로 늘립니다.
| 라운드 | 후보 수 | 후보당 자원 | 총 자원 |
|---|---|---|---|
| N/\eta^ |
각 라운드가 쓰는 총 자원이 같습니다. 라운드 수가 이므로 전체는 입니다.
전부 끝까지 재면 입니다. 문제 에서 대 이 이 차이입니다.
심화 3. 겹친 교차검증을 정리하세요.
| 어느 겹 | 무엇을 하나 |
|---|---|
| 바깥 겹 | 성능을 잽니다 |
| 안쪽 겹 | 손잡이를 고릅니다 |
| 바깥 겹마다 | 안쪽을 새로 돌립니다 |
핵심은 안쪽 겹이 바깥 검증 자료를 절대 안 본다는 것입니다. 그래야 문제 의 가 사라집니다.
비용은 바깥 겹 수를 곱한 만큼입니다. 면 배입니다.
심화 4. 언제 손잡이를 안 찾아도 되는지 정리하세요.
| 상황 | 왜 |
|---|---|
| 나무 개수 | 많을수록 좋고 평평해짐 |
| 배깅의 표본 크기 | 원래 크기가 표준 |
| 신경망의 층 수를 먼저 | 다른 손잡이가 이것에 딸림 |
| 자료가 아주 많음 | 벌점이 거의 안 듦 |
넷째 줄이 문제 의 뒤집힌 경우입니다. 표본이 개일 때 벌점이 배를 갈랐지만, 표본이 아주 많으면 벌점 과 벌점 이 거의 같아집니다.
심화 5. 순차 모형 기반 탐색을 정리하세요.
지금까지 본 점들로 손잡이에서 점수로 가는 함수를 근사하고, 다음에 볼 곳을 정합니다.
| 무엇 | 어떻게 |
|---|---|
| 대리 모형 | 가우스 과정이나 나무 |
| 획득 함수 | 좋아 보이거나 안 가 본 곳 |
| 맞바꿈 | 파고들기와 넓히기 |
강의 이상탐지와 같은 긴장입니다. 지금 좋아 보이는 자리를 더 파느냐, 안 가 본 자리를 보느냐입니다.
평가가 아주 비쌀 때만 값을 합니다. 리지 회귀처럼 한 번 적합이 싼 모형에서는 무작위 탐색이 더 낫습니다. 대리 모형을 학습시키는 비용이 더 크기 때문입니다.
심화 6. 이 강의의 결론을 한 장으로 정리하세요.
| 물음 | 답 |
|---|---|
| 왜 손잡이가 따로 있나 | 학습 손실이 언제나 한쪽 끝을 고르기 때문 |
| 어떻게 찾나 | 로그 눈금에서 무작위로 넓게 |
| 무엇으로 고르나 | 교차검증과 한 표준오차 규칙 |
| 얼마나 촘촘히 | 개면 충분하고 개를 넘기면 낭비 |
| 어떻게 보고하나 | 고르는 절차를 바깥 겹으로 감싸서 |
넷째 줄이 뜻밖의 결론입니다. 문제 에서 후보 개가 개보다 밖에 안 좋았고, 낙관 폭만 커졌습니다.
정답.
| 기호 | 읽는 법 | 뜻 |
|---|---|---|
| 손잡이 | hyperparameter | 자료가 아니라 우리가 정하는 값입니다 |
| 격자 탐색 | grid search | 모든 조합을 다 봅니다 |
| 무작위 탐색 | random search | 무작위로 뽑아 봅니다 |
| 로그 눈금 | log scale | 자릿수마다 고르게 뽑습니다 |
| 한 표준오차 규칙 | one-standard-error rule | 최솟값에서 표준오차 안의 간단한 쪽을 고릅니다 |
| 연속 반감 | successive halving | 나쁜 후보를 일찍 버립니다 |
| 순차 모형 기반 탐색 | sequential model-based optimization | 다음 볼 곳을 예측합니다 |
| 겹친 교차검증 | nested cross-validation | 고르는 절차 전체를 바깥 겹으로 감쌉니다 |
| 벌점 경로 | regularization path | 모든 벌점의 해를 한 번에 얻습니다 |
| 고른 이득 | selection bias | 여럿 중 최선을 고르면 점수가 부풀립니다 |
다음은 230강 모형 해석과 변수 중요도입니다. 이 강의까지 모형을 만들고 재고 고르는 일이 끝났습니다. 다음 강의는 그 모형이 무엇을 보고 판단하는지를 봅니다.
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 ridge(X, y, lam):
A = np.hstack([X, np.ones((len(X), 1))])
G = A.T @ A + lam * np.eye(A.shape[1])
G[-1, -1] -= lam
return np.linalg.solve(G, A.T @ y)
def pred(X, b):
return np.hstack([X, np.ones((len(X), 1))]) @ b
def mse(a, b):
return float(np.mean((a - b) ** 2))
def cv_mse(X, y, lam, k, rr):
idx = rr.permutation(len(y))
fs = np.array_split(idx, k)
v = []
for f in fs:
t = np.setdiff1d(idx, f)
b = ridge(X[t], y[t], lam)
v.append(mse(pred(X[f], b), y[f]))
return float(np.mean(v)), float(np.std(v))
print("=" * 78)
print("229강 하이퍼파라미터 탐색 코드 검산")
print("=" * 78)
print()
print("문제 1. 손잡이란 무엇인가")
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(" 왜 못 배웁니까. 학습 손실을 최소로 하면 벌점은 0 이 되기 때문입니다")
print()
print(" (2) 학습 손실로 손잡이를 고르면 어떻게 되는지 봅니다")
r = np.random.default_rng(20229)
n, p = 80, 60
X = r.normal(0, 1, (n, p))
beta = np.zeros(p)
beta[:6] = np.array([1.6, -1.3, 1.0, -0.8, 0.6, -0.5])
y = X @ beta + r.normal(0, 1.0, n)
Xt = r.normal(0, 1, (4000, p))
yt = Xt @ beta + r.normal(0, 1.0, 4000)
lams = [0.0001, 0.01, 1.0, 10.0, 100.0, 1000.0]
print(" " + rl("벌점 세기", 12) + " " + rl("학습 오차", 12) + " " + rl("교차검증 오차", 14) + " " + rl("따로 뗀 오차", 14))
tr_list, cv_list, te_list = [], [], []
for lam in lams:
b = ridge(X, y, lam)
tr = mse(pred(X, b), y)
cv, _ = cv_mse(X, y, lam, 5, np.random.default_rng(11))
te = mse(pred(Xt, b), yt)
tr_list.append(tr); cv_list.append(cv); te_list.append(te)
print(" " + rl("%.4f" % lam, 12) + " " + rl("%.6f" % tr, 12) + " " + rl("%.6f" % cv, 14) + " " + rl("%.6f" % te, 14))
print(" 학습 오차는 벌점이 작을수록 좋아져 %.6f 에서 가장 작습니다" % min(tr_list))
print(" 학습 오차로 고르면 벌점 %.4f 를 고르고 따로 뗀 오차는 %.6f 입니다" % (lams[int(np.argmin(tr_list))], te_list[int(np.argmin(tr_list))]))
print(" 교차검증으로 고르면 벌점 %.4f 를 고르고 따로 뗀 오차는 %.6f 입니다" % (lams[int(np.argmin(cv_list))], te_list[int(np.argmin(cv_list))]))
print(" 가장 좋은 벌점은 %.4f 이고 그때 %.6f 입니다" % (lams[int(np.argmin(te_list))], min(te_list)))
print(" 손잡이는 반드시 학습에 안 쓴 자료로 골라야 합니다")
print()
print(" (3) 손잡이가 얼마나 듣는지 봅니다")
res = []
for nm, lam in [("가장 나쁜 값", lams[int(np.argmax(te_list))]), ("기본값 1.0", 1.0), ("가장 좋은 값", lams[int(np.argmin(te_list))])]:
b = ridge(X, y, lam)
res.append((nm, lam, mse(pred(Xt, b), yt)))
w0 = max(pw(x[0]) for x in res + [("무엇", 0, 0)])
print(" " + rw("무엇", w0) + " " + rl("벌점 세기", 12) + " " + rl("따로 뗀 오차", 14))
for nm, lam, e in res:
print(" " + rw(nm, w0) + " " + rl("%.4f" % lam, 12) + " " + rl("%.6f" % e, 14))
print(" 가장 나쁜 값과 가장 좋은 값의 차이가 %.6f 배입니다" % (res[0][2] / res[2][2]))
print(" 모형을 바꾸는 것보다 손잡이 하나가 크게 듭니다")
print()
print("문제 2. 탐색 방법")
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(" (2) 격자와 무작위를 같은 예산에서 견줍니다")
r2g = np.random.default_rng(30229)
def target(h):
a, b_, c_, d_ = h
return (a - 0.62) ** 2 * 6.0 + (b_ - 0.35) ** 2 * 0.06 + (c_ - 0.41) ** 2 * 0.04 + (d_ - 0.58) ** 2 * 0.03
budgets = [9, 16, 25, 64]
print(" 네 손잡이 중 하나만 크게 듣는 함수를 최소로 만듭니다")
print(" " + rl("예산", 8) + " " + rl("격자 한 축 개수", 16) + " " + rl("격자 최적값", 14) + " " + rl("무작위 최적값 평균", 20) + " " + rl("무작위가 이긴 비율", 20))
for B in budgets:
g = int(round(B ** 0.25))
if g < 2:
g = 2
ax = (np.arange(g) + 0.5) / g
best_g = min(target((a, b_, c_, d_)) for a in ax for b_ in ax for c_ in ax for d_ in ax)
vals = []
for rep in range(400):
H = r2g.uniform(0, 1, (B, 4))
vals.append(min(target(h) for h in H))
vals = np.array(vals)
print(" " + rl("%d" % B, 8) + " " + rl("%d" % g, 16) + " " + rl("%.6f" % best_g, 14) + " " + rl("%.6f" % vals.mean(), 20) + " " + rl("%.6f" % float((vals < best_g).mean()), 20))
print(" 격자는 예산 64 를 써도 한 축에 2 개나 3 개밖에 못 씁니다")
print(" 무작위는 같은 예산으로 중요한 축에서 서로 다른 값을 다 시도합니다")
print(" 네 축 중 하나만 듣는 자리에서 무작위가 유리합니다")
print()
print(" (3) 손잡이가 다 듣는 자리에서는 어떤지 봅니다")
def target2(h):
ctr = [0.62, 0.35, 0.41, 0.58]
return sum((v - c) ** 2 for v, c in zip(h, ctr))
print(" " + rl("예산", 8) + " " + rl("격자 최적값", 14) + " " + rl("무작위 최적값 평균", 20) + " " + rl("무작위가 이긴 비율", 20))
for B in budgets:
g = int(round(B ** 0.25))
if g < 2:
g = 2
ax = (np.arange(g) + 0.5) / g
best_g = min(target2((a, b_, c_, d_)) for a in ax for b_ in ax for c_ in ax for d_ in ax)
vals = []
for rep in range(400):
H = r2g.uniform(0, 1, (B, 4))
vals.append(min(target2(h) for h in H))
vals = np.array(vals)
print(" " + rl("%d" % B, 8) + " " + rl("%.6f" % best_g, 14) + " " + rl("%.6f" % vals.mean(), 20) + " " + rl("%.6f" % float((vals < best_g).mean()), 20))
print(" 네 축이 다 듣는 자리에서는 예산이 적을수록 격자가 낫습니다")
print(" 격자는 가운데 근처를 반드시 훑는데 무작위는 운에 맡기기 때문입니다")
print(" 예산이 64 로 늘면 무작위가 다시 앞섭니다. 격자는 한 축에 3 개뿐이기 때문입니다")
print(" 실제 손잡이는 대개 몇 개만 크게 듭니다. 그래서 무작위가 기본입니다")
print()
print(" (4) 어느 눈금으로 찾을지 봅니다")
r3 = np.random.default_rng(40229)
lo, hi = 1e-4, 1e2
print(" 벌점처럼 배수로 듣는 손잡이를 찾습니다. 참 최적은 %.4f 근처입니다" % 1.0)
print(" " + rw("어느 눈금으로 뽑았나", 22) + " " + rl("참값의 3 배 안에 든 비율", 24) + " " + rl("찾은 값의 중앙값", 18))
for nm, samp in [("고르게", lambda m: r3.uniform(lo, hi, m)), ("로그 눈금으로", lambda m: np.exp(r3.uniform(np.log(lo), np.log(hi), m)))]:
hit, meds = [], []
for rep in range(400):
v = samp(20)
best = v[np.argmin(np.abs(np.log(v) - np.log(1.0)))]
hit.append(1.0 if (best < 3.0 and best > 1.0 / 3.0) else 0.0)
meds.append(best)
print(" " + rw(nm, 22) + " " + rl("%.6f" % float(np.mean(hit)), 24) + " " + rl("%.6f" % float(np.median(meds)), 18))
print(" 고르게 뽑으면 20 개 중 대부분이 큰 값에 몰려 작은 자리를 못 봅니다")
print(" 로그 눈금으로 뽑으면 자릿수마다 고르게 퍼집니다")
print(" 배수로 듣는 손잡이는 반드시 로그 눈금에서 찾습니다")
print()
print("문제 3. 고르는 기준")
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(" (2) 교차검증 곡선이 얼마나 흔들리는지 봅니다")
grid = np.array([0.01, 0.1, 0.5, 1.0, 3.0, 10.0, 30.0, 100.0, 300.0])
picks = []
for rep in range(60):
rr = np.random.default_rng(1000 + rep)
cv = [cv_mse(X, y, lam, 5, rr)[0] for lam in grid]
picks.append(grid[int(np.argmin(cv))])
picks = np.array(picks)
u = np.unique(picks)
print(" 같은 자료에서 겹을 나누는 방식만 바꿔 60 번 골라 봅니다")
print(" " + rl("고른 벌점", 12) + " " + rl("몇 번 골랐나", 14) + " " + rl("비율", 10))
for uu in u:
print(" " + rl("%.4f" % uu, 12) + " " + rl("%d" % int((picks == uu).sum()), 14) + " " + rl("%.6f" % float((picks == uu).mean()), 10))
print(" 같은 자료인데 겹만 다르게 나눠도 고르는 값이 %d 가지로 갈립니다" % len(u))
print(" 교차검증 곡선의 바닥은 평평하고 그 평평한 자리에서 잡음이 이깁니다")
print()
print(" (3) 한 표준오차 규칙을 씁니다")
rr = np.random.default_rng(555)
cvm, cvs = [], []
for lam in grid:
a, b_ = cv_mse(X, y, lam, 5, np.random.default_rng(555))
cvm.append(a)
cvs.append(b_ / np.sqrt(5))
cvm = np.array(cvm); cvs = np.array(cvs)
j = int(np.argmin(cvm))
thr = cvm[j] + cvs[j]
j1 = int(np.max(np.where(cvm <= thr)[0]))
print(" " + rl("벌점 세기", 12) + " " + rl("교차검증 평균", 14) + " " + rl("표준오차", 12) + " " + rl("따로 뗀 오차", 14))
for i, lam in enumerate(grid):
b = ridge(X, y, lam)
print(" " + rl("%.4f" % lam, 12) + " " + rl("%.6f" % cvm[i], 14) + " " + rl("%.6f" % cvs[i], 12) + " " + rl("%.6f" % mse(pred(Xt, b), yt), 14))
print(" 가장 작은 교차검증은 벌점 %.4f 에서 %.6f 입니다" % (grid[j], cvm[j]))
print(" 거기에 표준오차 %.6f 를 더하면 %.6f 입니다" % (cvs[j], thr))
print(" 그 선 아래에서 가장 벌점이 큰 값은 %.4f 입니다" % grid[j1])
print(" 따로 뗀 오차는 %.6f 대 %.6f 입니다" % (mse(pred(Xt, ridge(X, y, grid[j])), yt), mse(pred(Xt, ridge(X, y, grid[j1])), yt)))
print(" 한 표준오차 규칙은 성능이 사실상 같은 구간에서 더 간단한 쪽을 고르는 규칙입니다")
print()
print(" (4) 겹 수를 바꿔 봅니다")
print(" 겹 나누기를 20 번 되풀이해 평균 냅니다")
print(" " + rl("겹 수", 8) + " " + rl("고른 벌점 중앙값", 18) + " " + rl("그때 교차검증 평균", 20) + " " + rl("따로 뗀 오차 평균", 20) + " " + rl("적합 횟수", 12))
for k in [2, 5, 10, len(y)]:
ps, cs, es = [], [], []
reps = 20 if k < len(y) else 1
for rep in range(reps):
cv = [cv_mse(X, y, lam, k, np.random.default_rng(777 + rep))[0] for lam in grid]
jj = int(np.argmin(cv))
ps.append(grid[jj]); cs.append(cv[jj]); es.append(mse(pred(Xt, ridge(X, y, grid[jj])), yt))
print(" " + rl("%d" % k, 8) + " " + rl("%.4f" % float(np.median(ps)), 18) + " " + rl("%.6f" % float(np.mean(cs)), 20) + " " + rl("%.6f" % float(np.mean(es)), 20) + " " + rl("%d" % (k * len(grid)), 12))
print(" 겹이 많을수록 학습 자료를 많이 써서 교차검증 값이 낮아집니다")
print(" 그런데 고르는 값과 최종 성능은 크게 안 달라집니다")
print(" 212강 문제 3 에서 본 편향과 분산의 맞바꿈이 여기서도 그대로입니다")
print()
print("문제 4. 예산을 아끼기")
print()
print(" (1) 방법들을 정리합니다")
rows = [
("일찍 멈추기", "나빠지면 중단", "손잡이가 아니라 학습에서"),
("연속 반감", "예산을 이긴 쪽에 몰아줌", "여러 손잡이 조합에서"),
("작은 자료로 먼저", "순위만 보고 거름", "순위가 바뀔 수 있음"),
("따뜻한 시작", "이전 해에서 이어감", "벌점 경로에서"),
("겹을 줄임", "2 겹으로 훑고 10 겹으로 확정", "두 단계로"),
]
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(50229)
full = np.arange(n)
sizes = [20, 40, 60, 80]
ref = np.array([mse(pred(Xt, ridge(X, y, lam)), yt) for lam in grid])
ref_rank = np.argsort(np.argsort(ref))
jstar = int(np.argmin(ref))
print(" 따로 뗀 자료에서의 순위를 참 순위로 놓고 30 번씩 되풀이합니다")
print(" " + rl("쓴 표본 수", 12) + " " + rl("순위 상관 평균", 16) + " " + rl("최적을 맞힌 비율", 18) + " " + rl("상위 3 개 안에 든 비율", 22))
for m in sizes:
cs, hs, ts = [], [], []
for rep in range(30):
sub = r4.permutation(n)[:m]
cv = np.array([cv_mse(X[sub], y[sub], lam, 5, np.random.default_rng(99 + rep))[0] for lam in grid])
rk = np.argsort(np.argsort(cv))
cs.append(float(np.corrcoef(rk, ref_rank)[0, 1]))
hs.append(1.0 if int(np.argmin(cv)) == jstar else 0.0)
ts.append(1.0 if jstar in list(np.argsort(cv)[:3]) else 0.0)
print(" " + rl("%d" % m, 12) + " " + rl("%.6f" % float(np.mean(cs)), 16) + " " + rl("%.6f" % float(np.mean(hs)), 18) + " " + rl("%.6f" % float(np.mean(ts)), 22))
print(" 표본을 4 분의 3 만 써도 순위 상관이 0.7 을 넘습니다")
print(" 그런데 4 분의 1 만 쓰면 0.08 로 무너집니다. 자료가 너무 적으면 순위조차 못 믿습니다")
print(" 상위 3 개 안에 드는 비율이 최적을 맞히는 비율보다 언제나 높습니다")
print(" 그래서 작은 자료로는 하나를 고르지 않고 후보를 좁히는 데만 씁니다")
print()
print(" (3) 연속 반감을 돌려 봅니다")
r5 = np.random.default_rng(60229)
cands = np.exp(r5.uniform(np.log(1e-3), np.log(1e3), 32))
alive = np.arange(len(cands))
budget_used = 0
rounds = []
mm = 20
while len(alive) > 1:
sub = r5.permutation(n)[:min(mm, n)]
sc = np.array([cv_mse(X[sub], y[sub], cands[i], 3, np.random.default_rng(31))[0] for i in alive])
budget_used += len(alive) * min(mm, n)
keep = alive[np.argsort(sc)[:max(1, len(alive) // 2)]]
rounds.append((len(alive), min(mm, n), len(keep)))
alive = keep
mm = mm * 2
print(" 후보 32 개를 절반씩 줄이며 표본을 두 배씩 늘립니다")
print(" " + rl("남은 후보", 12) + " " + rl("쓴 표본 수", 12) + " " + rl("살아남은 수", 14))
for a, b_, c_ in rounds:
print(" " + rl("%d" % a, 12) + " " + rl("%d" % b_, 12) + " " + rl("%d" % c_, 14))
win = cands[alive[0]]
full_sc = np.array([cv_mse(X, y, c_, 5, np.random.default_rng(31))[0] for c_ in cands])
best = cands[int(np.argmin(full_sc))]
print(" 연속 반감이 고른 벌점은 %.6f 입니다" % win)
print(" 전체 자료로 32 개를 다 재면 %.6f 가 최선입니다" % best)
print(" 따로 뗀 오차는 %.6f 대 %.6f 입니다" % (mse(pred(Xt, ridge(X, y, win)), yt), mse(pred(Xt, ridge(X, y, best)), yt)))
print(" 이번에는 같은 답을 찾았습니다")
print(" 쓴 표본 적합 수는 %d 대 %d 입니다" % (budget_used, len(cands) * 5 * n))
print(" %.6f 배 싼 값으로 같은 답을 얻었습니다" % (len(cands) * 5 * n / float(budget_used)))
print(" 다만 이른 판단이 틀리면 좋은 후보를 일찍 버립니다. 그것이 이 방법의 값입니다")
print()
print(" (4) 벌점 경로를 한 번에 계산합니다")
A = np.hstack([X, np.ones((n, 1))])
U, S, Vt = np.linalg.svd(A, full_matrices=False)
UT = U.T @ y
print(" 특이값 분해를 한 번 하면 모든 벌점의 해를 바로 얻습니다")
print(" " + rl("벌점 세기", 12) + " " + rl("직접 푼 오차", 14) + " " + rl("분해로 푼 오차", 16) + " " + rl("두 해의 차이", 14))
for lam in [0.1, 1.0, 10.0, 100.0]:
b1 = ridge(X, y, lam)
b2 = Vt.T @ (S / (S ** 2 + lam) * UT)
print(" " + rl("%.4f" % lam, 12) + " " + rl("%.6f" % mse(pred(Xt, b1), yt), 14) + " " + rl("%.6f" % mse(pred(Xt, b2), yt), 16) + " " + rl("%.8f" % float(np.abs(b1 - b2).max()), 14))
print(" 값이 조금 다른 것은 직접 푸는 쪽이 절편에 벌점을 안 주기 때문입니다")
print(" 84강의 특이값 분해가 여기서 예산을 아끼는 도구가 됩니다")
print(" 분해 한 번에 벌점 개수만큼의 해를 얻습니다")
print()
print("문제 5. 실무에서 쓰기")
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(" (2) 고른 값의 교차검증 점수를 성능으로 보고하면 어떻게 되는지 봅니다")
r6 = np.random.default_rng(70229)
sel, honest = [], []
for rep in range(40):
rr2 = np.random.default_rng(2000 + rep)
Xs = rr2.normal(0, 1, (n, p))
ys = Xs @ beta + rr2.normal(0, 1.0, n)
cv = np.array([cv_mse(Xs, ys, lam, 5, np.random.default_rng(3000 + rep))[0] for lam in grid])
jj = int(np.argmin(cv))
sel.append(cv[jj])
again = [cv_mse(Xs, ys, grid[jj], 5, np.random.default_rng(6000 + rep * 7 + q))[0] for q in range(5)]
honest.append(float(np.mean(again)))
sel = np.array(sel); honest = np.array(honest)
print(" 자료를 40 번 새로 뽑아 매번 격자에서 최선을 고릅니다")
print(" 그리고 고른 값 하나만 겹을 새로 나눠 다시 잽니다")
print(" " + rw("무엇", 30) + " " + rl("평균", 12) + " " + rl("표준편차", 12))
print(" " + rw("고를 때 본 교차검증 점수", 30) + " " + rl("%.6f" % sel.mean(), 12) + " " + rl("%.6f" % sel.std(), 12))
print(" " + rw("같은 값을 다시 잰 점수", 30) + " " + rl("%.6f" % honest.mean(), 12) + " " + rl("%.6f" % honest.std(), 12))
print(" 고를 때 본 점수가 %.6f 만큼 낙관적입니다" % (honest.mean() - sel.mean()))
print(" 9 개 중 가장 좋아 보이는 것을 골랐으니 그 점수에는 고른 이득이 섞였습니다")
print(" 40 번 중 %d 번은 다시 재면 나빠집니다" % int((honest > sel).sum()))
print(" 212강 문제 4 의 겹친 교차검증이 이것을 풉니다")
print()
print(" (3) 후보를 몇 개까지 늘려야 하는지 봅니다")
print(" 후보 수를 늘리며 40 번씩 반복합니다")
print(" " + rl("후보 수", 10) + " " + rl("고를 때 본 점수", 18) + " " + rl("다시 잰 점수", 16) + " " + rl("낙관 폭", 12) + " " + rl("실제 따로 뗀 오차", 20))
for K in [3, 9, 30, 100]:
cg = np.exp(np.linspace(np.log(0.01), np.log(300.0), K))
a1, a2, a3 = [], [], []
for rep in range(40):
rr2 = np.random.default_rng(4000 + rep)
Xs = rr2.normal(0, 1, (n, p))
ys = Xs @ beta + rr2.normal(0, 1.0, n)
cv = np.array([cv_mse(Xs, ys, lam, 5, np.random.default_rng(5000 + rep))[0] for lam in cg])
jj = int(np.argmin(cv))
a1.append(cv[jj])
a2.append(float(np.mean([cv_mse(Xs, ys, cg[jj], 5, np.random.default_rng(9000 + rep * 7 + q))[0] for q in range(5)])))
a3.append(mse(pred(Xt, ridge(Xs, ys, cg[jj])), yt))
a1 = np.array(a1); a2 = np.array(a2); a3 = np.array(a3)
print(" " + rl("%d" % K, 10) + " " + rl("%.6f" % a1.mean(), 18) + " " + rl("%.6f" % a2.mean(), 16) + " " + rl("%.6f" % (a2.mean() - a1.mean()), 12) + " " + rl("%.6f" % a3.mean(), 20))
print(" 후보를 3 개에서 9 개로 늘릴 때는 실제 오차가 크게 좋아집니다")
print(" 9 개에서 100 개로 늘리면 거의 제자리입니다")
print(" 대신 낙관 폭은 3 개에서 음수였다가 9 개 이후로 양수가 됩니다")
print(" 더 많이 봤으니 더 잘 골라 보인 것입니다. 다만 30 개를 넘으면 더 안 커집니다")
print(" 촘촘한 격자가 답이 아닙니다. 넓게 훑고 멈추는 편이 낫습니다")
print()
print(" (4) 체크리스트를 정리합니다")
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(" 230강은 이렇게 고른 모형이 무엇을 보고 판단하는지를 봅니다")
print()
print("=" * 78)
print("검산 끝")
print("=" * 78)
# ==============================================================================
# 229강 하이퍼파라미터 탐색 코드 검산
# ==============================================================================
#
# 문제 1. 손잡이란 무엇인가
#
# (1) 배우는 것과 정해 주는 것을 가릅니다
# 무엇 어떻게 정하나 기준
# 계수 자료에서 배움 손실을 최소로
# 벌점 세기 정해 줌 교차검증으로
# 나무 깊이 정해 줌 교차검증으로
# 이웃 수 정해 줌 교차검증으로
# 학습률 정해 줌 교차검증으로
# 갈래 문턱 정해 줌 비용으로
# 첫 줄만 자료가 정하고 나머지는 우리가 정합니다
# 왜 못 배웁니까. 학습 손실을 최소로 하면 벌점은 0 이 되기 때문입니다
#
# (2) 학습 손실로 손잡이를 고르면 어떻게 되는지 봅니다
# 벌점 세기 학습 오차 교차검증 오차 따로 뗀 오차
# 0.0001 0.168367 17.030101 4.538885
# 0.0100 0.168368 15.721250 4.532020
# 1.0000 0.172770 2.595001 4.046118
# 10.0000 0.294498 2.085705 3.352680
# 100.0000 1.796323 4.035780 4.590584
# 1000.0000 4.869007 5.636009 7.038497
# 학습 오차는 벌점이 작을수록 좋아져 0.168367 에서 가장 작습니다
# 학습 오차로 고르면 벌점 0.0001 를 고르고 따로 뗀 오차는 4.538885 입니다
# 교차검증으로 고르면 벌점 10.0000 를 고르고 따로 뗀 오차는 3.352680 입니다
# 가장 좋은 벌점은 10.0000 이고 그때 3.352680 입니다
# 손잡이는 반드시 학습에 안 쓴 자료로 골라야 합니다
#
# (3) 손잡이가 얼마나 듣는지 봅니다
# 무엇 벌점 세기 따로 뗀 오차
# 가장 나쁜 값 1000.0000 7.038497
# 기본값 1.0 1.0000 4.046118
# 가장 좋은 값 10.0000 3.352680
# 가장 나쁜 값과 가장 좋은 값의 차이가 2.099365 배입니다
# 모형을 바꾸는 것보다 손잡이 하나가 크게 듭니다
#
# 문제 2. 탐색 방법
#
# (1) 방법들을 정리합니다
# 방법 무엇을 하나 무엇이 문제인가
# 격자 탐색 모든 조합을 다 봄 차원이 늘면 폭발
# 무작위 탐색 무작위로 뽑아 봄 운이 필요함
# 순차 모형 기반 다음 볼 곳을 예측 구현이 복잡
# 연속 반감 나쁜 것을 일찍 버림 일찍 판단이 틀릴 수 있음
# 손으로 직관으로 재현이 안 됨
#
# (2) 격자와 무작위를 같은 예산에서 견줍니다
# 네 손잡이 중 하나만 크게 듣는 함수를 최소로 만듭니다
# 예산 격자 한 축 개수 격자 최적값 무작위 최적값 평균 무작위가 이긴 비율
# 9 2 0.103891 0.043188 0.907500
# 16 2 0.103891 0.019472 0.992500
# 25 2 0.103891 0.013658 1.000000
# 64 3 0.088266 0.007247 1.000000
# 격자는 예산 64 를 써도 한 축에 2 개나 3 개밖에 못 씁니다
# 무작위는 같은 예산으로 중요한 축에서 서로 다른 값을 다 시도합니다
# 네 축 중 하나만 듣는 자리에서 무작위가 유리합니다
#
# (3) 손잡이가 다 듣는 자리에서는 어떤지 봅니다
# 예산 격자 최적값 무작위 최적값 평균 무작위가 이긴 비율
# 9 0.081400 0.132592 0.215000
# 16 0.081400 0.100186 0.422500
# 25 0.081400 0.080314 0.557500
# 64 0.051400 0.047115 0.605000
# 네 축이 다 듣는 자리에서는 예산이 적을수록 격자가 낫습니다
# 격자는 가운데 근처를 반드시 훑는데 무작위는 운에 맡기기 때문입니다
# 예산이 64 로 늘면 무작위가 다시 앞섭니다. 격자는 한 축에 3 개뿐이기 때문입니다
# 실제 손잡이는 대개 몇 개만 크게 듭니다. 그래서 무작위가 기본입니다
#
# (4) 어느 눈금으로 찾을지 봅니다
# 벌점처럼 배수로 듣는 손잡이를 찾습니다. 참 최적은 1.0000 근처입니다
# 어느 눈금으로 뽑았나 참값의 3 배 안에 든 비율 찾은 값의 중앙값
# 고르게 0.420000 3.624212
# 로그 눈금으로 0.952500 1.027237
# 고르게 뽑으면 20 개 중 대부분이 큰 값에 몰려 작은 자리를 못 봅니다
# 로그 눈금으로 뽑으면 자릿수마다 고르게 퍼집니다
# 배수로 듣는 손잡이는 반드시 로그 눈금에서 찾습니다
#
# 문제 3. 고르는 기준
#
# (1) 무엇으로 고를지 정리합니다
# 기준 언제 무엇이 문제인가
# 교차검증 평균 가장 흔함 겹 수에 따라 흔들림
# 한 표준오차 규칙 간단한 쪽으로 덜 과적합
# 따로 뗀 검증 빠름 한 번뿐이라 운
# 겹친 교차검증 정직한 성능 비쌈
#
# (2) 교차검증 곡선이 얼마나 흔들리는지 봅니다
# 같은 자료에서 겹을 나누는 방식만 바꿔 60 번 골라 봅니다
# 고른 벌점 몇 번 골랐나 비율
# 3.0000 12 0.200000
# 10.0000 48 0.800000
# 같은 자료인데 겹만 다르게 나눠도 고르는 값이 2 가지로 갈립니다
# 교차검증 곡선의 바닥은 평평하고 그 평평한 자리에서 잡음이 이깁니다
#
# (3) 한 표준오차 규칙을 씁니다
# 벌점 세기 교차검증 평균 표준오차 따로 뗀 오차
# 0.0100 11.559051 3.219460 4.532020
# 0.1000 6.651580 1.141348 4.472030
# 0.5000 3.584494 0.420439 4.249778
# 1.0000 2.768635 0.368427 4.046118
# 3.0000 2.086481 0.362448 3.627764
# 10.0000 2.154338 0.368144 3.352680
# 30.0000 2.840438 0.398024 3.578625
# 100.0000 4.035372 0.438962 4.590584
# 300.0000 5.081522 0.480284 5.944027
# 가장 작은 교차검증은 벌점 3.0000 에서 2.086481 입니다
# 거기에 표준오차 0.362448 를 더하면 2.448930 입니다
# 그 선 아래에서 가장 벌점이 큰 값은 10.0000 입니다
# 따로 뗀 오차는 3.627764 대 3.352680 입니다
# 한 표준오차 규칙은 성능이 사실상 같은 구간에서 더 간단한 쪽을 고르는 규칙입니다
#
# (4) 겹 수를 바꿔 봅니다
# 겹 나누기를 20 번 되풀이해 평균 냅니다
# 겹 수 고른 벌점 중앙값 그때 교차검증 평균 따로 뗀 오차 평균 적합 횟수
# 2 3.0000 3.611023 3.576698 18
# 5 10.0000 2.114947 3.366434 45
# 10 10.0000 1.852337 3.366434 90
# 80 10.0000 1.685316 3.352680 720
# 겹이 많을수록 학습 자료를 많이 써서 교차검증 값이 낮아집니다
# 그런데 고르는 값과 최종 성능은 크게 안 달라집니다
# 212강 문제 3 에서 본 편향과 분산의 맞바꿈이 여기서도 그대로입니다
#
# 문제 4. 예산을 아끼기
#
# (1) 방법들을 정리합니다
# 방법 무엇을 하나 어디서
# 일찍 멈추기 나빠지면 중단 손잡이가 아니라 학습에서
# 연속 반감 예산을 이긴 쪽에 몰아줌 여러 손잡이 조합에서
# 작은 자료로 먼저 순위만 보고 거름 순위가 바뀔 수 있음
# 따뜻한 시작 이전 해에서 이어감 벌점 경로에서
# 겹을 줄임 2 겹으로 훑고 10 겹으로 확정 두 단계로
#
# (2) 작은 자료로 거르면 순위가 지켜지는지 봅니다
# 따로 뗀 자료에서의 순위를 참 순위로 놓고 30 번씩 되풀이합니다
# 쓴 표본 수 순위 상관 평균 최적을 맞힌 비율 상위 3 개 안에 든 비율
# 20 0.080000 0.000000 0.100000
# 40 0.381111 0.233333 0.366667
# 60 0.713333 0.400000 0.766667
# 80 0.795556 0.900000 1.000000
# 표본을 4 분의 3 만 써도 순위 상관이 0.7 을 넘습니다
# 그런데 4 분의 1 만 쓰면 0.08 로 무너집니다. 자료가 너무 적으면 순위조차 못 믿습니다
# 상위 3 개 안에 드는 비율이 최적을 맞히는 비율보다 언제나 높습니다
# 그래서 작은 자료로는 하나를 고르지 않고 후보를 좁히는 데만 씁니다
#
# (3) 연속 반감을 돌려 봅니다
# 후보 32 개를 절반씩 줄이며 표본을 두 배씩 늘립니다
# 남은 후보 쓴 표본 수 살아남은 수
# 32 20 16
# 16 40 8
# 8 80 4
# 4 80 2
# 2 80 1
# 연속 반감이 고른 벌점은 5.928731 입니다
# 전체 자료로 32 개를 다 재면 5.928731 가 최선입니다
# 따로 뗀 오차는 3.421800 대 3.421800 입니다
# 이번에는 같은 답을 찾았습니다
# 쓴 표본 적합 수는 2400 대 12800 입니다
# 5.333333 배 싼 값으로 같은 답을 얻었습니다
# 다만 이른 판단이 틀리면 좋은 후보를 일찍 버립니다. 그것이 이 방법의 값입니다
#
# (4) 벌점 경로를 한 번에 계산합니다
# 특이값 분해를 한 번 하면 모든 벌점의 해를 바로 얻습니다
# 벌점 세기 직접 푼 오차 분해로 푼 오차 두 해의 차이
# 0.1000 4.472030 4.468772 0.00144847
# 1.0000 4.046118 4.029803 0.01007132
# 10.0000 3.352680 3.334704 0.03125716
# 100.0000 4.590584 4.521231 0.14378221
# 값이 조금 다른 것은 직접 푸는 쪽이 절편에 벌점을 안 주기 때문입니다
# 84강의 특이값 분해가 여기서 예산을 아끼는 도구가 됩니다
# 분해 한 번에 벌점 개수만큼의 해를 얻습니다
#
# 문제 5. 실무에서 쓰기
#
# (1) 순서를 정리합니다
# 순서 무엇을 하나 무엇으로
# 먼저 가장 크게 듣는 손잡이부터 대개 벌점과 깊이
# 다음 넓게 로그 눈금으로 훑음 무작위 탐색
# 다음 좋은 자리 주변을 좁게 두 단계
# 다음 한 표준오차 규칙 적용 간단한 쪽
# 마지막 겹친 교차검증으로 성능 보고 고른 값이 아니라 절차를 평가
#
# (2) 고른 값의 교차검증 점수를 성능으로 보고하면 어떻게 되는지 봅니다
# 자료를 40 번 새로 뽑아 매번 격자에서 최선을 고릅니다
# 그리고 고른 값 하나만 겹을 새로 나눠 다시 잽니다
# 무엇 평균 표준편차
# 고를 때 본 교차검증 점수 2.945220 0.585864
# 같은 값을 다시 잰 점수 3.041499 0.533629
# 고를 때 본 점수가 0.096279 만큼 낙관적입니다
# 9 개 중 가장 좋아 보이는 것을 골랐으니 그 점수에는 고른 이득이 섞였습니다
# 40 번 중 24 번은 다시 재면 나빠집니다
# 212강 문제 4 의 겹친 교차검증이 이것을 풉니다
#
# (3) 후보를 몇 개까지 늘려야 하는지 봅니다
# 후보 수를 늘리며 40 번씩 반복합니다
# 후보 수 고를 때 본 점수 다시 잰 점수 낙관 폭 실제 따로 뗀 오차
# 3 4.002242 3.945683 -0.056559 3.407844
# 9 3.033768 3.067157 0.033389 2.633464
# 30 2.983760 3.041185 0.057425 2.600862
# 100 2.981221 3.033671 0.052450 2.597634
# 후보를 3 개에서 9 개로 늘릴 때는 실제 오차가 크게 좋아집니다
# 9 개에서 100 개로 늘리면 거의 제자리입니다
# 대신 낙관 폭은 3 개에서 음수였다가 9 개 이후로 양수가 됩니다
# 더 많이 봤으니 더 잘 골라 보인 것입니다. 다만 30 개를 넘으면 더 안 커집니다
# 촘촘한 격자가 답이 아닙니다. 넓게 훑고 멈추는 편이 낫습니다
#
# (4) 체크리스트를 정리합니다
# 무엇을 묻나 어떻게
# 무엇이 크게 듣는지 아나 듣는 것부터 넓게 봅니다
# 눈금이 배수인가 로그 눈금에서 뽑습니다
# 바닥이 평평한가 한 표준오차 규칙을 씁니다
# 성능을 어떻게 보고하나 겹친 교차검증으로 합니다
# 예산이 모자란가 작은 자료로 후보를 좁힙니다
# 경로를 한 번에 되나 벌점 회귀는 분해 한 번으로
# 넷째 줄이 실무에서 가장 자주 어기는 자리입니다
# 230강은 이렇게 고른 모형이 무엇을 보고 판단하는지를 봅니다
#
# ==============================================================================
# 검산 끝
# ==============================================================================