Quiz / ML 전처리와 피처 / 전체 문항
GitHub

ML 전처리와 피처 전체 문항

스케일링, 피처 선택, 인코딩, 결측치 처리. 문항과 선택지별 해설을 글 순서대로 나열했습니다.

30문항 · 글 5편
직접 풀어보기 →

One-Hot과 Label, 범주형 데이터 인코딩을 고르는 기준

원문 읽기 →
개념 확인구조
도시 컬럼에 Label Encoding을 적용했다. 대구=0, 부산=1, 서울=2, 인천=3

이 결과를 선형 회귀에 넣으면 안 되고 결정 트리에는 넣어도 되는 이유는 무엇인가?

  1. 선형 회귀는 정수형 입력을 실수로 변환하면서 정밀도를 잃지만 트리는 그대로 쓰기 때문이다.

    정밀도와 무관하다. 두 모델 모두 같은 숫자를 받고, 그 숫자를 어떻게 해석하느냐가 갈린다.

  2. 선형 회귀는 계수를 하나만 학습하지만 트리는 카테고리마다 다른 규칙을 만들 수 있기 때문이다.

    결과적으로는 맞지만 이유가 아니다. 트리가 카테고리를 갈라낼 수 있는 것은 숫자를 크기가 아니라 분할 기준으로만 쓰기 때문이다.

  3. 정답선형 회귀는 숫자를 크기로 읽어 없던 순서를 학습하지만 트리는 분할 기준으로만 쓰기 때문이다.

    계수를 곱하는 모델에게 인천은 부산의 세 배이고 서울과 부산의 간격이 부산과 대구의 간격과 같다. 트리는 이 값보다 큰가 작은가만 물으므로 어떤 번호가 붙었든 분할을 거듭해 필요한 그룹을 갈라낸다.

  4. 선형 회귀는 결측을 허용하지 않지만 트리는 결측을 처리할 수 있기 때문이다.

    결측 처리와 무관한 문제다. 여기서는 모든 값이 채워져 있다.

해설

Label Encoding은 카테고리를 가나다순으로 정렬한 뒤 0부터 번호를 붙인다. 그런데 계수를 곱하거나 거리를 재는 모델은 이 번호를 크기로 읽으므로, 도시 데이터에 존재하지 않는 순서와 간격을 진짜 관계로 학습한다. 트리 계열은 이 숫자를 크기가 아니라 분할 기준으로만 쓴다. 어떤 번호가 붙었든 분할을 거듭하면 필요한 그룹을 갈라낼 수 있으므로 거짓 순서가 문제 되지 않고, 그래서 트리 모델에서는 Label Encoding이 실무 기본값이다. 순서가 실제로 존재하는 변수라면 이야기가 다르다. 그때는 가나다순이 아니라 도메인 순서를 직접 지정해 넣는다. 간격까지 같다는 보장은 없지만 방향이 맞으므로 모델이 단조 관계를 쓸 수 있다.

이 내용 다시 읽기 →
상황 적용설계

트리 모델에서 One-Hot 인코딩이 오히려 불리한 이유는 무엇인가?

  1. 0과 1만 갖는 컬럼은 분산이 낮아 트리가 분할 후보로 잘 고르지 않기 때문이다.

    분산의 절댓값은 분할 선택 기준이 아니다. 트리는 불순도 감소량으로 후보를 고른다.

  2. 정답각 컬럼이 한 카테고리인가 아닌가만 물을 수 있어 여러 카테고리를 한쪽에 모으려면 분할을 여러 번 써야 하기 때문이다.

    번호로 넣은 컬럼 하나는 어느 지점에서든 자를 수 있어 한 번의 분할로 여러 카테고리를 묶을 수 있다. 펼쳐 놓으면 같은 그룹을 만드는 데 깊이만 낭비된다.

  3. 컬럼 사이에 다중공선성이 생겨 불순도 계산이 왜곡되기 때문이다.

    트리는 다중공선성의 영향을 거의 받지 않는다. 컬럼 사이의 선형 종속은 분할 규칙에 개입하지 않는다.

  4. 카테고리가 늘어나면 각 컬럼의 값이 대부분 0이 되어 결측으로 처리되기 때문이다.

    0은 결측이 아니라 유효한 값이다. 트리는 이를 그대로 분할에 쓴다.

해설

트리에서 One-Hot이 불리한 이유는 분할의 정보량 때문이다. 번호로 넣은 컬럼 하나는 트리가 어느 지점에서든 자를 수 있어 한 번의 분할로 여러 카테고리를 한쪽에 모을 수 있다. 펼쳐 놓으면 각 컬럼이 이 카테고리인가 아닌가 하나만 물을 수 있어, 같은 그룹을 만들려면 분할을 여러 번 써야 하고 깊이만 낭비된다. 그래서 트리 계열에서는 번호를 매기는 쪽이 기본이고, 순서가 있는 변수라면 도메인 순서를 지정해 넣는다. LightGBM처럼 인코딩을 아예 건너뛰는 길도 있다. 범주형 컬럼을 지정하면 카테고리를 정렬해 최적 분할점을 직접 찾으므로, 범주를 두 집합으로 나누는 문제가 정렬된 축에서의 이분 문제로 바뀐다.

이 내용 다시 읽기 →
상황 적용구조
도시 4종을 One-Hot으로 펼쳐 네 컬럼을 만들었다. 모든 행에서 네 컬럼의 합이 정확히 1이다.

선형 회귀에서 무엇이 문제이고 어떻게 해결하는가?

  1. 합이 1이라 정규화 조건을 위반하므로 각 컬럼을 컬럼 수로 나눠야 한다.

    합이 1인 것 자체는 위반이 아니다. 문제는 그래서 한 컬럼이 나머지로 완전히 결정된다는 점이다.

  2. 0이 대부분이라 희소 행렬이 되므로 밀집 배열로 변환해야 한다.

    저장 형식의 문제이지 계수 추정의 문제가 아니다. 형식을 바꿔도 종속 관계는 그대로다.

  3. 합이 1이면 각 컬럼의 계수가 확률로 해석되어 음수를 가질 수 없으므로 제약을 걸어야 한다.

    계수는 확률이 아니고 음수도 자유롭게 가질 수 있다. 그런 제약은 필요하지 않다.

  4. 정답한 컬럼이 나머지로 완벽히 결정되는 선형 종속이 생기므로 하나를 떨어뜨린다.

    완전한 선형 종속은 역행렬 계산을 불안정하게 만들고 계수 해석을 망가뜨린다. 기준이 된 카테고리는 나머지가 전부 0인 상태로 표현되므로 정보를 잃지 않는다.

해설

카테고리를 전부 펼치면 모든 행에서 컬럼의 합이 정확히 1이 되고, 마지막 컬럼은 나머지로 완벽히 결정된다. 선형 회귀에서는 이 완전한 선형 종속이 역행렬 계산을 불안정하게 만들고 계수 해석을 망가뜨리므로 하나를 떨어뜨린다. 기준이 된 카테고리는 나머지가 전부 0인 상태로 표현되니 정보는 사라지지 않고, 남은 계수들이 그 기준 대비 차이로 읽힌다. 다만 이것은 선형 모델의 문제다. 트리 모델은 다중공선성의 영향을 거의 받지 않으므로 굳이 떨어뜨리지 않는 편이 해석에 낫다. 규제가 걸린 선형 모델에서도 판단이 갈릴 수 있는데, 하나를 떨어뜨리면 기준 카테고리만 규제를 받지 않는 비대칭이 생기기 때문이다.

이 내용 다시 읽기 →
분석과 판단함정
train_enc = pd.get_dummies(train, columns=['city'])
test_enc  = pd.get_dummies(test,  columns=['city'])

model.fit(train_enc, y_train)
model.predict(test_enc)   # 훈련에는 대구가 있고 테스트에는 없다

무엇이 잘못됐는가?

  1. 정답실제로 등장한 값만 보고 컬럼을 만들므로 훈련과 테스트의 컬럼 구성이 어긋난다.

    컬럼 수가 다르면 예외로 멈추고, 빠진 것과 새로 생긴 것이 상쇄되어 수가 우연히 같으면 엉뚱한 컬럼에 가중치가 곱해진 채 조용히 지나간다. fit에서 목록을 기억하는 인코더를 써야 이 문제가 없다.

  2. 불리언 타입으로 출력되므로 모델이 받지 못해 예외가 발생한다.

    정수형으로 지정할 수 있고 불리언도 대개 그대로 처리된다. 여기서 터지는 것은 타입이 아니라 컬럼 구성이다.

  3. 테스트에서 대구 컬럼이 사라져 그 카테고리의 정보가 손실되므로 결측 대체가 필요하다.

    테스트에 대구가 없으므로 손실될 정보 자체가 없다. 문제는 모델이 기대하는 컬럼 자리가 비어 어긋난다는 점이다.

  4. 훈련과 테스트를 각각 인코딩했으므로 전체 데이터로 한 번에 인코딩해야 한다.

    전체 데이터로 인코딩하면 테스트에만 있던 카테고리가 컬럼으로 자리를 잡아 점수가 부풀려진다. 방향이 반대다.

해설

