강 문제 에서 제곱 항 하나가 PR 넓이를 두 배로 만들었습니다. 이 강의는 그 항을 어떻게 만드는지를 봅니다.
강부터 강까지 여섯 방법을 봤고 강과 강에서 재는 법을 봤습니다. 그런데 어느 모형을 쓰든 입력이 참 모양을 못 담으면 소용이 없습니다.
이 강의는 눈금·모양·범주·상호작용 넷을 차례로 보고, 마지막에 누출 없이 만드는 절차를 정리합니다.
문제. 무엇을 만드는지 봅니다.
() 종류를 정리하세요.
() 참 모양을 알 때와 모를 때를 견주세요.
() 아무 변환이나 넣으면 어떻게 되는지 보세요.
생각의 실마리. 모형은 주어진 변수의 함수만 만들 수 있습니다. 직선 모형에 를 안 주면 곱을 절대 못 만듭니다.
풀이. () 정리합니다.
| 무엇 | 무엇을 하나 | 왜 필요한가 |
|---|---|---|
| 눈금 맞추기 | 단위를 통일함 | 거리와 벌점이 공평해짐 |
| 모양 펴기 | 치우친 분포를 고침 | 직선 가정에 맞춤 |
| 범주 다루기 | 숫자로 바꿈 | 모형이 읽을 수 있게 |
| 상호작용 | 곱을 만듦 | 한쪽 효과가 다른 쪽에 달림 |
| 굽은 항 | 제곱이나 조각을 넣음 | 직선으로 안 되는 모양 |
| 묶어 요약 | 여러 줄을 하나로 | 관측 단위가 다를 때 |
() 참 관계가 인 자료로 견줍니다.
| 무엇을 넣었나 | 변수 수 | 따로 뗀 RMSE | 결정계수 |
|---|---|---|---|
| 원래 변수 셋 | |||
| 로그만 넣음 | |||
| 곱만 넣음 | |||
| 둘 다 넣음 | |||
| 참 모양 그대로 |
원래 변수만 쓰면 결정계수가 입니다. 로그와 곱을 함께 넣으면 로 오릅니다.
맨 아래가 가장 중요합니다. 참 모양을 세 개로 적으면 **변수가 더 적은데 **로 가장 좋습니다.
변수를 많이 만드는 것이 아니라 맞는 것을 만드는 일입니다.
() 참 모양을 모른 채 거듭제곱을 마구 넣어 봅니다.
| 무엇을 넣었나 | 변수 수 | 학습 RMSE | 따로 뗀 RMSE |
|---|---|---|---|
| 원래 셋 | |||
| 제곱까지 | |||
| 세제곱까지 | |||
| 네제곱까지 |
학습 오차는 에서 으로 줄지만 따로 뗀 오차는 에서 으로 오히려 나빠집니다.
참 모양이 로그와 곱인데 거듭제곱으로는 못 만들기 때문입니다. 로그 하나가 거듭제곱 아홉 개보다 낫습니다.
이 문제에서 배우는 것. 특성공학은 아무거나 많이 만드는 일이 아닙니다. 참 모양을 모르면 거듭제곱을 열두 개 넣어도 제자리이고, 맞는 것 하나를 넣으면 결정계수가 에서 로 뜁니다.
확인 1-1. 직선 모형에 곱을 안 주면 어떻게 되는지 쓰세요.
답. 곱을 절대 못 만듭니다.
확인 1-2. 검산에서 원래 변수 셋과 참 모양 그대로의 결정계수를 쓰세요.
답. 와 입니다.
확인 1-3. 검산에서 네제곱까지 넣었을 때 학습 오차와 따로 뗀 오차가 어떻게 되는지 쓰세요.
답. 학습은 으로 줄지만 따로 뗀 것은 으로 나빠집니다.
문제. 단위와 분포를 손봅니다.
() 어떤 모형이 눈금에 흔들리는지 가르세요.
() 단위를 바꿔 실제로 재세요.
() 눈금 맞추는 방법들을 견주세요.
() 치우친 분포를 펴 보세요.
생각의 실마리. "표준화는 항상 한다"는 습관이 있습니다. 그런데 왜 하는지를 알아야 언제 안 해도 되는지도 압니다.
풀이. () 가릅니다.
| 모형 | 눈금이 필요한가 | 왜 |
|---|---|---|
| 최소제곱 | 필요 없음 | 계수가 단위를 흡수함 |
| 벌점 회귀 | 필요함 | 벌점이 계수 크기를 봄 |
| 이웃 방법 | 필요함 | 거리가 단위를 그대로 씀 |
| 경사하강 | 필요함 | 수렴이 느려짐 |
| 나무 | 필요 없음 | 각 변수에서 자를 뿐 |
| 주성분 | 필요함 | 분산이 큰 축을 고름 |
첫 줄과 다섯째 줄이 예외입니다. 최소제곱은 를 배 하면 계수가 이 되어 예측이 그대로이고, 나무는 각 변수에서 자를 자리만 찾으므로 단위가 상관없습니다.
() 첫 변수의 단위만 바꿔 잽니다.
| 첫 변수의 단위 | 이웃 그대로 | 이웃 표준화 | 로지스틱 그대로 | 그때 ROC 넓이 | 로지스틱 표준화 |
|---|---|---|---|---|---|
| 모두 같음 | |||||
| 천 배 차이 | |||||
| 백만 배 차이 |
이웃 방법은 에서 로 떨어집니다. 표준화하면 으로 되돌아옵니다. 강 문제 에서 거리가 큰 축 하나에 잡아먹힌다고 한 것입니다.
경사하강으로 푸는 로지스틱은 더 심합니다. 천 배 차이에서 정확도 이고 ROC 넓이가 입니다.
절반보다 낮다는 것은 순위가 뒤집혔다는 뜻입니다. 걸음이 발산해 부호가 틀린 자리에 갇힌 것입니다. 표준화하면 으로 돌아옵니다.
() 눈금 맞추는 방법 셋을 견줍니다. 정상 점 개에 이상점 개를 섞고, 이상점이 없었을 때와 견줍니다.
| 방법 | 보통 점의 폭 | 이상점 없었으면 | 줄어든 비율 |
|---|---|---|---|
| 표준화 | |||
| 최소최대 | |||
| 중앙값과 사분위 |
최소최대는 보통 점의 폭이 만큼 쪼그라듭니다. 가장 큰 값 하나가 전체 눈금을 정하기 때문입니다.
표준화도 만큼 줄어듭니다. 표준편차가 이상점에 끌려 커지기 때문입니다.
중앙값과 사분위는 로 거의 안 흔들립니다. 사분위 범위는 가운데 절반만 보므로 이상점이 못 건드립니다.
() 치우친 분포를 폅니다. 참 관계가 인 자료입니다.
| 변환 | 비대칭도 | 따로 뗀 RMSE | 결정계수 |
|---|---|---|---|
| 그대로 | |||
| 로그 | |||
| 제곱근 | |||
| 순위를 과 사이로 |
비대칭도가 에서 로 줄고 결정계수가 에서 로 오릅니다.
그런데 순위 변환은 비대칭도가 정확히 인데 결정계수가 로 로그보다 낮습니다.
비대칭도가 줄어서 좋아진 것이 아니라 참 모양에 맞아서 좋아진 것입니다. 참 관계가 로그였으므로 로그가 맞습니다.
이 문제에서 배우는 것. 눈금은 거리나 벌점이나 걸음을 쓰는 모형에서만 문제가 됩니다. 그리고 모양 펴기는 대칭을 만드는 일이 아니라 참 관계를 직선으로 만드는 일입니다.
확인 2-1. 눈금이 필요 없는 모형 둘을 쓰세요.
답. 최소제곱과 나무입니다.
확인 2-2. 검산에서 천 배 차이일 때 로지스틱의 ROC 넓이를 쓰고 무엇을 뜻하는지 쓰세요.
답. 이고 순위가 뒤집혔다는 뜻입니다.
확인 2-3. 검산에서 순위 변환의 비대칭도와 결정계수를 쓰세요.
답. 과 입니다.
문제. 칸을 숫자로 바꿉니다.
() 방법들을 정리하세요.
() 칸이 많을 때 어떻게 되는지 보세요.
() 목표값 평균 인코딩의 누출을 재세요.
() 드문 칸을 어떻게 할지 정하세요.
생각의 실마리. 범주는 순서가 없습니다. 칸 번호 과 가 과 보다 가깝다는 근거가 없습니다.
풀이. () 정리합니다.
| 방법 | 무엇을 하나 | 무엇이 문제인가 |
|---|---|---|
| 한 자리만 | 칸마다 열 하나 | 칸이 많으면 열이 폭발 |
| 하나 빼고 | 기준 칸을 뺌 | 선형모형의 공선성 방지 |
| 순서를 숫자로 | 으로 | 간격이 같다고 가정함 |
| 빈도로 | 나온 횟수로 | 다른 칸이 같은 값 |
| 목표값 평균으로 | 칸별 평균 | 누출 위험이 큼 |
| 드문 칸 묶기 | 기타로 합침 | 표본이 적은 칸 |
() 칸이 개이고 학습 표본이 개인 자료로 봅니다. 가장 흔한 칸은 개이고 학습에 한 번도 안 나온 칸이 개입니다.
| 무엇을 했나 | 변수 수 | 따로 뗀 RMSE | 결정계수 |
|---|---|---|---|
| 칸을 버림 | |||
| 한 자리만 | |||
| 칸 번호를 숫자로 | |||
| 빈도로 |
칸 번호를 그대로 숫자로 쓰면 결정계수 로 칸을 버린 와 거의 같습니다. 번호에는 순서가 없는데 순서가 있다고 읽기 때문입니다.
빈도로 바꾼 것이 로 뜻밖에 쓸 만합니다. 칸마다 빈도가 다르면 빈도가 칸을 부분적으로 식별하기 때문입니다. 빈도가 같은 칸끼리는 못 가릅니다.
() 목표값 평균 인코딩의 누출을 잽니다.
| 어떻게 만들었나 | 학습 RMSE | 따로 뗀 RMSE | 차이 |
|---|---|---|---|
| 그냥 학습 전체로 | |||
| 겹 밖 값으로 | |||
| 평활을 으로 |
그냥 만들면 학습에서 인데 밖에서 입니다. 자기 가 자기 변수에 들어갔기 때문입니다.
겹 밖 값으로 만들면 학습 오차가 로 오히려 커지고 밖에서 로 가장 좋습니다. 학습에서 본 값이 밖보다 나쁘므로 낙관이 없습니다.
강 문제 의 재표집 누출과 정확히 같은 구조입니다.
() 드문 칸을 어떻게 할지 봅니다. 학습 표본이 칸당 평균 개입니다.
| 무엇을 했나 | 남은 칸 수 | 따로 뗀 RMSE |
|---|---|---|
| 안 묶음 | ||
| 개 미만 묶음 | ||
| 개 미만 묶음 | ||
| 개 미만 묶음 |
개 미만을 묶으면 에서 으로 좋아집니다. 드문 칸은 표본이 적어 평균이 잡음에 흔들리기 때문입니다.
그런데 개 미만까지 묶으면 로 다시 나빠집니다. 쓸 만한 칸까지 지웠기 때문입니다.
아래로 볼록한 곡선입니다. 어디서 자를지는 손잡이이고 강에서 고르는 법을 봅니다.
이 문제에서 배우는 것. 범주 처리는 칸 수와 표본 수의 싸움입니다. 칸이 많으면 원핫이 흔들리고 목표값 평균은 누출되며 드문 칸은 못 믿습니다. 를 쓰는 인코딩은 반드시 겹 안에서 만들어야 합니다.
확인 3-1. 칸 번호를 그대로 숫자로 쓰면 안 되는 이유를 쓰세요.
답. 번호에 순서가 없는데 순서가 있다고 읽기 때문입니다.
확인 3-2. 검산에서 칸을 버린 것과 한 자리만 의 결정계수를 쓰세요.
답. 와 입니다.
확인 3-3. 검산에서 그냥 만든 목표값 평균의 학습과 밖의 차이를 쓰세요.
답. 입니다.
문제. 직선으로 안 되는 것을 담습니다.
() 언제 필요한지 정리하세요.
() 나무와 견주세요.
() 굽은 모양을 어떻게 담을지 견주세요.
() 곱을 다 만들면 어떻게 되는지 보세요.
생각의 실마리. 강의 나무는 스스로 조각을 만듭니다. 그러면 직선 모형에만 손으로 만들어 주면 됩니다.
풀이. () 정리합니다.
| 어떤 관계 | 무엇을 만드나 | 예 |
|---|---|---|
| 한쪽 효과가 다른 쪽에 달림 | 곱을 넣음 | |
| 효과가 커지다 꺾임 | 제곱을 넣음 | |
| 특정 값에서 꺾임 | 조각 직선 | |
| 주기가 있음 | 사인과 코사인 | 시각이나 요일 |
| 비율이 중요함 | 나눗셈 | 밀도나 단가 |
() 참 관계가 인 자료로 견줍니다.
| 무엇 | 따로 뗀 RMSE | 결정계수 |
|---|---|---|
| 직선 곱 없이 | ||
| 직선 곱 넣고 | ||
| 깊이 나무 |
곱이 없는 직선은 결정계수 로 평균을 내놓는 것보다 나쁩니다. 곱 하나를 넣으면 입니다.
나무는 곱을 안 줘도 를 냅니다. 스스로 조각을 만들기 때문입니다.
다만 나무는 곱을 직접 준 직선보다 못합니다. 계단으로 기울어진 면을 흉내 내야 하기 때문입니다.
() 참 모양이 에서 꺾이는 조각 직선인 자료로 견줍니다.
| 무엇으로 담았나 | 변수 수 | 따로 뗀 RMSE | 결정계수 |
|---|---|---|---|
| 직선만 | |||
| 제곱까지 | |||
| 세제곱까지 | |||
| 조각 직선 매듭 셋 | |||
| 구간을 칸으로 다섯 |
조각 직선이 RMSE 로 가장 낫습니다. 매듭을 와 과 에 뒀는데 참 꺾임 자리인 를 몰라도 잘 담습니다.
구간을 칸으로 나누면 으로 크게 나빠집니다. 칸 안에서 평평해져 기울기를 못 살리기 때문입니다.
강의 기저 전개가 여기서 그대로 쓰입니다.
() 변수 쌍의 곱을 전부 만들어 봅니다. 참 관계는 로 고정하고 잡음 변수 수만 늘립니다. 학습 표본은 개입니다.
| 원래 변수 수 | 곱까지 넣은 수 | 원래만 결정계수 | 곱까지 결정계수 |
|---|---|---|---|
변수가 개면 곱을 다 넣어 으로 크게 오릅니다.
개면 곱이 개가 되고 학습 표본 개를 넘어 로 무너집니다.
강의 차원의 저주가 만든 변수에서도 그대로 일어납니다. 쌍의 수는 로 제곱으로 늘기 때문입니다.
이 문제에서 배우는 것. 상호작용은 어디에 넣을지 알 때만 이득입니다. 변수가 적으면 전부 만들어도 되지만, 많아지면 참 곱 하나를 개의 잡음이 덮습니다.
확인 4-1. 나무에 곱을 안 줘도 되는 이유를 쓰세요.
답. 스스로 조각을 만들기 때문입니다.
확인 4-2. 검산에서 곱 없는 직선과 곱 넣은 직선의 결정계수를 쓰세요.
답. 과 입니다.
확인 4-3. 검산에서 변수가 개일 때 곱까지 넣은 결정계수를 쓰세요.
답. 입니다.
문제. 실제로 씁니다.
() 순서를 정리하세요.
() 눈금을 겹 밖에서 맞추면 어떤지 재세요.
() 만든 변수가 정말 듣는지 검사하세요.
() 체크리스트를 정리하세요.
생각의 실마리. 문제 에서 목표값 평균 인코딩이 크게 낙관됐습니다. 그러면 모든 변환을 겹 안에서 해야 할까요.
풀이. () 정리합니다.
| 순서 | 무엇을 하나 | 왜 |
|---|---|---|
| 먼저 | 자료를 보고 단위와 분포 확인 | 무엇이 이상한지 봄 |
| 다음 | 도메인 지식으로 변수 제안 | 가장 크게 듣는 자리 |
| 다음 | 겹 안에서만 만들기 | 누출 차단 |
| 다음 | 만든 변수를 교차검증으로 검사 | 정말 듣는지 확인 |
| 마지막 | 안 듣는 변수는 버림 | 잡음만 늘림 |
() 눈금 맞추기를 겹 밖에서 하면 어떤지 잽니다.
| 언제 표준화했나 | 교차검증 정확도 |
|---|---|
| 겹을 나누기 전에 | |
| 겹 안에서 |
차이가 입니다.
평균과 표준편차는 전체 요약이라 한 점의 정보가 거의 안 새기 때문입니다. 문제 의 목표값 평균 인코딩은 를 쓰므로 이야기가 완전히 다릅니다.
규칙은 하나입니다. 를 쓰는 변환은 반드시 겹 안에서 합니다.
() 만든 변수가 정말 듣는지 검사합니다. 참 관계는 이고 는 과 강하게 겹칩니다. 기본 세 변수만의 교차검증 결정계수는 입니다.
| 후보 변수 | 와의 상관 | 넣었을 때 결정계수 | 올린 값 |
|---|---|---|---|
| 제곱 | |||
| 곱하기 | |||
| 곱하기 | |||
| 과 겹치는 | |||
| 잡음 하나 |
는 와의 상관이 으로 잡음 하나의 와 비슷한 수준인데 넣으면 이나 올립니다.
과 겹쳐 있어 혼자서는 안 보이고 옆에 놓아야 보이기 때문입니다.
상관 하나로 후보를 고르면 이런 변수를 놓칩니다. 강의 변수 중요도가 이 문제를 정면으로 다룹니다.
() 체크리스트를 정리합니다.
| 무엇을 묻나 | 어떻게 |
|---|---|
| 를 쓰는 변환인가 | 겹 안에서만 만듭니다 |
| 미래 정보가 들어갔나 | 시간을 지켜 자릅니다 |
| 추론 때 구할 수 있나 | 학습에만 있는 값은 못 씁니다 |
| 결측이 나오면 | 채우는 규칙도 겹 안에서 |
| 칸이 새로 생기면 | 기타로 보낼 자리를 미리 |
| 정말 듣는가 | 교차검증으로 확인 |
셋째 줄이 실무에서 가장 자주 놓치는 자리입니다. 학습 자료에는 있는데 예측할 시점에는 아직 없는 값을 넣으면 교차검증까지 다 통과하고 배포 뒤에 무너집니다.
이 문제에서 배우는 것. 누출을 막는 기준은 "모든 변환을 겹 안에서"가 아니라 **"를 쓰거나 미래를 쓰는 변환만 겹 안에서"**입니다. 그리고 만든 변수는 상관이 아니라 교차검증으로 검사합니다.
확인 5-1. 겹 안에서 만들어야 하는 변환의 기준을 쓰세요.
답. 를 쓰거나 미래 정보를 쓰는 변환입니다.
확인 5-2. 검산에서 표준화를 겹 밖에서 했을 때와 안에서 했을 때의 차이를 쓰세요.
답. 으로 차이가 없습니다.
확인 5-3. 검산에서 의 와의 상관과 올린 값을 쓰세요.
답. 과 입니다.
| 유형 | 무엇을 묻나 | 어디를 보나 |
|---|---|---|
| 맞는 변수 하나 | 거듭제곱 아홉보다 나음 | 문제 |
| 눈금이 필요한 모형 | 거리·벌점·걸음 | 문제 |
| 단위가 무너뜨림 | 순위까지 뒤집힘 | 문제 |
| 로버스트 눈금 | 사분위는 안 흔들림 | 문제 |
| 모양 펴기 | 대칭이 아니라 참 모양 | 문제 |
| 칸 번호를 숫자로 | 칸을 버린 것과 같음 | 문제 |
| 목표값 평균 누출 | 겹 안에서만 | 문제 |
| 드문 칸 묶기 | 아래로 볼록 | 문제 |
| 나무와 상호작용 | 스스로 만듦 | 문제 |
| 곱을 다 만들기 | 쌍이 제곱으로 늘음 | 문제 |
| 상관으로 고르기 | 겹친 변수를 놓침 | 문제 |
변수를 만들 때 묻는 순서를 한자리에 모읍니다.
| 순서 | 질문 | 아니면 |
|---|---|---|
| 참 관계를 아는가 | 도메인 지식을 먼저 씁니다 | |
| 이 모형이 눈금에 흔들리는가 | 안 흔들리면 안 합니다 | |
| 를 쓰는 변환인가 | 겹 안에서만 만듭니다 | |
| 추론 때 구할 수 있는가 | 못 구하면 버립니다 | |
| 교차검증이 올랐는가 | 안 올랐으면 버립니다 |
문제 6. 직선 모형에 곱을 안 주면 어떻게 되는지 쓰세요.
답. 곱을 절대 못 만듭니다.
문제 7. 검산에서 원래 변수 셋과 참 모양 그대로의 결정계수를 쓰세요.
답. 와 입니다.
문제 8. 검산에서 네제곱까지 넣었을 때 학습 오차와 따로 뗀 오차가 어떻게 되는지 쓰세요.
답. 학습은 으로 줄지만 따로 뗀 것은 으로 나빠집니다.
문제 9. 눈금이 필요 없는 모형 둘을 쓰세요.
답. 최소제곱과 나무입니다.
문제 10. 검산에서 천 배 차이일 때 로지스틱의 ROC 넓이를 쓰고 무엇을 뜻하는지 쓰세요.
답. 이고 순위가 뒤집혔다는 뜻입니다.
문제 11. 검산에서 순위 변환의 비대칭도와 결정계수를 쓰세요.
답. 과 입니다.
문제 12. 검산에서 칸을 버린 것과 한 자리만 의 결정계수를 쓰세요.
답. 와 입니다.
문제 13. 검산에서 그냥 만든 목표값 평균의 학습과 밖의 차이를 쓰세요.
답. 입니다.
문제 14. 검산에서 개 미만 묶음과 개 미만 묶음의 따로 뗀 RMSE를 쓰세요.
답. 과 입니다.
문제 15. 검산에서 곱 없는 직선과 곱 넣은 직선의 결정계수를 쓰세요.
답. 과 입니다.
문제 16. 검산에서 변수가 개일 때 곱까지 넣은 결정계수를 쓰세요.
답. 입니다.
문제 17. 검산에서 의 와의 상관과 올린 값을 쓰세요.
답. 과 입니다.
문제 18. 겹 안에서 만들어야 하는 변환의 기준을 쓰세요.
답. 를 쓰거나 미래 정보를 쓰는 변환입니다.
심화 1. 최소제곱이 왜 눈금에 안 흔들리는지 보이세요.
를 배 하면 이렇게 됩니다.
| 무엇 | 어떻게 되나 |
|---|---|
| 계수 | 배 |
| 예측 | 그대로 |
| 잔차 | 그대로 |
계수가 단위를 정확히 흡수합니다. 그런데 벌점을 넣는 순간 깨집니다. 은 의 크기를 보는데, 단위가 커지면 계수가 작아져 벌점을 덜 받습니다. 그래서 강의 라소와 리지는 표준화가 필수입니다.
심화 2. 목표값 평균 인코딩의 평활을 유도하세요.
칸별 평균과 전체 평균을 섞습니다.
| 무엇에 가까운가 | |
|---|---|
| 아주 큼 | 칸별 평균 |
| 과 비슷 | 반반 |
| 아주 작음 | 전체 평균 |
강의 축소 추정과 같은 식입니다. 표본이 적은 칸일수록 전체 평균 쪽으로 끌어당깁니다. 문제 에서 평활 이 그냥 만든 것보다 나았던 이유입니다.
심화 3. 순환 변수를 어떻게 다룰지 정리하세요.
시각이나 요일은 시와 시가 이웃입니다. 숫자로 쓰면 가장 먼 값이 됩니다.
| 무엇 | 왜 |
|---|---|
| 둘을 함께 씁니다 | 하나만으로는 두 시각이 같은 값 |
| 거리가 원 위의 거리 | 시와 시가 가까움 |
| 주기가 여럿이면 | 배수 주기도 함께 |
하루 주기와 일주일 주기가 겹치면 넷을 씁니다. 강의 푸리에 기저와 같은 생각입니다.
심화 4. 결측값을 어떻게 다룰지 정리하세요.
| 방법 | 무엇을 하나 | 언제 |
|---|---|---|
| 줄을 버림 | 결측 있는 관측 제거 | 아주 적을 때 |
| 평균으로 채움 | 분산을 줄임 | 급할 때만 |
| 모형으로 채움 | 다른 변수로 예측 | 관계가 있을 때 |
| 결측 표시를 변수로 | 있음 없음 열 추가 | 결측 자체가 정보일 때 |
넷째 줄이 자주 놓치는 자리입니다. 소득란을 비운 사람과 적은 사람은 다르므로, 비웠다는 사실 자체가 변수가 됩니다. 채우는 규칙도 강대로 겹 안에서 만듭니다.
심화 5. 변수를 고르는 방법들을 정리하세요.
| 방법 | 어떻게 | 무엇이 문제인가 |
|---|---|---|
| 상관으로 거르기 | 와의 상관 순 | 문제 의 를 놓침 |
| 앞으로 넣기 | 하나씩 넣어 봄 | 느림 |
| 뒤로 빼기 | 하나씩 빼 봄 | 변수가 많으면 못 함 |
| 라소 | 계수를 으로 | 겹친 변수 중 하나만 |
| 중요도 순 | 나무의 이득 순 | 강에서 봄 |
첫 줄이 가장 흔하고 가장 위험합니다. 문제 에서 의 상관이 으로 잡음 수준이었는데 넣으면 을 올렸습니다.
심화 6. 언제 특성공학을 안 해도 되는지 정리하세요.
| 상황 | 왜 |
|---|---|
| 나무 계열을 씀 | 눈금과 단조 변환에 안 흔들림 |
| 표본이 아주 많음 | 모형이 스스로 배움 |
| 깊은 신경망 | 층이 변수를 만듦 |
| 참 관계가 이미 직선 | 더할 것이 없음 |
넷째 줄이 문제 의 ()입니다. 참 관계가 직선인데 거듭제곱을 넣으면 따로 뗀 오차만 나빠집니다.
다만 표본이 적고 도메인 지식이 있으면 특성공학이 가장 값싼 이득입니다. 문제 에서 로그와 곱 두 개가 결정계수를 에서 로 올렸습니다.
정답.
| 기호 | 읽는 법 | 뜻 |
|---|---|---|
| 특성공학 | feature engineering | 입력 변수를 만들고 고칩니다 |
| 표준화 | standardization | 평균을 빼고 표준편차로 나눕니다 |
| 최소최대 | min-max scaling | 최솟값과 최댓값 사이로 옮깁니다 |
| 로버스트 눈금 | robust scaling | 중앙값과 사분위 범위를 씁니다 |
| 비대칭도 | skewness | 분포가 한쪽으로 치우친 정도입니다 |
| 한 자리만 | one-hot encoding | 칸마다 열을 하나씩 만듭니다 |
| 목표값 평균 인코딩 | target encoding | 칸별 평균으로 바꿉니다 |
| 평활 | smoothing | 칸 평균을 전체 평균 쪽으로 당깁니다 |
| 상호작용 | interaction | 두 변수의 곱을 넣습니다 |
| 조각 직선 기저 | hinge basis | 꼴의 항입니다 |
다음은 229강 하이퍼파라미터 탐색입니다. 이 강의의 문제 에서 드문 칸을 어디서 자를지가 손잡이로 남았습니다. 다음 강의는 그런 손잡이를 어떻게 고르는지를 봅니다.
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=1e-6):
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 rmse(a, b):
return float(np.sqrt(np.mean((a - b) ** 2)))
def r2(yt, yp):
return float(1 - ((yt - yp) ** 2).sum() / ((yt - yt.mean()) ** 2).sum())
def sig(z):
return 1.0 / (1.0 + np.exp(-np.clip(z, -500.0, 500.0)))
def logit_fit(X, y, steps=3000, lr=0.5, lam=1e-3):
A = np.hstack([X, np.ones((len(X), 1))])
b = np.zeros(A.shape[1])
for _ in range(steps):
b -= lr * (A.T @ (sig(A @ b) - y) / len(y) + lam * b)
return b
def logit_p(X, b):
return sig(np.hstack([X, np.ones((len(X), 1))]) @ b)
def acc(p, y):
return float(((p > 0.5).astype(float) == y).mean())
def auc_roc(s, y):
o = np.argsort(s)
r = np.empty(len(s))
r[o] = np.arange(1, len(s) + 1)
su = np.unique(s)
if len(su) < len(s):
for v in su:
m = s == v
if m.sum() > 1:
r[m] = r[m].mean()
np_, nn = float(y.sum()), float((1 - y).sum())
if np_ == 0 or nn == 0:
return float('nan')
return float((r[y == 1].sum() - np_ * (np_ + 1) / 2) / (np_ * nn))
print("=" * 78)
print("228강 특성공학 코드 검산")
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(" 227강 문제 4 에서 제곱 항 하나가 PR 넓이를 두 배로 만들었습니다")
print(" 그 변수를 어떻게 만드는지가 이 강의입니다")
print()
print(" (2) 참 모양을 알 때와 모를 때를 견줍니다")
r = np.random.default_rng(20228)
n = 800
x1 = r.uniform(0, 4, n)
x2 = r.uniform(-2, 2, n)
x3 = r.normal(0, 1, n)
yt = 2.0 * np.log1p(x1) + 1.5 * x2 * x3 + 0.5 * x3 + r.normal(0, 0.4, n)
tr = np.arange(600)
te = np.arange(600, n)
sets = [
("원래 변수 셋", np.column_stack([x1, x2, x3])),
("로그만 넣음", np.column_stack([np.log1p(x1), x2, x3])),
("곱만 넣음", np.column_stack([x1, x2, x3, x2 * x3])),
("둘 다 넣음", np.column_stack([np.log1p(x1), x2, x3, x2 * x3])),
("참 모양 그대로", np.column_stack([np.log1p(x1), x2 * x3, x3])),
]
res = []
for nm, X in sets:
b = ridge(X[tr], yt[tr])
res.append((nm, X.shape[1], rmse(pred(X[te], b), yt[te]), r2(yt[te], pred(X[te], b))))
w0 = max(pw(x[0]) for x in res + [("무엇을 넣었나", 0, 0, 0)])
print(" " + rw("무엇을 넣었나", w0) + " " + rl("변수 수", 8) + " " + rl("따로 뗀 RMSE", 14) + " " + rl("결정계수", 12))
for nm, k, e, q in res:
print(" " + rw(nm, w0) + " " + rl("%d" % k, 8) + " " + rl("%.6f" % e, 14) + " " + rl("%.6f" % q, 12))
print(" 원래 변수만 쓰면 결정계수가 %.6f 입니다" % res[0][3])
print(" 로그와 곱을 함께 넣으면 %.6f 로 오릅니다" % res[3][3])
print(" 참 모양을 세 개로 적으면 변수가 더 적은데 %.6f 입니다" % res[4][3])
print(" 변수를 많이 만드는 것이 아니라 맞는 것을 만드는 일입니다")
print()
print(" (3) 아무 변환이나 넣으면 어떻게 되는지 봅니다")
res2 = []
Xb = np.column_stack([x1, x2, x3])
for nm, k in [("원래 셋", 0), ("제곱까지", 1), ("세제곱까지", 2), ("네제곱까지", 3)]:
cols = [Xb]
for d in range(2, k + 2):
cols.append(Xb ** d)
X = np.hstack(cols)
b = ridge(X[tr], yt[tr], 1e-6)
res2.append((nm, X.shape[1], rmse(pred(X[tr], b), yt[tr]), rmse(pred(X[te], b), yt[te])))
w0 = max(pw(x[0]) for x in res2 + [("무엇을 넣었나", 0, 0, 0)])
print(" " + rw("무엇을 넣었나", w0) + " " + rl("변수 수", 8) + " " + rl("학습 RMSE", 12) + " " + rl("따로 뗀 RMSE", 14))
for nm, k, a, e in res2:
print(" " + rw(nm, w0) + " " + rl("%d" % k, 8) + " " + rl("%.6f" % a, 12) + " " + rl("%.6f" % e, 14))
print(" 제곱 세제곱을 아무리 넣어도 따로 뗀 오차가 %.6f 에서 %.6f 로 거의 안 줄어듭니다" % (res2[0][3], res2[3][3]))
print(" 참 모양이 로그와 곱인데 거듭제곱으로는 못 만들기 때문입니다")
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(30228)
m = 900
a1 = r2g.normal(0, 1, m)
a2 = r2g.normal(0, 1, m)
a3 = r2g.normal(0, 1, m)
yy = (1.2 * a1 - 0.9 * a2 + 0.6 * a3 + r2g.normal(0, 0.8, m) > 0).astype(float)
trm = np.arange(600)
tem = np.arange(600, m)
scales = [("모두 같음", 1.0), ("천 배 차이", 1000.0), ("백만 배 차이", 1e6)]
def knn_acc(Xa, ya, Xb_, yb, k=15):
D = ((Xb_[:, None, :] - Xa[None, :, :]) ** 2).sum(axis=2)
idx = np.argsort(D, axis=1)[:, :k]
return float(((ya[idx].mean(axis=1) > 0.5).astype(float) == yb).mean())
res3 = []
for nm, sc in scales:
X = np.column_stack([a1 * sc, a2, a3])
Xs = (X - X[trm].mean(axis=0)) / X[trm].std(axis=0)
pr_raw = logit_p(X[tem], logit_fit(X[trm], yy[trm]))
res3.append((nm, knn_acc(X[trm], yy[trm], X[tem], yy[tem]),
knn_acc(Xs[trm], yy[trm], Xs[tem], yy[tem]),
acc(pr_raw, yy[tem]),
auc_roc(pr_raw, yy[tem]),
acc(logit_p(Xs[tem], logit_fit(Xs[trm], yy[trm])), yy[tem])))
w0 = max(pw(x[0]) for x in res3 + [("첫 변수의 단위", 0, 0, 0, 0, 0)])
print(" " + rw("첫 변수의 단위", w0) + " " + rl("이웃 그대로", 12) + " " + rl("이웃 표준화", 12) + " " + rl("로지스틱 그대로", 16) + " " + rl("그때 ROC 넓이", 16) + " " + rl("로지스틱 표준화", 16))
for nm, k1, k2, l1, fr, l2 in res3:
print(" " + rw(nm, w0) + " " + rl("%.6f" % k1, 12) + " " + rl("%.6f" % k2, 12) + " " + rl("%.6f" % l1, 16) + " " + rl("%.6f" % fr, 16) + " " + rl("%.6f" % l2, 16))
print(" 단위를 백만 배로 키우면 이웃 방법은 %.6f 에서 %.6f 로 무너집니다" % (res3[0][1], res3[2][1]))
print(" 표준화하면 %.6f 로 되돌아옵니다" % res3[2][2])
print(" 213강 문제 2 에서 거리가 큰 축 하나에 잡아먹힌다고 한 것입니다")
print(" 경사하강으로 푸는 로지스틱은 더 심합니다")
print(" 천 배 차이에서 정확도가 %.6f 이고 ROC 넓이가 %.6f 입니다" % (res3[1][3], res3[1][4]))
print(" ROC 넓이가 절반보다 낮다는 것은 순위가 뒤집혔다는 뜻입니다")
print(" 단위가 커서 걸음이 발산했고 부호가 틀린 자리에 갇힌 것입니다")
print(" 표준화하면 %.6f 로 돌아옵니다" % res3[2][5])
print()
print(" (3) 눈금 맞추는 방법들을 견줍니다")
r3 = np.random.default_rng(40228)
clean = r3.normal(0, 1, 500)
dirty = clean.copy()
dirty[:10] = dirty[:10] + 40.0
def scale_by(v, ref, how):
if how == 0:
return (v - ref.mean()) / ref.std()
if how == 1:
return (v - ref.min()) / (ref.max() - ref.min())
return (v - np.median(ref)) / (np.quantile(ref, 0.75) - np.quantile(ref, 0.25))
names = ["표준화", "최소최대", "중앙값과 사분위"]
ok = np.arange(10, 500)
w0 = max(pw(x) for x in names + ["방법"])
print(" " + rw("방법", w0) + " " + rl("보통 점의 폭", 14) + " " + rl("이상점 없었으면", 16) + " " + rl("줄어든 비율", 14))
res_sc = []
for j, nm in enumerate(names):
a = scale_by(dirty, dirty, j)[ok]
b = scale_by(clean, clean, j)[ok]
sa = float(a.max() - a.min())
sb = float(b.max() - b.min())
res_sc.append((nm, sa, sb, 1 - sa / sb))
print(" " + rw(nm, w0) + " " + rl("%.6f" % sa, 14) + " " + rl("%.6f" % sb, 16) + " " + rl("%.6f" % (1 - sa / sb), 14))
print(" 이상점 열 개가 들어오면 최소최대는 보통 점의 폭이 %.6f 만큼 쪼그라듭니다" % res_sc[1][3])
print(" 가장 큰 값 하나가 전체 눈금을 정하기 때문입니다")
print(" 표준화도 %.6f 만큼 줄어듭니다. 표준편차가 이상점에 끌려 커지기 때문입니다" % res_sc[0][3])
print(" 중앙값과 사분위는 %.6f 로 거의 안 흔들립니다" % res_sc[2][3])
print(" 사분위 범위는 가운데 절반만 보므로 이상점이 못 건드립니다")
print(" (4) 치우친 분포를 펴 봅니다")
r4 = np.random.default_rng(50228)
n2 = 900
z = r4.normal(0, 1, n2)
xs = np.exp(1.0 + 0.9 * z)
ys = 1.5 * np.log(xs) + r4.normal(0, 0.5, n2)
tr2 = np.arange(600)
te2 = np.arange(600, n2)
trans = [
("그대로", xs),
("로그", np.log(xs)),
("제곱근", np.sqrt(xs)),
("순위를 0 과 1 사이로", np.argsort(np.argsort(xs)) / (n2 - 1.0)),
]
w0 = max(pw(x[0]) for x in trans + [("변환", 0)])
print(" " + rw("변환", w0) + " " + rl("비대칭도", 12) + " " + rl("따로 뗀 RMSE", 14) + " " + rl("결정계수", 12))
for nm, v in trans:
sk = float(((v - v.mean()) ** 3).mean() / v.std() ** 3)
X = v.reshape(-1, 1)
b = ridge(X[tr2], ys[tr2])
print(" " + rw(nm, w0) + " " + rl("%.6f" % sk, 12) + " " + rl("%.6f" % rmse(pred(X[te2], b), ys[te2]), 14) + " " + rl("%.6f" % r2(ys[te2], pred(X[te2], b)), 12))
b0 = ridge(xs[tr2].reshape(-1, 1), ys[tr2])
b1 = ridge(np.log(xs[tr2]).reshape(-1, 1), ys[tr2])
print(" 비대칭도가 %.6f 에서 %.6f 로 줄고 결정계수가 %.6f 에서 %.6f 로 오릅니다" % (
float(((xs - xs.mean()) ** 3).mean() / xs.std() ** 3),
float(((np.log(xs) - np.log(xs).mean()) ** 3).mean() / np.log(xs).std() ** 3),
r2(ys[te2], pred(xs[te2].reshape(-1, 1), b0)),
r2(ys[te2], pred(np.log(xs[te2]).reshape(-1, 1), b1))))
print(" 참 관계가 로그였으므로 로그가 맞습니다")
print(" 비대칭도가 줄어서가 아니라 참 모양에 맞아서 좋아진 것입니다")
print()
print("문제 3. 범주형 변수")
print()
print(" (1) 방법들을 정리합니다")
rows = [
("한 자리만 1", "칸마다 열 하나", "칸이 많으면 열이 폭발"),
("하나 빼고", "기준 칸을 뺌", "선형모형의 공선성 방지"),
("순서를 숫자로", "1 2 3 으로", "간격이 같다고 가정함"),
("빈도로", "나온 횟수로", "다른 칸이 같은 값"),
("목표값 평균으로", "칸별 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(" (2) 칸이 많을 때 어떻게 되는지 봅니다")
r5 = np.random.default_rng(60228)
n3 = 2000
K = 150
pk = 1.0 / (np.arange(1, K + 1) ** 0.9)
pk = pk / pk.sum()
cat = r5.choice(K, n3, p=pk)
eff = r5.normal(0, 1.2, K)
xc = r5.normal(0, 1, n3)
yc = eff[cat] + 0.8 * xc + r5.normal(0, 2.5, n3)
tr3 = np.arange(1400)
te3 = np.arange(1400, n3)
onehot = np.zeros((n3, K))
onehot[np.arange(n3), cat] = 1.0
cnt = np.array([(cat[tr3] == k).sum() for k in range(K)], dtype=float)
enc = [
("칸을 버림", xc.reshape(-1, 1)),
("한 자리만 1", np.hstack([onehot, xc.reshape(-1, 1)])),
("칸 번호를 숫자로", np.column_stack([cat.astype(float), xc])),
("빈도로", np.column_stack([cnt[cat], xc])),
]
w0 = max(pw(x[0]) for x in enc + [("무엇을 했나", 0)])
print(" " + rw("무엇을 했나", w0) + " " + rl("변수 수", 8) + " " + rl("따로 뗀 RMSE", 14) + " " + rl("결정계수", 12))
for nm, X in enc:
b = ridge(X[tr3], yc[tr3], 1e-4)
print(" " + rw(nm, w0) + " " + rl("%d" % X.shape[1], 8) + " " + rl("%.6f" % rmse(pred(X[te3], b), yc[te3]), 14) + " " + rl("%.6f" % r2(yc[te3], pred(X[te3], b)), 12))
print(" 칸이 %d 개이고 학습 표본이 %d 개입니다" % (K, len(tr3)))
print(" 가장 흔한 칸은 %d 개이고 학습에 한 번도 안 나온 칸이 %d 개입니다" % (int(cnt.max()), int((cnt == 0).sum())))
print(" 칸 번호를 그대로 숫자로 쓰면 칸을 버린 것과 거의 같습니다")
print(" 번호에는 순서가 없는데 순서가 있다고 읽기 때문입니다")
print()
print(" (3) 목표값 평균 인코딩의 누출을 잽니다")
gm = float(yc[tr3].mean())
def te_encode(idx_fit, idx_apply, sm=0.0):
v = np.zeros(K)
for k in range(K):
mm = cat[idx_fit] == k
c = mm.sum()
v[k] = (yc[idx_fit][mm].sum() + sm * gm) / (c + sm) if c + sm > 0 else gm
return v[cat[idx_apply]]
naive = te_encode(tr3, np.arange(n3))
Xn = np.column_stack([naive, xc])
bn = ridge(Xn[tr3], yc[tr3])
oof = np.zeros(n3)
folds = np.array_split(r5.permutation(tr3), 5)
for f in folds:
rest = np.setdiff1d(tr3, f)
oof[f] = te_encode(rest, f)
oof[te3] = te_encode(tr3, te3)
Xo = np.column_stack([oof, xc])
bo = ridge(Xo[tr3], yc[tr3])
Xs2 = np.column_stack([te_encode(tr3, np.arange(n3), sm=20.0), xc])
bs = ridge(Xs2[tr3], yc[tr3])
res4 = [
("그냥 학습 전체로", rmse(pred(Xn[tr3], bn), yc[tr3]), rmse(pred(Xn[te3], bn), yc[te3])),
("겹 밖 값으로", rmse(pred(Xo[tr3], bo), yc[tr3]), rmse(pred(Xo[te3], bo), yc[te3])),
("평활을 20 으로", rmse(pred(Xs2[tr3], bs), yc[tr3]), rmse(pred(Xs2[te3], bs), yc[te3])),
]
w0 = max(pw(x[0]) for x in res4 + [("어떻게 만들었나", 0, 0)])
print(" " + rw("어떻게 만들었나", w0) + " " + rl("학습 RMSE", 12) + " " + rl("따로 뗀 RMSE", 14) + " " + rl("차이", 10))
for nm, a, e in res4:
print(" " + rw(nm, w0) + " " + rl("%.6f" % a, 12) + " " + rl("%.6f" % e, 14) + " " + rl("%.6f" % (e - a), 10))
print(" 그냥 만들면 학습에서 %.6f 인데 밖에서 %.6f 입니다" % (res4[0][1], res4[0][2]))
print(" 자기 y 가 자기 변수에 들어갔기 때문입니다")
print(" 겹 밖 값으로 만들면 학습과 밖의 차이가 %.6f 로 줄어듭니다" % (res4[1][2] - res4[1][1]))
print(" 227강 문제 5 의 재표집 누출과 정확히 같은 구조입니다")
print()
print(" (4) 드문 칸을 어떻게 할지 봅니다")
res5 = []
for nm, thr in [("안 묶음", 0), ("3 개 미만 묶음", 3), ("8 개 미만 묶음", 8), ("20 개 미만 묶음", 20)]:
grp = cat.copy()
small = np.where(cnt < thr)[0]
if len(small) > 0:
grp[np.isin(cat, small)] = K
u = np.unique(grp)
oh = np.zeros((n3, len(u)))
for j, uu in enumerate(u):
oh[grp == uu, j] = 1.0
X = np.hstack([oh, xc.reshape(-1, 1)])
b = ridge(X[tr3], yc[tr3], 1e-4)
res5.append((nm, len(u), rmse(pred(X[te3], b), yc[te3])))
w0 = max(pw(x[0]) for x in res5 + [("무엇을 했나", 0, 0)])
print(" " + rw("무엇을 했나", w0) + " " + rl("남은 칸 수", 12) + " " + rl("따로 뗀 RMSE", 14))
for nm, k, e in res5:
print(" " + rw(nm, w0) + " " + rl("%d" % k, 12) + " " + rl("%.6f" % e, 14))
print(" 학습 표본이 칸당 평균 %.6f 개입니다" % (len(tr3) / float(K)))
print(" 3 개 미만을 묶으면 %.6f 에서 %.6f 로 좋아집니다" % (res5[0][2], res5[1][2]))
print(" 드문 칸은 표본이 적어 평균이 잡음에 흔들리기 때문입니다")
print(" 그런데 20 개 미만까지 묶으면 %.6f 로 다시 나빠집니다" % res5[3][2])
print(" 쓸 만한 칸까지 지웠기 때문입니다. 어디서 자를지는 손잡이입니다")
print()
print("문제 4. 상호작용과 굽은 항")
print()
print(" (1) 언제 필요한지 정리합니다")
rows = [
("한쪽 효과가 다른 쪽에 달림", "곱을 넣음", "x1 곱하기 x2"),
("효과가 커지다 꺾임", "제곱을 넣음", "x 제곱"),
("특정 값에서 꺾임", "조각 직선", "max(x-c, 0)"),
("주기가 있음", "사인과 코사인", "시각이나 요일"),
("비율이 중요함", "나눗셈", "밀도나 단가"),
]
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(70228)
n4 = 1000
u1 = r6.uniform(-2, 2, n4)
u2 = r6.uniform(-2, 2, n4)
yv = 2.0 * u1 * u2 + r6.normal(0, 0.5, n4)
tr4 = np.arange(700)
te4 = np.arange(700, n4)
def tree_fit(X, y, depth, mn=10):
if depth == 0 or len(y) < mn:
return ('L', float(y.mean()))
best = None
for j in range(X.shape[1]):
qs = np.quantile(X[:, j], np.linspace(0.1, 0.9, 9))
for t in qs:
m = X[:, j] <= t
if m.sum() < mn or (~m).sum() < mn:
continue
s = ((y[m] - y[m].mean()) ** 2).sum() + ((y[~m] - y[~m].mean()) ** 2).sum()
if best is None or s < best[0]:
best = (s, j, t, m)
if best is None:
return ('L', float(y.mean()))
_, j, t, m = best
return ('N', j, t, tree_fit(X[m], y[m], depth - 1, mn), tree_fit(X[~m], y[~m], depth - 1, mn))
def tree_pred(T, X):
if T[0] == 'L':
return np.full(len(X), T[1])
m = X[:, T[1]] <= T[2]
o = np.empty(len(X))
if m.sum():
o[m] = tree_pred(T[3], X[m])
if (~m).sum():
o[~m] = tree_pred(T[4], X[~m])
return o
Xa = np.column_stack([u1, u2])
Xb2 = np.column_stack([u1, u2, u1 * u2])
ba = ridge(Xa[tr4], yv[tr4])
bb = ridge(Xb2[tr4], yv[tr4])
T = tree_fit(Xa[tr4], yv[tr4], 5)
res6 = [
("직선 곱 없이", rmse(pred(Xa[te4], ba), yv[te4]), r2(yv[te4], pred(Xa[te4], ba))),
("직선 곱 넣고", rmse(pred(Xb2[te4], bb), yv[te4]), r2(yv[te4], pred(Xb2[te4], bb))),
("깊이 5 나무", rmse(tree_pred(T, Xa[te4]), yv[te4]), r2(yv[te4], tree_pred(T, Xa[te4]))),
]
w0 = max(pw(x[0]) for x in res6 + [("무엇", 0, 0)])
print(" " + rw("무엇", w0) + " " + rl("따로 뗀 RMSE", 14) + " " + rl("결정계수", 12))
for nm, e, q in res6:
print(" " + rw(nm, w0) + " " + rl("%.6f" % e, 14) + " " + rl("%.6f" % q, 12))
print(" 참 관계가 곱인데 직선은 결정계수 %.6f 로 아무것도 못 잡습니다" % res6[0][2])
print(" 곱 하나를 넣으면 %.6f 입니다" % res6[1][2])
print(" 나무는 곱을 안 줘도 %.6f 를 냅니다. 스스로 조각을 만들기 때문입니다" % res6[2][2])
print(" 다만 나무는 %.6f 로 곱을 직접 준 직선보다 못합니다" % res6[2][2])
print(" 계단으로 기울어진 면을 흉내 내야 하기 때문입니다")
print()
print(" (3) 굽은 모양을 어떻게 담을지 견줍니다")
r7 = np.random.default_rng(80228)
n5 = 800
xv = r7.uniform(0, 10, n5)
yk = np.where(xv < 4, 0.2 * xv, 0.8 + 2.6 * (xv - 4)) + r7.normal(0, 0.6, n5)
tr5 = np.arange(550)
te5 = np.arange(550, n5)
knots = np.array([2.5, 5.0, 7.5])
feats = [
("직선만", xv.reshape(-1, 1)),
("제곱까지", np.column_stack([xv, xv ** 2])),
("세제곱까지", np.column_stack([xv, xv ** 2, xv ** 3])),
("조각 직선 매듭 셋", np.column_stack([xv] + [np.maximum(xv - k, 0) for k in knots])),
("구간을 칸으로 다섯", np.column_stack([(np.digitize(xv, np.quantile(xv, [0.2, 0.4, 0.6, 0.8])) == j).astype(float) for j in range(5)])),
]
w0 = max(pw(x[0]) for x in feats + [("무엇으로 담았나", 0)])
print(" " + rw("무엇으로 담았나", w0) + " " + rl("변수 수", 8) + " " + rl("따로 뗀 RMSE", 14) + " " + rl("결정계수", 12))
for nm, X in feats:
b = ridge(X[tr5], yk[tr5], 1e-4)
print(" " + rw(nm, w0) + " " + rl("%d" % X.shape[1], 8) + " " + rl("%.6f" % rmse(pred(X[te5], b), yk[te5]), 14) + " " + rl("%.6f" % r2(yk[te5], pred(X[te5], b)), 12))
bl = ridge(xv[tr5].reshape(-1, 1), yk[tr5], 1e-4)
Xh = np.column_stack([xv] + [np.maximum(xv - k, 0) for k in knots])
bh = ridge(Xh[tr5], yk[tr5], 1e-4)
print(" 참 모양이 4 에서 꺾이는 조각 직선입니다")
print(" 조각 직선이 RMSE %.6f 로 가장 낫습니다" % rmse(pred(Xh[te5], bh), yk[te5]))
print(" 구간을 칸으로 나누면 안에서 평평해져 꺾인 자리를 못 살립니다")
print(" 201강의 기저 전개가 여기서 그대로 쓰입니다")
print()
print(" (4) 곱을 다 만들면 어떻게 되는지 봅니다")
r8 = np.random.default_rng(90228)
res7 = []
for p in [5, 10, 20, 40]:
Xr = r8.normal(0, 1, (700, p))
yr = 1.5 * Xr[:, 0] * Xr[:, 1] + 0.8 * Xr[:, 2] + r8.normal(0, 0.6, 700)
tri = np.arange(450)
tei = np.arange(450, 700)
pairs = [Xr[:, i] * Xr[:, j] for i in range(p) for j in range(i + 1, p)]
Xall = np.hstack([Xr, np.column_stack(pairs)])
b1 = ridge(Xr[tri], yr[tri], 1e-3)
b2 = ridge(Xall[tri], yr[tri], 1e-3)
res7.append((p, Xall.shape[1], r2(yr[tei], pred(Xr[tei], b1)), r2(yr[tei], pred(Xall[tei], b2))))
print(" " + rl("원래 변수 수", 14) + " " + rl("곱까지 넣은 수", 16) + " " + rl("원래만 결정계수", 18) + " " + rl("곱까지 결정계수", 18))
for p, q, s1, s2 in res7:
print(" " + rl("%d" % p, 14) + " " + rl("%d" % q, 16) + " " + rl("%.6f" % s1, 18) + " " + rl("%.6f" % s2, 18))
print(" 변수가 5 개면 곱을 다 넣어 %.6f 로 크게 오릅니다" % res7[0][3])
print(" 40 개면 곱이 %d 개가 되고 학습 표본 450 개를 넘어 %.6f 로 무너집니다" % (res7[3][1], res7[3][3]))
print(" 209강의 차원의 저주가 만든 변수에서도 그대로 일어납니다")
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) 눈금을 겹 밖에서 맞추면 어떻게 되는지 봅니다")
r9 = np.random.default_rng(100228)
n6 = 400
Xq = r9.normal(0, 1, (n6, 6))
Xq[:, 0] = Xq[:, 0] * 30 + 100
yq = (0.06 * Xq[:, 0] + 0.9 * Xq[:, 1] - 6.0 + r9.normal(0, 0.7, n6) > 0).astype(float)
def cv_knn(X, y, k, scale_first, rr):
idx = rr.permutation(len(y))
fs = np.array_split(idx, k)
Xf = (X - X.mean(axis=0)) / X.std(axis=0) if scale_first else X
v = []
for f in fs:
t = np.setdiff1d(idx, f)
if scale_first:
Xa_, Xb_ = Xf[t], Xf[f]
else:
mu, sd = X[t].mean(axis=0), X[t].std(axis=0)
Xa_, Xb_ = (X[t] - mu) / sd, (X[f] - mu) / sd
v.append(knn_acc(Xa_, y[t], Xb_, y[f], 9))
return float(np.mean(v))
print(" 단위 맞추기는 겹 밖에서 해도 크게 안 다칩니다. 얼마나인지 잽니다")
a_first = cv_knn(Xq, yq, 8, True, np.random.default_rng(1))
a_in = cv_knn(Xq, yq, 8, False, np.random.default_rng(1))
print(" " + rw("언제 표준화했나", 20) + " " + rl("교차검증 정확도", 18))
print(" " + rw("겹을 나누기 전에", 20) + " " + rl("%.6f" % a_first, 18))
print(" " + rw("겹 안에서", 20) + " " + rl("%.6f" % a_in, 18))
print(" 차이가 %.6f 로 작습니다. 평균과 표준편차는 전체 요약이라 한 점의 정보가 거의 안 새기 때문입니다" % (a_first - a_in))
print(" 문제 3 의 목표값 평균 인코딩은 y 를 쓰므로 이야기가 완전히 다릅니다")
print(" 규칙은 하나입니다. y 를 쓰는 변환은 반드시 겹 안에서 합니다")
print()
print(" (3) 만든 변수가 정말 듣는지 검사합니다")
r10 = np.random.default_rng(110228)
n7 = 900
c1 = r10.normal(0, 1, n7)
c2 = r10.normal(0, 1, n7)
c3 = r10.normal(0, 1, n7)
c4 = 0.95 * c1 + r10.normal(0, 0.31, n7)
yz = 1.2 * c1 + 0.9 * c1 * c2 - 1.1 * c4 + r10.normal(0, 0.6, n7)
cands = [
("c1 제곱", c1 ** 2),
("c1 곱하기 c2", c1 * c2),
("c1 곱하기 c3", c1 * c3),
("c1 과 겹치는 c4", c4),
("잡음 하나", r10.normal(0, 1, n7)),
]
base_X = np.column_stack([c1, c2, c3])
def cv_r2(X, y, k=5, rr=None):
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], 1e-4)
v.append(r2(y[f], pred(X[f], b)))
return float(np.mean(v))
b_r2 = cv_r2(base_X, yz, 5, np.random.default_rng(7))
w0 = max(pw(x[0]) for x in cands + [("후보 변수", 0)])
print(" 기본 세 변수만의 교차검증 결정계수는 %.6f 입니다" % b_r2)
print(" " + rw("후보 변수", w0) + " " + rl("y 와의 상관", 14) + " " + rl("넣었을 때 결정계수", 20) + " " + rl("올린 값", 12))
for nm, v in cands:
cr = float(np.corrcoef(v, yz)[0, 1])
q = cv_r2(np.column_stack([base_X, v]), yz, 5, np.random.default_rng(7))
print(" " + rw(nm, w0) + " " + rl("%.6f" % cr, 14) + " " + rl("%.6f" % q, 20) + " " + rl("%.6f" % (q - b_r2), 12))
print(" 참 변수인 c1 곱하기 c2 와 c4 만 크게 올립니다")
print(" c4 는 y 와의 상관이 %.6f 로 잡음 수준인데 넣으면 %.6f 나 올립니다" % (
float(np.corrcoef(c4, yz)[0, 1]), cv_r2(np.column_stack([base_X, c4]), yz, 5, np.random.default_rng(7)) - b_r2))
print(" c1 과 겹쳐 있어 혼자서는 안 보이고 c1 옆에 놓아야 보이기 때문입니다")
print(" 상관 하나로 후보를 고르면 이런 변수를 놓칩니다")
print()
print(" (4) 체크리스트를 정리합니다")
rows = [
("y 를 쓰는 변환인가", "겹 안에서만 만듭니다"),
("미래 정보가 들어갔나", "시간을 지켜 자릅니다"),
("추론 때 구할 수 있나", "학습에만 있는 값은 못 씁니다"),
("결측이 나오면", "채우는 규칙도 겹 안에서"),
("칸이 새로 생기면", "기타로 보낼 자리를 미리"),
("정말 듣는가", "교차검증으로 확인"),
]
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(" 229강은 이렇게 만든 변수 위에서 손잡이를 어떻게 고를지 봅니다")
print()
print("=" * 78)
print("검산 끝")
print("=" * 78)
# ==============================================================================
# 228강 특성공학 코드 검산
# ==============================================================================
#
# 문제 1. 변수를 만든다는 것
#
# (1) 무엇을 만드는지 정리합니다
# 무엇 무엇을 하나 왜 필요한가
# 눈금 맞추기 단위를 통일함 거리와 벌점이 공평해짐
# 모양 펴기 치우친 분포를 고침 직선 가정에 맞춤
# 범주 다루기 숫자로 바꿈 모형이 읽을 수 있게
# 상호작용 곱을 만듦 한쪽 효과가 다른 쪽에 달림
# 굽은 항 제곱이나 조각을 넣음 직선으로 안 되는 모양
# 묶어 요약 여러 줄을 하나로 관측 단위가 다를 때
# 227강 문제 4 에서 제곱 항 하나가 PR 넓이를 두 배로 만들었습니다
# 그 변수를 어떻게 만드는지가 이 강의입니다
#
# (2) 참 모양을 알 때와 모를 때를 견줍니다
# 무엇을 넣었나 변수 수 따로 뗀 RMSE 결정계수
# 원래 변수 셋 3 1.974625 0.200825
# 로그만 넣음 3 1.952802 0.218392
# 곱만 넣음 4 0.420537 0.963752
# 둘 다 넣음 4 0.397524 0.967611
# 참 모양 그대로 3 0.395039 0.968015
# 원래 변수만 쓰면 결정계수가 0.200825 입니다
# 로그와 곱을 함께 넣으면 0.967611 로 오릅니다
# 참 모양을 세 개로 적으면 변수가 더 적은데 0.968015 입니다
# 변수를 많이 만드는 것이 아니라 맞는 것을 만드는 일입니다
#
# (3) 아무 변환이나 넣으면 어떻게 되는지 봅니다
# 무엇을 넣었나 변수 수 학습 RMSE 따로 뗀 RMSE
# 원래 셋 3 1.800802 1.974625
# 제곱까지 6 1.792498 1.984122
# 세제곱까지 9 1.777548 2.016684
# 네제곱까지 12 1.774443 2.007200
# 제곱 세제곱을 아무리 넣어도 따로 뗀 오차가 1.974625 에서 2.007200 로 거의 안 줄어듭니다
# 참 모양이 로그와 곱인데 거듭제곱으로는 못 만들기 때문입니다
# 로그 하나가 거듭제곱 아홉 개보다 낫습니다
#
# 문제 2. 눈금 맞추기와 모양 펴기
#
# (1) 눈금이 필요한 모형과 아닌 모형을 가릅니다
# 모형 눈금이 필요한가 왜
# 최소제곱 필요 없음 계수가 단위를 흡수함
# 벌점 회귀 필요함 벌점이 계수 크기를 봄
# 이웃 방법 필요함 거리가 단위를 그대로 씀
# 경사하강 필요함 수렴이 느려짐
# 나무 필요 없음 각 변수에서 자를 뿐
# 주성분 필요함 분산이 큰 축을 고름
#
# (2) 단위를 바꿔 실제로 재 봅니다
# 첫 변수의 단위 이웃 그대로 이웃 표준화 로지스틱 그대로 그때 ROC 넓이 로지스틱 표준화
# 모두 같음 0.833333 0.833333 0.823333 0.921678 0.823333
# 천 배 차이 0.716667 0.833333 0.250000 0.249608 0.823333
# 백만 배 차이 0.716667 0.833333 0.753333 0.752049 0.823333
# 단위를 백만 배로 키우면 이웃 방법은 0.833333 에서 0.716667 로 무너집니다
# 표준화하면 0.833333 로 되돌아옵니다
# 213강 문제 2 에서 거리가 큰 축 하나에 잡아먹힌다고 한 것입니다
# 경사하강으로 푸는 로지스틱은 더 심합니다
# 천 배 차이에서 정확도가 0.250000 이고 ROC 넓이가 0.249608 입니다
# ROC 넓이가 절반보다 낮다는 것은 순위가 뒤집혔다는 뜻입니다
# 단위가 커서 걸음이 발산했고 부호가 틀린 자리에 갇힌 것입니다
# 표준화하면 0.823333 로 돌아옵니다
#
# (3) 눈금 맞추는 방법들을 견줍니다
# 방법 보통 점의 폭 이상점 없었으면 줄어든 비율
# 표준화 0.999066 5.790416 0.827462
# 최소최대 0.130342 0.867332 0.849721
# 중앙값과 사분위 4.223868 4.340844 0.026948
# 이상점 열 개가 들어오면 최소최대는 보통 점의 폭이 0.849721 만큼 쪼그라듭니다
# 가장 큰 값 하나가 전체 눈금을 정하기 때문입니다
# 표준화도 0.827462 만큼 줄어듭니다. 표준편차가 이상점에 끌려 커지기 때문입니다
# 중앙값과 사분위는 0.026948 로 거의 안 흔들립니다
# 사분위 범위는 가운데 절반만 보므로 이상점이 못 건드립니다
# (4) 치우친 분포를 펴 봅니다
# 변환 비대칭도 따로 뗀 RMSE 결정계수
# 그대로 3.051374 0.865705 0.649483
# 로그 -0.018095 0.481882 0.891394
# 제곱근 1.294480 0.608621 0.826754
# 순위를 0 과 1 사이로 -0.000000 0.518941 0.874048
# 비대칭도가 3.051374 에서 -0.018095 로 줄고 결정계수가 0.649483 에서 0.891394 로 오릅니다
# 참 관계가 로그였으므로 로그가 맞습니다
# 비대칭도가 줄어서가 아니라 참 모양에 맞아서 좋아진 것입니다
#
# 문제 3. 범주형 변수
#
# (1) 방법들을 정리합니다
# 방법 무엇을 하나 무엇이 문제인가
# 한 자리만 1 칸마다 열 하나 칸이 많으면 열이 폭발
# 하나 빼고 기준 칸을 뺌 선형모형의 공선성 방지
# 순서를 숫자로 1 2 3 으로 간격이 같다고 가정함
# 빈도로 나온 횟수로 다른 칸이 같은 값
# 목표값 평균으로 칸별 y 평균 누출 위험이 큼
# 드문 칸 묶기 기타로 합침 표본이 적은 칸
#
# (2) 칸이 많을 때 어떻게 되는지 봅니다
# 무엇을 했나 변수 수 따로 뗀 RMSE 결정계수
# 칸을 버림 1 2.729578 0.050154
# 한 자리만 1 151 2.564266 0.161721
# 칸 번호를 숫자로 2 2.694605 0.074338
# 빈도로 2 2.602251 0.136702
# 칸이 150 개이고 학습 표본이 1400 개입니다
# 가장 흔한 칸은 215 개이고 학습에 한 번도 안 나온 칸이 4 개입니다
# 칸 번호를 그대로 숫자로 쓰면 칸을 버린 것과 거의 같습니다
# 번호에는 순서가 없는데 순서가 있다고 읽기 때문입니다
#
# (3) 목표값 평균 인코딩의 누출을 잽니다
# 어떻게 만들었나 학습 RMSE 따로 뗀 RMSE 차이
# 그냥 학습 전체로 2.391645 2.564959 0.173314
# 겹 밖 값으로 2.667399 2.513587 -0.153812
# 평활을 20 으로 2.526421 2.537712 0.011291
# 그냥 만들면 학습에서 2.391645 인데 밖에서 2.564959 입니다
# 자기 y 가 자기 변수에 들어갔기 때문입니다
# 겹 밖 값으로 만들면 학습과 밖의 차이가 -0.153812 로 줄어듭니다
# 227강 문제 5 의 재표집 누출과 정확히 같은 구조입니다
#
# (4) 드문 칸을 어떻게 할지 봅니다
# 무엇을 했나 남은 칸 수 따로 뗀 RMSE
# 안 묶음 149 2.564266
# 3 개 미만 묶음 115 2.532210
# 8 개 미만 묶음 44 2.537505
# 20 개 미만 묶음 13 2.590215
# 학습 표본이 칸당 평균 9.333333 개입니다
# 3 개 미만을 묶으면 2.564266 에서 2.532210 로 좋아집니다
# 드문 칸은 표본이 적어 평균이 잡음에 흔들리기 때문입니다
# 그런데 20 개 미만까지 묶으면 2.590215 로 다시 나빠집니다
# 쓸 만한 칸까지 지웠기 때문입니다. 어디서 자를지는 손잡이입니다
#
# 문제 4. 상호작용과 굽은 항
#
# (1) 언제 필요한지 정리합니다
# 어떤 관계 무엇을 만드나 예
# 한쪽 효과가 다른 쪽에 달림 곱을 넣음 x1 곱하기 x2
# 효과가 커지다 꺾임 제곱을 넣음 x 제곱
# 특정 값에서 꺾임 조각 직선 max(x-c, 0)
# 주기가 있음 사인과 코사인 시각이나 요일
# 비율이 중요함 나눗셈 밀도나 단가
#
# (2) 나무는 스스로 만들고 직선은 못 만드는 것을 봅니다
# 무엇 따로 뗀 RMSE 결정계수
# 직선 곱 없이 2.851917 -0.042511
# 직선 곱 넣고 0.526823 0.964426
# 깊이 5 나무 0.952978 0.883595
# 참 관계가 곱인데 직선은 결정계수 -0.042511 로 아무것도 못 잡습니다
# 곱 하나를 넣으면 0.964426 입니다
# 나무는 곱을 안 줘도 0.883595 를 냅니다. 스스로 조각을 만들기 때문입니다
# 다만 나무는 0.883595 로 곱을 직접 준 직선보다 못합니다
# 계단으로 기울어진 면을 흉내 내야 하기 때문입니다
#
# (3) 굽은 모양을 어떻게 담을지 견줍니다
# 무엇으로 담았나 변수 수 따로 뗀 RMSE 결정계수
# 직선만 1 1.709614 0.904368
# 제곱까지 2 0.782444 0.979969
# 세제곱까지 3 0.706516 0.983668
# 조각 직선 매듭 셋 4 0.680889 0.984831
# 구간을 칸으로 다섯 5 1.370760 0.938521
# 참 모양이 4 에서 꺾이는 조각 직선입니다
# 조각 직선이 RMSE 0.680889 로 가장 낫습니다
# 구간을 칸으로 나누면 안에서 평평해져 꺾인 자리를 못 살립니다
# 201강의 기저 전개가 여기서 그대로 쓰입니다
#
# (4) 곱을 다 만들면 어떻게 되는지 봅니다
# 원래 변수 수 곱까지 넣은 수 원래만 결정계수 곱까지 결정계수
# 5 15 0.195270 0.897640
# 10 55 0.251961 0.854970
# 20 210 0.129171 0.827324
# 40 820 0.090540 0.226815
# 변수가 5 개면 곱을 다 넣어 0.897640 로 크게 오릅니다
# 40 개면 곱이 820 개가 되고 학습 표본 450 개를 넘어 0.226815 로 무너집니다
# 209강의 차원의 저주가 만든 변수에서도 그대로 일어납니다
#
# 문제 5. 실무에서 쓰기
#
# (1) 순서를 정리합니다
# 순서 무엇을 하나 왜
# 먼저 자료를 보고 단위와 분포 확인 무엇이 이상한지 봄
# 다음 도메인 지식으로 변수 제안 가장 크게 듣는 자리
# 다음 겹 안에서만 만들기 누출 차단
# 다음 만든 변수를 교차검증으로 검사 정말 듣는지 확인
# 마지막 안 듣는 변수는 버림 잡음만 늘림
#
# (2) 눈금을 겹 밖에서 맞추면 어떻게 되는지 봅니다
# 단위 맞추기는 겹 밖에서 해도 크게 안 다칩니다. 얼마나인지 잽니다
# 언제 표준화했나 교차검증 정확도
# 겹을 나누기 전에 0.835000
# 겹 안에서 0.835000
# 차이가 0.000000 로 작습니다. 평균과 표준편차는 전체 요약이라 한 점의 정보가 거의 안 새기 때문입니다
# 문제 3 의 목표값 평균 인코딩은 y 를 쓰므로 이야기가 완전히 다릅니다
# 규칙은 하나입니다. y 를 쓰는 변환은 반드시 겹 안에서 합니다
#
# (3) 만든 변수가 정말 듣는지 검사합니다
# 기본 세 변수만의 교차검증 결정계수는 0.015109 입니다
# 후보 변수 y 와의 상관 넣었을 때 결정계수 올린 값
# c1 제곱 0.063596 0.014302 -0.000807
# c1 곱하기 c2 0.801824 0.647415 0.632306
# c1 곱하기 c3 -0.030567 0.015074 -0.000035
# c1 과 겹치는 c4 0.095320 0.091069 0.075960
# 잡음 하나 0.031764 0.015207 0.000097
# 참 변수인 c1 곱하기 c2 와 c4 만 크게 올립니다
# c4 는 y 와의 상관이 0.095320 로 잡음 수준인데 넣으면 0.075960 나 올립니다
# c1 과 겹쳐 있어 혼자서는 안 보이고 c1 옆에 놓아야 보이기 때문입니다
# 상관 하나로 후보를 고르면 이런 변수를 놓칩니다
#
# (4) 체크리스트를 정리합니다
# 무엇을 묻나 어떻게
# y 를 쓰는 변환인가 겹 안에서만 만듭니다
# 미래 정보가 들어갔나 시간을 지켜 자릅니다
# 추론 때 구할 수 있나 학습에만 있는 값은 못 씁니다
# 결측이 나오면 채우는 규칙도 겹 안에서
# 칸이 새로 생기면 기타로 보낼 자리를 미리
# 정말 듣는가 교차검증으로 확인
# 셋째 줄이 실무에서 가장 자주 놓치는 자리입니다
# 229강은 이렇게 만든 변수 위에서 손잡이를 어떻게 고를지 봅니다
#
# ==============================================================================
# 검산 끝
# ==============================================================================