이 함수는 넘겨받은 데이터에 실제로 등장한 값만 보고 컬럼을 만든다. 훈련에 있던 카테고리가 테스트에 없으면 두 결과의 컬럼 구성이 어긋난다. 컬럼 수가 다르면 예외로 멈추지만, 빠진 컬럼과 새로 생긴 컬럼이 상쇄되어 수가 우연히 같으면 엉뚱한 컬럼에 가중치가 곱해진 채 조용히 지나간다. 꼭 써야 한다면 테스트 결과를 훈련 결과의 컬럼 목록에 맞춰 재정렬하고 빠진 자리를 0으로 채워야 한다. 학습 코드에는 처음부터 fit에서 카테고리 목록을 기억했다가 transform에서 항상 같은 컬럼을 만들어내는 인코더를 쓰는 편이 안전하다. 여기에 모르는 카테고리를 무시하는 옵션까지 켜면 프로덕션에서 새 값이 들어와도 서비스가 멈추지 않는다. 그 값은 모든 컬럼이 0인 벡터가 되어 정보는 없지만 예외도 나지 않는다.

이 내용 다시 읽기 →
상황 적용설계
카테고리 수에 따른 컬럼 수 비교다. 카테고리 One-Hot Binary Label 10 10 4 1 100 100 7 1 1,000 1,000 10 1

Binary Encoding이 치르는 대가는 무엇인가?

  1. 카테고리 수가 2의 거듭제곱이 아니면 남는 비트 조합이 생겨 정보가 손실된다.

    쓰이지 않는 조합이 생길 뿐 손실은 없다. 각 카테고리는 여전히 유일한 비트 패턴을 갖는다.

  2. 정답각 비트 컬럼 자체에는 아무 의미가 없어 해석을 포기하게 된다.

    어떤 비트가 1이라는 사실은 서로 무관한 카테고리들이 섞인 임의의 집합을 가리킬 뿐이다. 압축은 되지만 그 컬럼의 계수나 중요도를 읽을 수 없다.

  3. 비트 컬럼끼리 완전한 선형 종속이 생겨 선형 모델에서 쓸 수 없다.

    비트 컬럼 사이에는 그런 종속 관계가 없다. 오히려 전부 펼치는 쪽에서 그 문제가 생긴다.

  4. 카디널리티가 높아질수록 컬럼 수가 선형으로 늘어 결국 One-Hot과 비슷해진다.

    표에 나온 대로 로그로 늘어난다. 1,000개 카테고리에 10컬럼이면 차이가 압도적이다.

해설

Binary Encoding은 카테고리에 정수를 매긴 뒤 그 정수를 이진수로 적고 각 비트를 컬럼 하나로 만든다. 필요한 컬럼 수가 카테고리 수의 로그로 늘어나므로 1,000개 카테고리도 10컬럼이면 되고, 번호를 하나 매기는 방식만큼의 거짓 순서도 만들지 않는다. 대신 각 비트 컬럼 자체에는 아무 의미가 없다. 어떤 비트가 1이라는 사실은 서로 무관한 카테고리들이 섞인 임의의 집합을 가리킬 뿐이라, 그 컬럼의 계수나 중요도를 읽을 수 없다. 압축은 되지만 해석은 포기하는 거래다. 카디널리티가 아주 높은 구간에서는 이것도 부족해서, 타깃 값을 끌어와 범주를 숫자 하나로 압축하는 방법이나 임베딩으로 넘어가게 된다.

이 내용 다시 읽기 →
분석과 판단판단
로지스틱 회귀로 이탈 예측 모델을 만든다. 범주형 컬럼이 셋이다. gender 2종 education 고졸/학사/석사/박사 product_id 3,400종

셋을 각각 어떻게 다뤄야 하는가?

  1. 셋 다 One-Hot으로 통일해야 선형 모델의 계수 해석이 일관된다.

    3,400종을 펼치면 컬럼이 감당되지 않고 각 컬럼이 참조하는 샘플 수가 줄어 과적합 위험이 커진다. 해석의 일관성보다 그쪽이 큰 문제다.

  2. 정답gender는 One-Hot, education은 Ordinal, product_id는 타깃 기반으로 압축한다.

    순서가 있으면 그 순서를 직접 지정해 넣고, 없으면서 카테고리가 적으면 펼치고, 카디널리티가 수백을 넘으면 숫자 하나로 줄이는 방법이 필요하다. 세 질문이 각각 다른 답을 낸다.

  3. 셋 다 Label로 번호를 매긴 뒤 표준화하면 크기 문제가 해소된다.

    표준화는 값의 범위만 바꾼다. 번호 사이의 순서와 간격이 그대로 남아 선형 모델이 없던 관계를 학습한다.

  4. education만 One-Hot으로 펼치고 나머지 둘은 Label로 번호를 매긴다.

    순서가 있는 education을 펼쳐 그 정보를 버리는 데다, 3,400종짜리 product_id의 번호를 선형 모델이 크기로 읽게 된다. 2종뿐인 gender만 Label과 One-Hot이 사실상 같다.

해설

범주형 인코딩에서 실제로 결정해야 하는 것은 세 가지다. 이 변수에 순서가 있는가, 카테고리가 몇 개인가, 뒤에 붙는 모델이 숫자의 크기를 보는가 분할점만 보는가. 순서가 있으면 그 순서를 직접 지정해 넣고, 없으면서 카테고리가 적으면 펼치고, 트리를 쓸 것이라면 번호를 매긴다. 이 세 줄이 대부분의 상황을 덮는다. 남는 경우가 카디널리티가 수십을 넘어가는 변수다. 펼치기에는 컬럼이 감당이 안 되고, 번호를 매기는 것은 트리 밖에서 쓸 수 없으며, 빈도로 바꾸는 방법은 빈도 자체가 신호일 때만 통한다. 이 구간에서는 타깃 값을 끌어와 범주를 숫자 하나로 압축하는 방법이나 임베딩이 필요해진다.

이 내용 다시 읽기 →

StandardScaler와 MinMaxScaler, 피처 스케일링은 무엇을 기준으로 고르나

원문 읽기 →
개념 확인설계
원소의 99%가 0인 TF-IDF 희소 행렬에 스케일링을 걸려고 한다.

MaxAbsScaler를 쓰는 이유는 무엇인가?

  1. 결과가 -1과 1 사이에 들어와 텍스트 데이터의 값 범위에 맞기 때문이다.

    범위가 정해지는 것은 MinMaxScaler도 마찬가지다. 그것만으로는 희소 행렬을 고를 이유가 되지 못한다.

  2. 정답곱셈만 하므로 0이 계속 0으로 남아 희소 구조가 유지되기 때문이다.

    평균이나 최솟값을 빼는 스케일러는 0이던 원소를 0이 아닌 값으로 바꾼다. 저장하지 않던 0들이 전부 실제 값이 되면서 메모리가 폭증한다.

  3. 절댓값을 쓰므로 음수 가중치가 있는 TF-IDF 값을 안전하게 다룰 수 있기 때문이다.

    TF-IDF 값은 음수가 되지 않는다. 절댓값을 쓰는 것은 음수를 처리하기 위해서라기보다 나눌 기준을 정하기 위해서다.

  4. 이상치에 강해 특정 단어의 빈도가 극단적으로 높아도 흔들리지 않기 때문이다.

    기준이 절댓값 최댓값이라 오히려 극단값에 그대로 좌우된다. 이상치에 강한 쪽은 중앙값과 사분위 범위를 쓰는 스케일러다.

해설

MaxAbsScaler의 존재 이유는 0을 0으로 남긴다는 점 하나다. 각 피처를 절댓값 최대치로 나누기만 하므로 곱셈만 일어나고 0은 계속 0이다. 평균을 빼는 스케일러는 0이던 원소가 평균의 부호를 뒤집은 값이 되고, 최솟값을 빼는 스케일러도 최솟값이 0이 아니면 마찬가지로 0을 옮긴다. 원소의 대부분이 0인 희소 행렬에서는 이것이 치명적이다. 저장하지 않던 0들이 전부 실제 값이 되면서 메모리가 폭증한다. 그래서 TF-IDF 벡터나 원-핫 인코딩 결과처럼 희소한 데이터에서는 이 스케일러가 기본 선택지가 되고, sklearn 구현도 희소 행렬을 그대로 받는다.

이 내용 다시 읽기 →
상황 적용함정
[1, 2, 3, 4, 5, 1000]에 RobustScaler를 적용했다. 중앙값 3.5, IQR 2.5 → [-1.0, -0.6, -0.2, 0.2, 0.6, 398.6]

이 결과가 말해주는 것은 무엇인가?

  1. 정답이상치가 기준값에 영향을 주지 않을 뿐 이상치 자체는 그대로 남는다.

    정상 구간 1에서 5의 간격이 균등하게 살아 있는 것이 이 스케일러의 이득이다. 1000은 변환 후에도 극단값으로 남아 있으므로 없애려면 별도의 처리가 필요하다.

  2. IQR로 나눈 값이 1을 넘으면 이상치이므로 자동으로 판별할 수 있다.

    정상 데이터도 사분위 범위 바깥에 놓이면 1을 넘는다. 이 값은 판정 기준이 아니라 단위를 바꾼 결과다.

  3. 398.6은 계산 오류이므로 IQR이 0에 가까운 경우를 따로 처리해야 한다.

    여기서 사분위 범위는 2.5로 충분히 크다. 1000이 실제로 그만큼 멀리 있어서 나온 정직한 값이다.

  4. 이상치가 섞이면 중앙값도 함께 끌려가므로 먼저 제거한 뒤 적용해야 한다.

    중앙값과 사분위 범위는 값의 순위에 기반한 통계량이라 극단값이 몇 개 섞여도 흔들리지 않는다. 실제로 여기서도 정상 구간의 간격이 그대로다.

해설

RobustScaler가 이상치에 강하다는 말은 이상치가 스케일링 기준값에 영향을 주지 않는다는 뜻이지 이상치를 없앤다는 뜻이 아니다. 중앙값과 사분위 범위는 값의 순위에 기반한 통계량이라 극단값이 몇 개 섞여도 흔들리지 않으므로 정상 구간의 간격이 균등하게 살아 있고, 그것이 이 스케일러의 이득이다. 같은 데이터에 평균과 표준편차를 쓰면 두 통계량이 통째로 끌려가 1과 5의 차이가 0.01로 뭉개진다. 최솟값과 최댓값을 쓰는 쪽은 더 심하다. 평균은 극단값을 다른 값들과 나눠 가지지만 최댓값은 극단값 그 자체이기 때문이다. 다만 어느 쪽이든 변환 후에도 극단값 자체는 남으므로 지우고 싶다면 별도의 처리를 붙여야 한다.

이 내용 다시 읽기 →
상황 적용구조

규제를 거는 모델에서 스케일링이 선택이 아닌 이유는 무엇인가?

  1. 규제 항이 가중치의 제곱합이라 스케일이 큰 피처에서 수치 오버플로가 발생하기 때문이다.

    오버플로가 문제가 될 만한 크기는 아니다. 문제는 계산의 안정성이 아니라 페널티의 형평성이다.

  2. 정답규제가 모든 가중치에 같은 페널티를 매기는데 값이 작은 피처는 가중치가 커야 해서 더 큰 벌을 받기 때문이다.

    같은 영향력을 내는 데 필요한 가중치 크기가 피처마다 달라진다. 결국 규제가 스케일이 큰 피처를 편애하게 되므로 스케일을 맞춰야 모두가 같은 조건에서 규제를 받는다.

  3. 규제 계수를 교차 검증으로 고를 때 스케일이 다르면 후보 구간을 정할 수 없기 때문이다.

    후보 구간은 로그 스케일로 넓게 잡으면 되므로 그 자체가 막히지는 않는다. 근본 문제는 그 계수가 피처마다 다르게 작동한다는 점이다.

  4. 규제가 절편에도 적용되어 피처의 평균이 0이 아니면 편향이 생기기 때문이다.

    대부분의 구현은 절편을 규제 대상에서 제외한다. 편향의 원인이 아니다.

해설

규제는 모든 가중치에 같은 페널티를 매긴다. 그런데 피처 스케일이 다르면 같은 영향력을 내는 데 필요한 가중치 크기도 달라진다. 값이 작은 피처는 가중치가 커야 하고 그만큼 페널티를 더 크게 맞으므로, 결국 규제가 스케일이 큰 피처를 편애하게 된다. 스케일을 맞춰야 모든 피처가 같은 조건에서 규제를 받는다. 스케일링이 필요한지 판단할 때 물어야 할 질문은 결국 하나다. 이 모델이 값의 크기를 보는가 순서를 보는가. 거리를 재는 모델과 경사 하강으로 학습하는 모델, 규제를 거는 모델은 크기를 보므로 스케일링이 필요하고, 분할 기준으로 순서만 보는 트리 계열은 필요하지 않다.

이 내용 다시 읽기 →
분석과 판단구조
스케일이 제각각인 데이터로 경사 하강을 돌렸더니 비용이 줄기는 하는데 경로가 좁은 축을 사이에 두고 계속 튕겨 다닌다.

왜 이런 경로가 나오는가?

  1. 값이 큰 피처의 가중치가 먼저 수렴한 뒤 남은 축에서 진동이 시작되기 때문이다.

    축들은 매 스텝 동시에 갱신되므로 하나가 먼저 끝나고 다음이 시작되는 순서가 아니다. 진동은 처음부터 함께 일어난다.

  2. 정답등고선이 한쪽으로 늘어난 타원인데 학습률은 하나뿐이라 방향마다 적정 보폭이 어긋나기 때문이다.

    좁은 축에서는 매번 반대편으로 튕겨 나가고 넓은 축에서는 거의 나아가지 못한다. 스케일을 맞추면 등고선이 원에 가까워져 같은 보폭이 모든 방향에 맞아떨어진다.

  3. 그래디언트가 피처 값에 비례하므로 부호가 스텝마다 뒤집히기 때문이다.

    부호가 뒤집히는 것은 최저점을 지나쳤을 때 일어나는 현상이지 원인이 아니다. 지나치게 되는 이유가 보폭과 축의 폭이 어긋난 데 있다.

  4. 값이 큰 피처가 비용 함수의 최저점 자체를 원점에서 멀리 밀어내기 때문이다.

    최저점의 위치가 멀다고 진동이 생기지는 않는다. 진동을 만드는 것은 위치가 아니라 등고선의 모양이다.

해설

경사 하강법은 각 가중치를 그 방향의 그래디언트만큼 움직이는데, 어떤 가중치의 그래디언트는 짝지어진 피처 값에 비례한다. 큰 값을 가진 피처의 가중치는 그래디언트가 크고 작은 값을 가진 피처의 가중치는 그래디언트가 작으므로, 비용 함수의 등고선이 한쪽으로 심하게 늘어난 타원이 된다. 학습률은 하나뿐인데 방향마다 적정 보폭이 다르니 좁은 축에서는 매번 반대편으로 튕겨 나가고 넓은 축에서는 거의 나아가지 못한다. 스케일을 맞추면 등고선이 원에 가까워지고 어느 방향으로 가든 같은 보폭이 맞아떨어져 훨씬 적은 스텝으로 도착한다. 신경망에서는 여기에 문제가 하나 더 붙는다. 입력이 크면 곱한 결과도 커져 출력 범위가 막힌 활성화 함수의 포화 구간으로 밀려나고, 그 구간의 기울기가 0에 가까워 학습이 멈춘다.

이 내용 다시 읽기 →
상황 적용진단
소득 데이터에 StandardScaler를 적용했는데 히스토그램이 여전히 오른쪽으로 길게 늘어져 있다. 평균은 0, 표준편차는 1이 맞다.

무엇을 해야 하는가?

  1. RobustScaler로 바꾸면 꼬리 쪽 값들이 눌려 분포가 대칭에 가까워진다.

    기준 통계량만 바뀔 뿐 여전히 빼고 나누는 선형 변환이다. 꼬리의 모양은 그대로 남는다.

  2. MinMaxScaler로 범위를 0과 1로 묶으면 꼬리가 압축되어 해소된다.

    전체를 같은 비율로 줄이므로 꼬리의 상대적인 길이는 변하지 않는다. 오히려 극단값 하나가 나머지를 0 근처로 몰아붙인다.

  3. 정답스케일러는 위치와 폭만 바꾸므로 로그 변환이나 PowerTransformer로 분포 자체를 바꿔야 한다.

    로그는 큰 값일수록 더 많이 눌러주니 긴 꼬리가 접힌다. 0이 섞일 수 있으면 1을 더한 뒤 로그를 취하는 함수를 쓰고, 지수를 직접 고르기 싫으면 추정해 주는 변환기를 쓴다.

  4. 표준화가 제대로 적용되지 않은 것이므로 훈련 데이터로만 fit했는지 확인해야 한다.

    평균 0, 표준편차 1이 맞게 나왔으므로 변환 자체는 정상이다. 선형 변환이 할 수 있는 일이 거기까지일 뿐이다.

해설

일반적인 스케일러들은 전부 빼고 나누는 선형 변환이라 위치와 폭만 바꾼다. 정규분포였으면 정규분포로, 오른쪽으로 치우쳐 있었으면 치우친 채로 남는다. 소득이나 집값, 방문 횟수처럼 오른쪽 꼬리가 긴 데이터는 스케일이 아니라 분포 자체를 바꿔야 한다. 로그 변환이 가장 간단한 도구인데 큰 값일수록 더 많이 눌러주니 긴 꼬리가 접힌다. 0이 섞일 수 있는 데이터에는 1을 더한 뒤 로그를 취하는 함수를 쓴다. 지수를 직접 고르기 싫으면 정규분포에 가까워지도록 지수를 추정해 주는 변환기를 쓰면 되고, 이쪽은 변환 뒤 표준화까지 기본으로 수행하므로 뒤에 스케일러를 또 붙일 필요가 없다.

이 내용 다시 읽기 →
분석과 판단판단
Wine 데이터에 k=5 KNN을 돌리며 스케일러만 바꿨다. 없음 0.7407 Standard 0.9630 MinMax 0.9444 Robust 0.9444

이 표에서 읽어야 할 것은 무엇인가?

  1. Standard가 가장 높으므로 KNN에는 이 스케일러가 정답으로 정해져 있다.

    세 값의 차이는 테스트 표본 몇 건에 해당하는 폭이라 한 번의 분할로 우열을 가릴 수 없다. 스케일러 선택은 데이터의 분포 모양을 보고 정한다.

  2. MinMax와 Robust가 같은 값이므로 이 데이터에는 이상치가 전혀 없다는 뜻이다.

    두 스케일러가 우연히 같은 정확도를 낸 것과 이상치의 유무는 별개다. 정확도는 판정 결과의 요약이라 분포의 성질을 알려주지 않는다.

  3. 정답스케일러들 사이의 차이보다 스케일링을 하느냐 마느냐의 차이가 압도적으로 크다는 것이다.

    세 스케일러는 0.02 안쪽에서 갈리는데 스케일링 유무는 0.22를 만든다. 스케일링 없이 돌린 KNN은 값의 범위가 가장 큰 피처 하나로 이웃을 고르고 있었다.

  4. 정확도가 0.96까지 오른 것은 전처리가 테스트 정보를 흡수했기 때문이므로 누수를 의심해야 한다.

    훈련 데이터로만 기준을 잡고 테스트에는 변환만 적용하면 누수가 없다. 상승폭이 큰 것은 원래 왜곡이 그만큼 심했다는 뜻이다.

해설

Wine 데이터는 피처 스케일이 대놓고 제각각이다. 값의 범위가 가장 넓은 피처와 가장 좁은 피처의 자릿수가 세 개 차이 나므로, 스케일링 없이 거리를 재면 넓은 피처 하나가 계산을 독점한다. 이름만 KNN이지 실제로는 그 피처 하나로 이웃을 고르는 모델이었던 셈이고, 그래서 전처리 두 줄을 넣는 것만으로 정확도가 0.74에서 0.96으로 올라간다. 반면 세 스케일러 사이의 차이는 0.02 안쪽이라 한 번의 분할로 우열을 가릴 수 없다. 판단할 것은 스케일러 이름이 아니라 두 가지다. 이 피처의 값 분포가 어떤 모양인가, 그리고 이 모델이 값의 크기를 보는가 순서를 보는가.

이 내용 다시 읽기 →

필터·래퍼·임베디드, 피처를 걸러내는 세 갈래

원문 읽기 →
개념 확인구조

피처 선택 기법을 필터, 래퍼, 임베디드 셋으로 가르는 기준은 무엇인가?

  1. 타겟을 쓰느냐 안 쓰느냐다. 필터만 타겟 없이 동작한다.

    상호 정보량이나 상관계수를 쓰는 필터도 타겟을 본다. 분산 기준처럼 타겟을 안 보는 필터가 있을 뿐이다.

  2. 선택 결과가 피처의 부분집합이냐 새로 만든 축이냐다.

    셋 다 원래 피처 중 일부를 남긴다. 새 축을 만드는 것은 차원 축소 쪽 이야기다.

  3. 정답선택 단계에서 모델 학습이 몇 번 일어나느냐, 즉 모델 학습과의 시점 관계다.

    필터는 학습 전에 통계 점수로 끝내고, 래퍼는 학습을 되풀이하며 고르고, 임베디드는 학습 한 번 안에서 선택이 부산물로 나온다. 속도와 정확도의 차이가 전부 여기서 파생된다.

  4. 피처를 하나씩 보느냐 여럿을 묶어 보느냐다.

    조합을 보는지는 시점 관계에서 따라 나오는 결과다. 원인이 아니라 결과 쪽이다.

해설

셋을 가르는 기준은 원리가 아니라 모델 학습과의 시점 관계다. 필터는 학습하기 전에 피처의 통계적 성질만 보고 자르므로 선택 단계에서 학습이 0회다. 래퍼는 실제로 모델을 학습시켜 부분집합의 성능을 직접 재므로 조합마다 학습이 한 번씩 든다. 임베디드는 모델을 학습시키면 선택이 부산물로 따라 나오므로 학습이 한 번이다. 속도와 정확도의 차이는 전부 여기서 파생된다. 학습을 아예 하지 않는 쪽은 싸지만 피처를 하나씩 따로 채점하므로 조합 효과를 못 보고, 조합마다 학습하는 쪽은 정확하지만 비싸다. 어느 하나가 우월한 것이 아니라 피처 수와 모델 무게가 어디까지 감당되는지가 선택을 정한다.

이 내용 다시 읽기 →
상황 적용함정
두 이진 피처 A와 B가 있고 타겟은 둘의 배타적 논리합이다. A=0, B=0 → y=0 A=0, B=1 → y=1 A=1, B=0 → y=1 A=1, B=1 → y=0

필터 계열이 이 데이터에서 무엇을 놓치는가?

  1. 정답A와 B 각각은 타겟과 상관이 0이라 둘 다 탈락시키지만 함께 쓰면 타겟이 완벽하게 결정된다.

    필터는 피처를 하나씩 따로 채점하므로 혼자로는 쓸모없지만 짝을 이루면 강력해지는 관계를 잡아내지 못한다. 필터를 단독 선택기가 아니라 1차 관문으로 쓰는 이유다.

  2. A와 B의 상관계수가 매우 높아 중복으로 판정하고 하나를 버린다.

    두 피처는 서로 독립이라 상관이 0이다. 중복 판정에 걸리지 않는다.

  3. 이진 피처라 분산이 낮게 계산되어 분산 기준에서 먼저 잘려 나간다.

    0과 1이 절반씩이면 이진 피처가 가질 수 있는 최대 분산이다. 분산 기준으로는 오히려 안전하게 살아남는다.

  4. 상호 정보량이 비선형 관계를 잡으므로 필터로도 문제없이 걸러낼 수 있다.

    상호 정보량도 피처 하나와 타겟 사이에서만 계산된다. 여기서는 한 피처만으로 타겟에 대한 정보가 0이므로 값이 0으로 나온다.

해설

필터 계열은 피처를 하나씩 따로 채점한다. 그래서 혼자로는 타겟과 아무 상관이 없지만 다른 피처와 같이 쓰면 강력해지는 경우를 잡아내지 못하고, 배타적 논리합 관계가 그 극단적인 예다. 두 이진 피처 각각은 타겟과 상관이 0이지만 둘을 함께 쓰면 타겟이 완벽하게 결정된다. 상호 정보량은 관계의 모양을 가리지 않는다는 장점이 있어 제곱 같은 비선형 관계도 잡지만, 피처 하나와 타겟 사이에서 계산된다는 점은 같으므로 이 한계는 그대로다. 그래서 필터는 단독 선택기가 아니라 1차 관문으로 쓴다. 분산 0과 상관이 아주 높은 중복을 쳐내 후보를 크게 줄여 놓고, 조합을 보는 비싼 방법을 그 좁아진 공간에만 쓰는 순서가 실용적이다.

이 내용 다시 읽기 →
상황 적용진단
랜덤 포레스트의 feature_importances_를 뽑았더니 주문번호 컬럼이 1위로 올라왔다. 훈련 정확도는 1.0이고 검증 정확도는 낮다.

왜 이런 값이 나오는가?

  1. 주문번호가 시간 순서를 담고 있어 실제로 예측에 유용한 신호일 수 있다.

    그런 경우가 있기는 하지만 여기서는 검증 성능이 낮다. 훈련에서만 듣는 피처의 전형적인 모습이다.

  2. 트리 수가 부족해 중요도 추정이 흔들린 것이므로 트리를 늘리면 순위가 바로잡힌다.

    트리를 늘려도 고유 값이 많은 피처가 유리하다는 구조는 그대로다. 값이 매끄러워질 뿐 순위가 바뀌지 않는다.

  3. 정답불순도 감소는 분기를 잘게 쪼갤수록 커지므로 값이 거의 다 다른 피처가 최상위로 올라온다.

    주문번호처럼 고유 값이 많은 컬럼은 훈련 데이터를 완벽하게 가를 수 있다. 게다가 이 중요도는 훈련 데이터에서 계산되므로 과적합에 기여한 피처일수록 점수가 높게 나온다.

  4. 주문번호가 정수라 스케일이 커서 불순도 계산에서 큰 가중치를 받은 것이다.

    트리는 값의 순서만 보므로 스케일이 중요도에 영향을 주지 않는다. 문제는 크기가 아니라 고유 값의 개수다.

해설

불순도 감소는 분기를 잘게 쪼갤수록 커진다. 그래서 값이 거의 다 다른 피처, 예컨대 주문번호 같은 식별자 컬럼은 훈련 데이터를 완벽하게 가를 수 있고 중요도가 최상위로 올라온다. 실제로는 일반화에 아무 도움도 안 되는 잡음이다. 훈련 데이터에서 계산된다는 점도 문제를 키운다. 과적합에 기여한 피처일수록 점수가 높게 나오기 때문이다. 그래서 최종 판단은 검증 데이터에서 잰 순열 중요도로 하는 편이 낫다. 학습이 끝난 모델을 그대로 두고 피처 하나의 값만 무작위로 섞어 성능이 얼마나 떨어지는지를 보면, 카디널리티에 끌려다니지 않고 훈련에서만 잘 듣던 피처도 걸러진다.

이 내용 다시 읽기 →
분석과 판단운영
피처 100개에서 10개를 남기려 한다. RFE(step=1) 학습 90회 RFECV(step=1, cv=5) 학습 450회 이상 RFE(step=0.1) 학습 9회

step을 키워 얻는 것과 잃는 것은 무엇인가?

  1. 정답학습 횟수가 줄지만 한 번에 여러 개를 같이 버리므로 그중 남았어야 할 피처가 있는지 확인할 방법이 없다.

    매 단계 하나씩 떨어뜨리면 남은 피처들로 다시 학습해 중요도를 갱신하는데, 열 개를 한꺼번에 버리면 그 갱신 기회가 사라진다. 속도와 정밀도의 맞바꿈이다.

  2. 학습 횟수가 줄지만 남는 피처 수가 목표치보다 많아져 다시 조정해야 한다.

    구현이 목표 개수에 맞춰 마지막 단계를 조정하므로 남는 개수는 지정한 값이 된다.

  3. 학습 횟수는 그대로이고 각 학습의 피처 수만 줄어 메모리 사용량이 감소한다.

    학습 횟수 자체가 90회에서 9회로 준다. 표에 그 값이 그대로 나와 있다.

  4. 학습 횟수가 줄지만 교차 검증을 함께 쓸 수 없게 되어 최적 개수를 자동으로 찾지 못한다.

    교차 검증판에서도 이 파라미터를 그대로 쓸 수 있다. 두 기능이 배타적이지 않다.

해설

래퍼의 비용은 곱셈으로 늘어난다. 피처 100개에서 하나씩 떨어뜨리며 10개를 남기려면 90번을 학습해야 하고, 여기에 5겹 교차 검증을 걸면 폴드마다 그 과정을 되풀이하므로 450번을 넘어간다. 한 단계에 떨어뜨리는 개수를 비율로 주면 학습이 90번에서 9번으로 줄지만, 한 번에 열 개를 같이 버리니 그중 하나가 남았어야 할 피처였는지 확인할 방법이 없다. 매 단계 남은 피처들로 다시 학습해 중요도를 갱신하는 것이 이 방법의 핵심인데 그 기회를 건너뛰는 셈이다. 그래서 순서를 두는 편이 낫다. 값싼 방법으로 후보를 크게 줄여 놓은 뒤 비싼 방법을 그 좁아진 공간에만 쓰면 정밀도를 크게 잃지 않고 비용을 감당할 수 있다.

이 내용 다시 읽기 →
상황 적용판단
데이터 100건에 피처 20개인 문제에서 SequentialFeatureSelector를 돌려 검증 성능이 가장 좋은 8개 조합을 찾았다.

이 결과를 어떻게 다뤄야 하는가?

  1. 정답조합의 수가 표본 수보다 훨씬 많아 그중 하나는 반드시 잘 맞으므로 우연일 확률이 높다.

    검증 성능이 가장 좋았다는 사실 자체가 선택 과정에서 이미 소진된 정보다. 데이터가 적을수록 선택 기준을 단순하게 두거나 L1 정규화에 맡기는 편이 낫다.

  2. 교차 검증을 썼으므로 이미 우연이 배제되어 그대로 신뢰할 수 있다.

    교차 검증은 한 번의 평가를 안정시킬 뿐이다. 수많은 조합 중 최고를 고르는 과정에서 생기는 낙관은 그대로 남는다.

  3. 피처 20개는 적은 편이라 조합 탐색이 안전하고 데이터 수는 문제가 되지 않는다.

    가능한 조합의 수는 피처 수에 지수적으로 늘어난다. 20개면 이미 100만 가지가 넘는다.

  4. 전진 선택이 아니라 후진 제거로 바꾸면 이 문제가 해소된다.

    방향을 바꿔도 여러 조합을 검증 성능으로 견주는 구조는 같다. 같은 낙관이 그대로 생긴다.

해설

데이터가 적은데 조합을 많이 훑으면 이 조합이 검증 성능에서 가장 좋았다는 결론 자체가 우연일 확률이 높아진다. 조합의 수가 표본 수보다 훨씬 많으니 그중 하나는 반드시 잘 맞기 때문이다. 데이터가 적을수록 선택 기준을 단순하게 두거나 L1 정규화처럼 학습 안에서 처리되는 방법에 맡기는 편이 낫다. 반대로 피처 선택을 굳이 하지 않아도 되는 경우도 있다. 피처 대비 데이터가 압도적으로 많으면 차원의 저주가 문제 되지 않고, 트리 앙상블은 분기마다 후보를 일부만 쓰거나 정규화와 서브샘플링으로 어느 정도 면역이 있다. 딥러닝에서는 원시 입력에서 유용한 조합을 찾는 것이 학습의 일부라 사람이 미리 잘라내면 그 재료를 뺏는 셈이 된다.

이 내용 다시 읽기 →
분석과 판단함정
# 전체 데이터로 상호 정보량 상위 20개를 먼저 고른다
X_sel = SelectKBest(mutual_info_classif, k=20).fit_transform(X, y)

# 그다음 교차 검증
scores = cross_val_score(model, X_sel, y, cv=5)

이 코드의 문제는 무엇인가?

  1. 상호 정보량은 비선형 관계를 잡으므로 교차 검증과 함께 쓰면 추정이 불안정해진다.

    추정의 안정성 문제가 아니다. 어떤 점수 함수를 쓰든 전체 데이터에서 골랐다는 사실이 문제다.

  2. 정답선택이 타겟을 쓰므로 검증 폴드의 정답이 이미 피처 선택에 반영되어 점수가 부풀려진다.

    검증 폴드는 이미 정답을 흘린 데이터가 된다. 선택기를 파이프라인에 넣어 폴드마다 독립적으로 다시 수행되게 해야 한다.

  3. SelectKBest가 변환기라 교차 검증 안에서 다시 fit되지 않으므로 개수가 어긋난다.

    이 코드에서는 아예 교차 검증 바깥에서 한 번만 수행됐다. 개수는 20개로 고정되어 어긋나지 않는다.

  4. 상위 20개라는 개수를 사람이 정했으므로 교차 검증으로 개수를 찾는 방법으로 바꿔야 한다.

    개수를 어떻게 정하든 선택을 어디서 수행했느냐는 그대로 남는다. 순서 문제가 먼저다.

해설

전체 데이터로 피처를 고른 다음 교차 검증을 돌리면 점수가 부풀려진다. 검증 폴드의 타겟을 보고 피처를 골랐으니 그 폴드는 이미 정답을 흘린 데이터이고, 그 상승분은 성능이 아니라 누수다. 상호 정보량이나 상관계수처럼 타겟을 쓰는 필터도 예외가 아니다. 선택기를 파이프라인 안에 넣으면 폴드마다 선택이 독립적으로 다시 수행되어 이 문제가 사라진다. 여기서 부수적으로 얻는 것이 하나 더 있다. 폴드별로 뽑히는 피처가 조금씩 달라지는 것이 정상이고, 그 변동 자체가 선택이 얼마나 안정적인지를 알려주는 신호가 된다. 매번 완전히 다른 피처가 뽑힌다면 그 선택 기준을 그대로 믿기 어렵다는 뜻이다.

이 내용 다시 읽기 →

Target Encoding을 타겟 누수 없이 쓰는 법

원문 읽기 →
개념 확인함정
도시별 이탈 여부의 평균으로 인코딩했다. 범주 샘플 수 타겟 평균 서울 5,000 0.32 속초 3 1.00 태백 1 0.00

태백의 인코딩 값이 위험한 이유는 무엇인가?

  1. 샘플이 하나뿐이라 평균의 표준오차를 계산할 수 없어 신뢰구간을 붙이지 못하기 때문이다.

    통계량을 얼마나 믿을 수 있느냐의 문제 이전에, 그 값이 곧 정답이라는 점이 더 근본적이다.

  2. 정답그 값이 그 행의 정답 그 자체라 모델이 학습이 아니라 정답을 받아 적게 되기 때문이다.

    타겟으로 만든 피처를 그 타겟을 예측하는 데 다시 쓰는 구조다. 훈련 점수만 비정상적으로 높고 검증 점수는 평범한 모습으로 드러난다.

  3. 값이 0이라 모델이 이 범주를 결측으로 오인하기 때문이다.

    0은 유효한 값이고 결측으로 처리되지 않는다. 값 자체가 아니라 그 값의 출처가 문제다.

  4. 서울과 값 차이가 커서 스케일링 없이는 거리 계산이 왜곡되기 때문이다.

    인코딩 값은 모두 0과 1 사이라 스케일 차이가 크지 않다. 누수가 스케일 문제로 나타나지도 않는다.

해설

타겟 평균으로 만든 피처를 그 타겟을 예측하는 데 다시 쓰면 정답의 일부가 입력에 섞여 들어온다. 이것이 타겟 누수이고, 가장 알아보기 쉬운 형태가 희귀 범주다. 샘플이 하나뿐인 범주의 인코딩 값은 그 행의 타겟 그 자체이므로 모델은 어떤 관계를 학습한 것이 아니라 정답을 받아 적은 셈이 된다. 증상은 훈련 점수와 검증 점수의 괴리로 나타난다. 훈련 점수가 비정상적으로 높은데 검증 점수는 평범하거나 나쁘고, 겉보기에는 분산이 큰 모델의 전형적인 모습이지만 원인은 복잡한 모델이 아니라 정답을 품은 피처다. 이 방법을 쓸 때 다루어야 할 난이도는 인코딩 자체가 아니라 이 누수를 어떻게 관리하느냐에 있다.

이 내용 다시 읽기 →
상황 적용함정
전체 평균 0.32, 수축 강도 100으로 Smoothing을 적용했다. 범주 n 범주 평균 인코딩 값 대도시 5,000 0.32 0.320 중소도시 50 0.60 0.413 희귀도시 3 1.00 0.340

이것만으로 누수가 해결되지 않는 이유는 무엇인가?

  1. 수축 강도를 교차 검증으로 정하지 않으면 값이 임의로 정해지기 때문이다.

    값을 잘 고르는 것과 누수를 막는 것은 다른 문제다. 어떤 값을 골라도 자기 참조는 남는다.

  2. 희귀 범주의 값이 여전히 전체 평균보다 높게 나와 정보가 새기 때문이다.

    0.340과 0.32의 차이는 실제 데이터가 만든 몫이라 그 자체가 누수의 증거가 아니다. 문제는 그 몫에 자기 타겟이 들어 있다는 점이다.

  3. 가상의 샘플을 더하는 방식이라 전체 평균이 왜곡되어 다른 범주의 값까지 흔들리기 때문이다.

    전체 평균은 가상 샘플과 무관하게 원본 데이터에서 계산된다. 다른 범주가 흔들리지도 않는다.

  4. 정답각 샘플이 여전히 자기 타겟이 포함된 평균을 보고 있다는 사실은 그대로이기 때문이다.

    수축은 희귀 범주가 만드는 극단값을 눌러줄 뿐이다. 샘플이 셋인 범주라면 자기 타겟의 지분이 3분의 1이므로 여전히 정답이 새어 들어온다.

해설

Smoothing은 범주별 평균을 전체 평균 쪽으로 수축시키되 샘플이 적은 범주일수록 더 많이 끌어당긴다. 식을 읽으면 타겟이 전체 평균인 가상의 샘플을 미리 넣어둔 것과 같아서, 실제 샘플이 많으면 범주 평균이 그대로 살아남고 적으면 결과가 전체 평균 근처로 끌려간다. 하지만 이것은 극단값을 눌러주는 장치일 뿐 각 샘플이 자기 타겟이 포함된 평균을 보고 있다는 사실 자체는 그대로다. 샘플이 셋인 범주라면 자기 타겟의 지분이 3분의 1이나 된다. 그래서 자기 참조를 끊는 별도의 장치가 필요하고, 두 방법은 서로 다른 문제를 막으므로 함께 쓴다. 폴드로 나눠 자기 참조를 끊고, 수축으로 샘플이 적은 범주의 통계를 눌러주는 조합이 실전 기본값이다.

이 내용 다시 읽기 →
상황 적용구조
훈련 데이터를 5개 폴드로 나누고, 각 폴드의 샘플을 나머지 4개 폴드의 타겟 통계로만 인코딩한다. 테스트는 훈련 폴드 전체의 통계로 인코딩한다.

이 구조가 막는 것은 정확히 무엇인가?

  1. 정답인코딩 값과 그 행의 타겟 사이의 직접적인 연결이다.

    어떤 샘플도 자기 자신이 포함된 통계를 보지 않는다. 격자로 그리면 대각선이 비어 있다는 것이 전부다.

  2. 희귀 범주의 인코딩 값이 극단으로 치우치는 현상이다.

    폴드를 나눠도 샘플이 적은 범주의 통계는 여전히 불안정하다. 그쪽은 수축이 담당한다.

  3. 훈련과 테스트에서 서로 다른 범주 목록이 만들어지는 문제다.

    그 문제는 매핑에 실패한 범주를 전체 평균으로 채워 따로 처리한다. 폴드 구조가 해결하는 대상이 아니다.

  4. 폴드마다 인코딩 값이 달라져 모델이 불안정해지는 현상이다.

    폴드마다 값이 달라지는 것은 이 방법의 부작용이 아니라 동작 방식 자체다. 그것을 막는 것이 목적이 아니다.

해설

자기 자신이 들어 있지 않은 데이터로 인코딩하면 인코딩 값과 그 행의 타겟 사이의 직접적인 연결이 끊긴다. 격자로 그렸을 때 대각선이 비어 있다는 것이 이 방법의 전부다. 다만 어떤 범주가 특정 폴드에만 등장하면 나머지 폴드에 없어 매핑에 실패하므로 그 자리는 전체 평균으로 채운다. 그리고 이 구조는 희귀 범주의 통계가 불안정하다는 문제까지 해결하지는 못한다. 네 폴드에서 계산해도 샘플이 셋뿐인 범주는 여전히 셋 남짓으로 추정한 값이다. 그래서 수축과 함께 쓴다. 하나가 자기 참조를 끊고 다른 하나가 표본이 적은 범주의 통계를 눌러주는 역할 분담이다.

이 내용 다시 읽기 →
분석과 판단함정
Leave-One-Out 방식은 각 샘플을 자기 자신만 뺀 같은 범주 전체의 평균으로 인코딩한다. 폴드를 나눌 필요가 없어 구현이 간단하다.

이 방식이 실전에서 권장되지 않는 이유는 무엇인가?

  1. 매 샘플마다 통계를 다시 계산해야 해서 데이터가 크면 감당되지 않기 때문이다.

    범주별 합과 개수를 한 번 구해 두고 자기 값을 빼면 되므로 계산은 오히려 가볍다.

  2. 샘플이 하나뿐인 범주에서 분모가 0이 되어 계산이 정의되지 않기 때문이다.

    그 경우는 전체 평균으로 채워 처리한다. 구현으로 막을 수 있는 문제라 근본 이유가 아니다.

  3. 정답같은 범주 안에서 인코딩 값이 정확히 두 개만 나오고 그중 어느 쪽인지가 곧 타겟이 되기 때문이다.

    분자에서 빼는 값이 자기 타겟이므로 타겟이 1인 샘플의 값이 0인 샘플보다 항상 낮게 나온다. 트리는 그 두 값 사이에 분할점 하나만 놓으면 타겟을 그대로 읽어낸다.

  4. 폴드를 나누지 않아 검증 데이터의 통계가 훈련에 섞여 들어가기 때문이다.

    훈련 데이터 안에서만 계산하면 검증 데이터가 섞이지 않는다. 문제는 훈련 데이터 내부에서 생긴다.

해설

Leave-One-Out은 폴드 크기를 1까지 줄인 극단이라 자기 자신을 확실히 배제하는 것처럼 보인다. 그런데 분자에서 빼는 값이 자기 타겟이므로 같은 범주 안에서도 타겟이 1인 샘플의 인코딩 값이 0인 샘플보다 항상 낮게 나온다. 결과적으로 범주마다 정확히 두 개의 값만 존재하고 그중 어느 쪽인지가 곧 타겟이 된다. 트리 모델은 그 두 값 사이에 분할점 하나만 놓으면 타겟을 그대로 읽어낼 수 있다. 누수를 막으려고 도입한 방식이 다른 경로로 더 직접적인 누수를 만드는 셈이다. 자기 자신을 빼는 것과 자기 자신을 뺀 흔적을 남기지 않는 것은 다른 이야기이고, 폴드 단위로 끊는 쪽이 그 흔적을 남기지 않는다.

이 내용 다시 읽기 →
상황 적용운영
같은 데이터와 같은 모델로 AUC를 두 번 쟀다. 파이프라인 안에 인코더를 넣고 교차 검증 0.655 enc.fit(X, y).transform(X)로 미리 인코딩한 뒤 검증 0.77

0.77을 성능으로 보고하면 안 되는 이유는 무엇인가?

  1. 인코더를 미리 적합하면 범주 목록이 고정되어 검증 폴드의 새 범주가 누락되기 때문이다.

    누락된 범주는 전체 평균으로 채워지므로 점수를 올리지 않는다. 오히려 내리는 쪽으로 작용한다.

  2. 정답교차 적합이 fit_transform에서만 일어나므로 이쪽은 전체 타겟으로 만든 통계를 그대로 돌려주기 때문이다.

    검증 폴드의 타겟이 인코딩 값에 이미 녹아 있어 그 컬럼이 정답표 노릇을 한다. 배포하면 나오는 숫자는 0.655 쪽이다.

  3. 미리 인코딩하면 값이 실수형으로 고정되어 모델이 범주형으로 인식하지 못하기 때문이다.

    두 경우 모두 실수형 컬럼이 모델에 들어간다. 타입 차이가 없다.

  4. 두 번의 실행에서 폴드 분할이 달라져 비교 자체가 성립하지 않기 때문이다.

    같은 설정으로 나누면 분할은 같다. 차이는 분할이 아니라 인코딩 시점에서 나온다.

해설

sklearn의 타겟 인코더는 교차 적합을 fit_transform에서만 수행한다. 따로 적합한 뒤 변환하면 전체 타겟으로 만든 통계를 그대로 돌려주므로 자기 참조가 살아 있고, 그 결과를 교차 검증에 넣으면 검증 폴드의 타겟이 이미 인코딩 값에 녹아 있어 점수가 부풀려진다. 배포하면 실제로 나오는 숫자는 낮은 쪽인데 그 사실은 배포 전까지 드러나지 않는다. 스케일러가 테스트 통계를 보면 점수가 조금 부풀지만, 인코더가 테스트 타겟을 보면 그 컬럼이 정답표가 된다는 점에서 위험의 크기가 다르다. 그래서 인코더는 파이프라인 안에 넣어 폴드마다 검증 폴드를 뺀 데이터로 새로 적합되게 해야 한다. 어떤 방식을 고르든 마지막 점검은 하나다. 이 인코딩 값을 만들 때 그 행의 타겟이 들어갔는가.

이 내용 다시 읽기 →
분석과 판단구조
CatBoost는 데이터를 무작위로 섞은 뒤 각 샘플을 자기보다 앞에 놓인 샘플들의 타겟으로만 인코딩한다. 샘플 1 (서울, y=1) → p 샘플 2 (서울, y=0) → (1 + a·p) / (1 + a) 샘플 4 (서울, y=1) → (1 + a·p) / (2 + a)

이 방식이 폴드 기반 인코딩과 다른 점은 무엇인가?

  1. 타겟을 전혀 쓰지 않으므로 누수 위험이 원리적으로 사라진다.

    앞선 샘플들의 타겟을 그대로 쓴다. 자기 자신과 뒤쪽 샘플만 계산에서 빠질 뿐이다.

  2. 정답같은 범주 안에서도 앞에 몇 개가 쌓였느냐에 따라 값이 달라져 정규화 강도가 자연스럽게 조절된다.

    폴드 기반에서는 한 폴드 안의 같은 범주 샘플이 전부 동일한 값을 받는다. 여기서는 앞쪽 샘플이 사전 확률에 가깝고 뒤로 갈수록 범주 평균에 수렴한다.

  3. 수축 계수가 필요 없어 튜닝할 파라미터가 하나 줄어든다.

    식에 수축 계수가 그대로 들어 있다. 사전 확률과 함께 분자와 분모에 더해진다.

  4. 한 번의 순열만 쓰므로 결과가 결정적이어서 재현성이 보장된다.

    부스팅 과정에서 여러 개의 무작위 순열을 번갈아 쓴다. 한 순열에 의존하는 편향을 줄이기 위해서다.

해설

순서 기반 인코딩은 무작위 순열에서 자기보다 앞선 샘플들의 타겟만 참조하므로 자기 자신과 뒤쪽 샘플이 애초에 계산에 들어오지 않는다. 폴드 기반과 갈리는 지점은 같은 범주에 붙는 값의 다양성이다. 폴드 기반에서는 한 폴드 안의 같은 범주 샘플이 전부 동일한 값을 받지만, 여기서는 앞에 몇 개가 쌓였느냐에 따라 값이 조금씩 달라진다. 앞쪽 샘플은 사전 확률에 가깝고 뒤로 갈수록 범주 평균에 수렴하므로 정규화 강도가 자연스럽게 조절되는 효과가 생긴다. 한 번의 순열에 의존하는 편향을 줄이기 위해 부스팅 과정에서 여러 개의 무작위 순열을 번갈아 쓴다. 이 라이브러리를 쓴다면 인코딩을 직접 할 필요 없이 범주형 컬럼만 알려주면 된다.

이 내용 다시 읽기 →

결측치를 지울 것인가 채울 것인가

원문 읽기 →
개념 확인구조
소득 항목의 결측을 세 가지 상황으로 나눠 봤다. A) 입력자가 무작위로 한 칸씩 빠뜨렸다 B) 자영업자가 소득 항목을 자주 비운다 C) 소득이 높을수록 소득 항목을 비운다

MAR이라는 이름이 붙는 것은 어느 쪽이고 무슨 뜻인가?

  1. A다. 결측이 완전히 무작위로 흩어져 있다는 뜻이다.

    A는 결측 여부가 아무것과도 상관이 없는 경우라 MCAR이다. 남은 행이 전체의 축소판이므로 지워도 분포가 그대로다.

  2. C다. 결측 여부가 값 자체에 달려 있어 무작위성이 값 안에 숨어 있다는 뜻이다.

    C는 결측 여부가 가려진 값 자체에 달린 MNAR이다. 관측된 값만 보고는 안 보이는 값이 얼마나 컸는지 알 길이 없다.

  3. 정답B다. 무작위라는 뜻이 아니라 결측 패턴이 관측된 다른 변수로 설명된다는 뜻이다.

    직업이 결측 여부를 정하고 직업은 데이터에 남아 있으므로, 그 정보를 쓰는 대체법이 통한다. 이름이 오해를 부르는 대표적인 경우다.

  4. B와 C 둘 다다. 결측이 어떤 변수와든 관계가 있으면 MAR이다.

    관계가 있는 변수가 데이터에 있느냐 없느냐가 두 유형을 가른다. 가려진 값에 달려 있으면 다른 이름이 붙는다.

해설

결측 유형을 가르는 기준은 하나다. 결측 여부가 무엇에 달려 있는가. 아무것과도 무관하면 MCAR이고 남은 행이 전체의 축소판이라 지워도 분포가 그대로다. 관측된 다른 변수로 설명되면 MAR인데, 이름과 달리 무작위라는 뜻이 아니라 결측 패턴이 데이터 안의 변수로 설명된다는 뜻이다. 자영업자가 소득을 자주 비운다면 결측 여부는 직업이 결정하고 직업은 데이터에 남아 있으므로 그 정보를 쓰는 대체법이 통한다. 결측 여부가 가려진 값 자체에 달려 있으면 MNAR이다. 유형은 데이터가 알려주지 않는다는 점도 함께 기억할 만하다. 검정으로 MCAR 가설을 기각할 수는 있지만 나머지 둘의 구별은 안 보이는 값에 달려 있어 도메인 지식으로 판단해야 한다.

이 내용 다시 읽기 →
상황 적용판단
고액 연봉자일수록 소득 항목을 비운다는 사실을 수집 담당자에게 확인했다. 결측률은 25%다.

KNN이나 MICE 같은 정교한 대체법을 쓰면 이 편향이 해소되는가?

  1. 정답해소되지 않는다. 그 방법들은 관측된 값이 안 보이는 값을 대변한다는 가정 위에 서 있는데 여기서는 그 가정이 깨졌다.

    관측된 소득만 보고는 안 보이는 소득이 얼마나 높았는지 알 길이 없다. 이럴 때는 결측 지시 변수를 남겨 결측 여부 자체를 신호로 쓰는 편이 낫다.

  2. 해소된다. 다른 변수로 소득을 회귀하면 가려진 고소득 구간까지 외삽되기 때문이다.

    회귀는 관측된 구간에서 배운 관계를 쓴다. 고소득 구간이 통째로 빠져 있으면 그 관계 자체가 낮은 쪽으로 치우쳐 있다.

  3. 해소된다. 반복 대체를 여러 번 돌리면 대체값의 분포가 실제 분포에 수렴하기 때문이다.

    반복은 변수 사이의 상호 의존을 푸는 절차이지 빠진 구간을 복원하는 절차가 아니다. 여러 번 돌려도 같은 가정 위에 있다.

  4. 부분적으로 해소된다. 결측률이 30% 미만이면 편향의 크기가 무시할 수준으로 줄기 때문이다.

    편향의 크기를 정하는 것은 결측률이 아니라 결측이 값과 얼마나 강하게 엮여 있느냐다. 비율이 낮아도 한쪽 꼬리가 통째로 빠지면 왜곡이 크다.

해설

결측 여부가 가려진 값 자체에 달려 있으면 어떤 대체법을 써도 편향이 남는다. 평균 대체는 물론이고 이웃에서 값을 빌려오거나 모델로 예측하는 방법도 마찬가지인데, 그 방법들이 전부 관측된 값들이 안 보이는 값을 대변한다는 가정 위에 서 있기 때문이다. 고액 연봉자가 소득을 비우는 상황은 정확히 그 가정이 깨진 경우다. 이럴 때 현실적인 대응은 결측 지시 변수를 만들어 두는 것이다. 결측 여부가 가려진 값과 엮여 있다면 그 지시 변수 자체가 값의 대리 신호가 되고, 트리 모델은 이 컬럼을 분기 조건으로 바로 쓸 수 있다. 대체법의 정교함을 비교하기 전에 결측이 왜 생겼는지를 먼저 묻는 편이 나은 이유다.

이 내용 다시 읽기 →
상황 적용계산
관측값 5개의 표본분산이 s²였다. 여기에 평균값 3개를 채워 넣었다. s²(대체 후) = (n-1) / (n+m-1) × s²

분산은 원래의 몇 퍼센트가 되는가?

  1. 정답약 57%다. 채운 값들의 편차가 정확히 0이라 제곱합은 그대로이고 자유도만 늘어나기 때문이다.

    4를 7로 나눈 값이다. 표준오차와 신뢰구간이 실제보다 좁아지고 이 변수와 다른 변수의 상관도 함께 흐려진다.

  2. 약 63%다. 채운 값이 평균이라 제곱합에 평균의 분산만큼이 더해지기 때문이다.

    평균을 채우면 그 값의 편차가 0이라 제곱합에 아무것도 더해지지 않는다.

  3. 100%다. 평균으로 채우면 평균이 그대로이므로 분산도 보존된다.

    평균은 보존되지만 분산은 보존되지 않는다. 분모가 커지는 만큼 값이 줄어든다.

  4. 약 80%다. 채운 값의 비중만큼 선형으로 줄어들기 때문이다.

    비율은 자유도의 비로 정해진다. 채운 개수의 비중과 직접 대응하지 않는다.

해설

평균으로 채운 값들은 편차가 정확히 0이라 제곱합에 아무것도 더하지 않는다. 그런데 분모의 자유도는 채운 개수만큼 늘어나므로 분산이 그 비율만큼 줄어든다. 관측값 다섯에 평균 셋을 채우면 원래의 57%가 된다. 분산 축소는 감으로 하는 말이 아니라 이렇게 계산되는 양이고, 표준오차와 신뢰구간이 실제보다 좁아지며 이 변수와 다른 변수의 상관도 함께 흐려진다. 더 근본적인 문제는 불확실성이 지워진다는 점이다. 빈칸의 진짜 값은 여러 값일 수 있는 상태인데 단순 대체는 거기에 하나를 확정해 찍고, 모델은 그 값이 추정치라는 사실을 알 방법 없이 관측된 값과 똑같은 무게로 학습한다. 결측 비율이 낮고 결측이 완전히 무작위일 때만 이 왜곡을 무시할 만하다.

이 내용 다시 읽기 →
분석과 판단함정
imputer = IterativeImputer(estimator=BayesianRidge(), max_iter=10, random_state=42)
df_imputed = imputer.fit_transform(df[numeric_cols])

# 이후 회귀계수와 95% 신뢰구간을 보고할 예정

이 코드에서 무엇이 빠졌는가?

  1. 반복 횟수가 부족하다. 신뢰구간을 보고하려면 대체값이 완전히 수렴할 때까지 돌려야 한다.

    수렴은 대체값의 변화가 기준 아래로 떨어지면 자동으로 끝난다. 수렴 여부와 신뢰구간의 폭은 다른 이야기다.

  2. 정답기본 동작이 단일 대체라 대체값이 추정이었다는 사실이 표준오차에 반영되지 않는다.

    이 방법의 이름에 붙은 다중이라는 말과 달리 기본값은 완성된 데이터셋 하나를 돌려준다. 사후 표집을 켜고 시드를 바꿔가며 여러 번 돌린 뒤 결과를 합쳐야 한다.

  3. 추정기를 선형 모델로 두었으므로 비선형 관계가 반영되지 않아 신뢰구간이 왜곡된다.

    추정기 선택은 대체값의 정확도에 영향을 주지만, 여기서 빠진 것은 대체의 불확실성을 남기는 절차다.

  4. 실험적 기능을 켜는 import가 없어 애초에 실행되지 않는다.

    그 import가 필요한 것은 맞지만 코드 조각 위쪽에 있을 수 있다. 여기서 물어야 할 것은 실행 여부가 아니라 절차의 완결성이다.

해설

연쇄 방정식 기반 대체의 이름에는 다중이라는 말이 붙어 있지만 sklearn 구현의 기본 동작은 단일 대체다. 완성된 데이터셋 하나를 돌려주고, 이후 분석은 그 값을 관측값과 똑같이 취급한다. 대체값이 추정이었다는 사실이 표준오차에 반영되지 않아 신뢰구간이 실제보다 좁아진다. 진짜 다중 대체는 사후 표집을 켜고 시드를 바꿔가며 여러 번 돌려 서로 다른 완성 데이터셋을 만들고, 각각에 같은 분석을 돌린 뒤 결과를 합치는 것이다. 합칠 때 쓰는 규칙에서 분산은 두 몫으로 나뉜다. 각 데이터셋 안에서 나온 분산의 평균과, 여러 추정치가 서로 갈라진 정도다. 두 번째 몫이 단일 대체가 잃어버리는 부분이고, 대체값을 바꿨을 때 결론이 흔들린다면 그 흔들림이 결과의 불확실성에 포함되어야 한다는 뜻이다.

이 내용 다시 읽기 →
상황 적용설계
XGBoost로 학습할 데이터에 결측이 12% 있다. 습관대로 앞에 중앙값 대체를 붙일지 고민 중이다.

어떻게 하는 편이 나은가?

  1. 중앙값 대체를 붙인다. 결측이 있으면 분할 기준을 정할 수 없어 학습이 불안정해진다.

    이 모델은 결측이 있어도 학습이 진행된다. 오히려 결측을 어느 쪽으로 보낼지를 학습 대상으로 삼는다.

  2. 정답NaN을 그대로 두고 필요하면 결측 지시 변수만 얹는다.

    분기마다 결측 샘플을 왼쪽과 오른쪽으로 보낸 두 경우의 손실을 계산해 이득이 큰 쪽을 기본 방향으로 학습한다. 미리 채우면 모델이 스스로 찾을 방향을 사람이 임의로 덮어쓰는 꼴이 된다.

  3. 결측이 있는 행을 지운다. 트리 계열은 표본 수에 덜 민감하기 때문이다.

    12%를 버리는 것은 적지 않은 손실이고, 결측이 무작위가 아니면 남는 행이 한쪽으로 쏠린다.

  4. 결측을 아주 큰 값이나 작은 값으로 채워 트리가 따로 분리하게 만든다.

    결측 처리 규칙을 이미 학습하는 모델이라 이런 수동 인코딩이 필요하지 않다. 값의 범위만 왜곡시킨다.

해설

부스팅 트리 구현들은 분기를 만들 때 결측 샘플을 왼쪽으로 보낸 경우와 오른쪽으로 보낸 경우의 손실을 둘 다 계산하고 이득이 큰 쪽을 그 분기의 기본 방향으로 학습해 둔다. 결측 처리 규칙 자체가 학습 대상이 되는 셈이다. 이런 모델에서 미리 대체를 해 봐야 얻는 것이 없고, 오히려 모델이 스스로 찾을 분기 방향을 사람이 임의로 덮어쓰게 된다. NaN을 그대로 두고 결측 여부가 신호일 것 같으면 지시 변수만 얹는 편이 낫다. sklearn 쪽에서도 히스토그램 기반 부스팅 구현은 결측을 자체 처리하지만, 로지스틱 회귀나 거리 기반 모델 같은 나머지 추정기는 여전히 결측을 받지 못하므로 대체가 필요하다.

이 내용 다시 읽기 →
분석과 판단운영
numeric = Pipeline([
    ('scaler', StandardScaler()),
    ('imputer', KNNImputer(n_neighbors=5)),
])

스케일러를 대체기보다 앞에 둘 수 있는 이유와 그렇게 두는 이유는 무엇인가?

  1. 스케일러가 결측을 0으로 채워 통과시키므로 대체기가 그 자리를 다시 계산할 수 있다.

    0으로 채우지 않는다. 결측인 자리는 결측인 채로 남겨 다음 단계로 넘긴다.

  2. 대체기가 내부에서 스케일링을 다시 수행하므로 앞에 두든 뒤에 두든 결과가 같다.

    그런 단계는 없다. 넘겨받은 값을 그대로 거리 계산에 쓴다.

  3. 정답스케일러가 결측을 남긴 채 통과시키므로 가능하고, 이웃 선택이 틀어지지 않게 하려는 것이다.

    이 대체기는 나머지 컬럼으로 거리를 재어 이웃을 찾으므로 값의 범위가 큰 컬럼이 있으면 그 컬럼이 거리를 지배하고 이웃 선택 자체가 틀린다. 스케일러가 결측을 무시하고 통계를 구한 뒤 그 자리를 그대로 남기는 성질 덕에 이 순서가 가능해진다.

  4. 대체 후에 스케일링하면 채운 값 때문에 평균이 이동하므로 순서를 뒤집어야 한다.

    평균이 이동하는 것은 맞지만 그것이 주된 이유는 아니다. 더 큰 문제는 대체 자체가 잘못된 이웃을 보고 이루어진다는 점이다.

해설

이 대체기는 결측이 없는 나머지 피처로 거리를 재서 가장 가까운 K개 행을 찾고 그 행들의 값으로 빈칸을 채운다. 거리 기반이므로 값의 범위가 큰 컬럼이 있으면 그 컬럼이 거리를 통째로 지배하고 이웃 선택 자체가 틀어진다. 스케일러가 결측을 무시하고 통계를 구한 뒤 결측인 자리를 그대로 통과시키므로 스케일링을 먼저 걸고 대체를 나중에 놓는 순서가 가능하다. 함께 확인할 것이 두 가지 더 있다. 이 대체기는 수치형만 받으므로 범주형은 최빈값 대체 같은 별도 경로로 처리해야 하고, 이웃 수는 5에서 10 사이가 무난하다. 너무 작으면 이웃 한둘의 잡음을 그대로 베끼고 너무 크면 결국 전체 평균에 수렴한다. 비용도 만만치 않아 결측이 있는 행마다 나머지 전체와 거리를 재므로 행 수의 제곱에 비례해 늘어난다.

이 내용 다시 읽기 →

이 페이지는 문항 품질을 점검하는 용도로도 씁니다. 한 화면에서 전부 훑어야 중복된 문항, 쏠린 난이도, 소거법으로 풀리는 선택지가 보입니다.

Thank You for Visiting My Blog, Have a Good Day 😆