●●○상황 적용함정
XGBoost에서 max_depth=7로 좋은 결과를 얻어 LightGBM으로 옮기면서 같은 크기가 되도록 리프 수를 127로 맞췄다. 훈련 손실은 훨씬 낮아졌는데 검증 점수가 떨어진다.
무엇이 잘못됐는가?
리프 수와 깊이는 단위가 달라 서로 변환할 수 없으 므로 애초에 옮겨 적으면 안 된다.
변환 자체는 성립한다. 깊이 7의 균형 트리 리프가 최대 2⁷ = 128개라 크기의 대응은 맞고, 문제는 그 리프가 어떤 모양으로 소비되느냐다.
정답leaf-wise 트리는 같은 리프 수를 훨씬 깊은 형태로 쓰므로 제한이 사실상 풀린 것과 같아진다.
127개 리프를 균형 잡힌 깊이 7이 아니라 한쪽으로 파고든 훨씬 깊은 형태로 소비한다. 공식 튜닝 문서가 이 경우 127이 아니라 70에서 80 사이를 권하는 이유다.
LightGBM은 히스토그램 분할만 쓰므로 분할점 후보가 부족해 리프를 다 채우지 못한다.
히스토그램 구간은 수백 개라 리프 127개를 만드는 데 부족하지 않다. 분할점 정밀도와 무관한 문제다.
깊이 제한이 함께 걸리지 않아 트리가 무한히 자라 학습이 발산한 것이다.
리프 수 상한이 걸려 있으므로 무한히 자라지 않는다. 발산이 아니라 과적합이다.
해설
깊이 7의 균형 트리는 리프가 최대 2⁷ = 128개라, 그 근처로 리프 수를 잡으면 크기가 대응되는 것처럼 보인다. 하지만 leaf-wise 트리는 같은 리프 수를 훨씬 깊은 형태로 소비하므로, 그대로 옮기면 제한이 사실상 풀린 것과 다름없다. 공식 튜닝 문서는 깊이 7로 좋은 정확도가 나오는 데이터라면 리프 수를 127이 아니라 70에서 80 사이로 권한다. 순서도 정해져 있다. 리프 수를 먼저 정하고 깊이는 뒤에서 받쳐주는 안전장치로 둔다. 여기에 리프 최소 행 수까지 걸어 리프에 최소 몇 행은 남게 강제하는 것이 실질적인 제동 장치다. 두 라이브러리 사이에서 맞춰야 할 축은 깊이가 아니라 리프 수라고 기억하면 된다.
이 내용 다시 읽기 →●●○상황 적용구조
GOSS는 그래디언트 절댓값 상위 20%를 전부 남기고 나머지에서 전체 대비 10%를 무작위로 뽑는다. 그리고 무작위로 뽑힌 행의 그래디언트와 헤시안에 8을 곱한다.
배율 = (1 - 0.2) / 0.1 = 8
이 배율을 곱하는 이유는 무엇인가?
무작위로 뽑힌 행이 정보량이 적으므로 가중치를 올려 학습 기여를 맞추기 위해서다.
정보량이 적다는 이유로 가중치를 올리지 않는다. 보정의 목적은 기여를 키우는 것이 아니라 원래 비중을 되돌리는 것이다.
그래디언트가 작은 행에서 수치 언더플로가 나지 않도록 값을 키워두기 위해서다.
언더플로는 여기서 문제가 되지 않고, 배율도 데이터가 아니라 두 샘플링 비율로만 정해진다.
상위 20%가 중복 선택될 수 있으므로 그만큼 아래쪽의 무게를 늘려 균형을 맞추기 위해서다.
상위 구간은 중복 없이 전부 그대로 가져간다. 보정 대상은 솎아낸 아래쪽이다.
정답작은 그래디언트 쪽을 솎아낸 만큼 분포가 왜곡되므로 남은 표본으로 원래 비중을 복원하기 위해서다.
아래쪽 80%에서 전체의 10%만 남겼으므로 뽑힌 한 행이 여덟 행을 대표한다. 이 배율을 곱하지 않으면 분할 이득 계산이 큰 그래디언트 쪽으로 치우친다.
해설
GOSS는 그래디언트 절댓값이 큰 행은 지금 모델이 크게 틀리고 있어 정보량이 많고 작은 행은 이미 잘 맞추고 있어 얻을 것이 적다는 관찰에서 출발한다. 그래서 앞쪽은 전부 남기고 뒤쪽만 솎아내는데, 그냥 끝내면 작은 그래디언트 쪽의 비중이 줄어들어 분포가 왜곡된다. 아래쪽 80%에서 전체의 10%만 남겼으므로 뽑힌 한 행이 여덟 행을 대표하는 셈이고, 그 배율을 그래디언트와 헤시안에 곱해 원래 비중을 복원한다. 한 가지 짚어둘 것은 GOSS가 기본값이 아니라는 점이다. LightGBM의 행 샘플링 기본값은 평범한 배깅이고 GOSS는 직접 켜야 한다. 논문이 말한 큰 폭의 속도 향상도 GOSS 하나가 아니라 히스토그램 분할과 특성 묶기까지 합친 결과다.
이 내용 다시 읽기 →●●●분석과 판단함정
XGBoost 설정을 LightGBM으로 옮겼다.
XGBoost subsample=0.8, min_child_weight=20
LightGBM bagging_fraction=0.8, min_child_weight=20
실행은 되는데 두 모델의 동작이 전혀 다르다.
어긋난 곳은 어디인가?
정답행 샘플링은 빈도 파라미터를 함께 주지 않아 꺼져 있고, min_child_weight는 양쪽 모두 헤시안 합이라 리프에 20행을 요구하지 않는다.
LightGBM의 행 샘플링은 빈도를 1 이상으로 주지 않으면 아무 일도 하지 않는다. 그리고 이 이름은 양쪽 모두 헤시안 합이라 행 개수를 강제하려면 리프 최소 행 수 파라미터를 써야 한다.
행 샘플링 파라미터의 이름만 다를 뿐 동작은 같고, min_child_weight가 LightGBM에서는 행 개수를 뜻해 제약이 훨씬 세진다.
방향이 반대다. LightGBM에서 행 개수를 뜻하는 것은 별칭이 다른 파라미터이고, 이 이름은 헤시안 합의 별칭이다.
min_child_weight는 양쪽에서 같은 뜻이고 행 샘플링 비율의 기준이 트리 단위와 노드 단위로 달라 결과가 갈린 것이다.
행 샘플링은 양쪽 모두 트리 단위로 적용된다. 문제는 기준이 아니라 빈도 파라미터 없이는 적용 자체가 되지 않는다는 점이다.
두 라이브러리의 기본 성장 방식이 달라 같은 파라미터도 다른 효과를 내는 것이므로 파라미터 자체는 잘못 옮기지 않았다.
성장 방식 차이는 별개로 존재하지만 여기서는 파라미터가 실제로 잘못 옮겨졌다. 하나는 꺼져 있고 하나는 단위가 다르다.
해설
이름만 보고 옮기면 다른 모델이 만들어지는 자리가 두 군데 있다. 하나는 행 샘플링이다. LightGBM의 비율 파라미터는 빈도 파라미터를 함께 1 이상으로 주지 않으면 아무 일도 하지 않으므로, 비율만 적어두고 서브샘플링이 걸렸다고 생각하는 실수가 여기서 나온다. 다른 하나는 리프 제약이다. min_child_weight는 양쪽 모두 리프의 헤시안 합에 대한 하한이지 행 개수가 아니다. LightGBM에서 이 이름은 헤시안 합 파라미터의 별칭이고, 행 개수를 강제하는 것은 이름이 비슷한 다른 파라미터의 별칭이다. 기본값도 XGBoost의 min_child_weight는 헤시안 합 1, LightGBM의 min_data_in_leaf는 행 수 20으로 단위 자체가 다르다. 파라미터를 옮길 때는 이름이 아니라 그 값이 무엇의 단위인지부터 확인해야 한다.
이 내용 다시 읽기 →●●○상황 적용구조
원-핫 인코딩된 세 컬럼을 한 컬럼으로 묶은 결과다.
city_seoul 1, 0, 0, 1, 0
city_busan 0, 1, 0, 0, 1
city_daegu 0, 0, 1, 0, 0
city_bundled 1, 2, 3, 1, 2
이 변환이 성립하는 조건과 얻는 것은 무엇인가?
세 컬럼의 상관이 낮을 때 성립하고, 특성 사이의 중복을 제거해 과적합을 줄인다.
상관의 높낮이가 조건이 아니다. 필요한 것은 동시에 0이 아닌 값을 갖지 않는다는 배타성이고, 얻는 것도 정확도가 아니라 속도다.
세 컬럼의 카디널리티가 같을 때 성립하고, 범주형을 정수로 바꿔 트리가 직접 분할할 수 있게 한다.
카디널리티가 조건이 아니고, 범주형 직접 분할은 별개의 기능이다. 이 변환은 히스토그램을 만드는 비용을 줄인다.
정답세 컬럼이 서로 배타적일 때 성립하고, 정보를 잃지 않은 채 히스토그램 계산 비용을 줄인다.
한 행에서 많아야 하나만 0이 아니므로 값이 겹치지 않아 되돌릴 수 있다. 히스토그램을 만드는 비용은 특성 수에 비례하므로 희소한 고차원 데이터일수록 이득이 크다.
세 컬럼의 결측 패턴이 같을 때 성립하고, 결측치를 한 번만 처리하면 되도록 만든다.
결측 처리와 무관한 변환이다. 트리 계열은 결측이 갈 방향을 따로 학습하므로 묶는다고 처리가 줄지 않는다.
해설
특성 묶기는 동시에 0이 아닌 값을 갖는 일이 없는 특성들, 즉 서로 배타적인 특성들을 한 컬럼으로 합친다. 원-핫 인코딩 결과가 전형적인 대상인데, 한 행에서 많아야 하나만 0이 아니므로 값의 범위를 어긋나게 배치하면 정보를 하나도 잃지 않고 되돌릴 수 있다. 얻는 것은 속도다. 히스토그램을 만드는 비용은 특성 수에 비례하므로 희소한 고차원 데이터일수록 이득이 크다. 다만 애초에 원-핫으로 펼치지 않는 편이 낫다는 점도 함께 기억할 만하다. 습관적으로 펼치면 차원이 불어나고 트리는 한 번에 범주 하나만 떼어내는 분할밖에 할 수 없게 된다. 세 라이브러리 모두 범주형을 직접 다루는 경로가 있으니 그쪽을 먼저 시도하는 것이 순서다.
이 내용 다시 읽기 →●●●분석과 판단구조
XGBoost가 손실 함수를 2차까지 테일러 전개해 얻는 리프 최적값이다.
w* = -( Σ g ) / ( Σ h + λ )
g는 1차 도함수, h는 2차 도함수, λ는 정규화 계수다.
이 식에서 읽어야 할 것은 무엇인가?
2차 도함수를 쓰면 손실 함수의 종류에 무관해지므로 어떤 손실에도 같은 식이 그대로 쓰인다.
손실이 달라지면 1차와 2차 도함수의 값이 달라진다. 형태가 같을 뿐 계산되는 값은 손실마다 다르다.
정답정규화가 분모에 들어앉아 리프 값을 눌러주고, 헤시안까지 쓰므로 같은 트리 수에서 손실을 더 깎는다.
λ가 클수록 리프 값이 0 쪽으로 당겨져 예측이 보수적으로 바뀐다. 그래디언트만 쓰는 방식보다 뉴턴법에 가까워 수렴이 빠르다.
분모가 0이 될 수 있으므로 λ는 수치 안정성을 위해 넣은 값이고 정규화 효과는 부수적이다.
순서가 반대다. λ는 목적 함수에 명시적으로 들어 있는 정규화 항에서 나왔고, 분모를 지켜주는 것은 부수적인 효과다.
헤시안 합이 클수록 리프 값이 커지므로 샘플이 많은 리프가 더 큰 예측을 내놓는다.
헤시안 합은 분모에 있으므로 클수록 리프 값이 작아진다. 방향이 반대다.
해설
손실 함수를 현재 예측 주변에서 2차까지 전개하면 각 리프의 최적값과 분할 이득이 닫힌 형태로 나온다. 그 식의 분모에 정규화 계수가 들어앉아 있어 값이 클수록 리프 값이 0 쪽으로 당겨지고 예측이 보수적으로 바뀐다. 정규화가 학습이 끝난 뒤의 가지치기가 아니라 분할 여부를 판단하는 그 순간에 비용으로 반영된다는 뜻이다. 목적 함수에는 리프 하나를 늘릴 때마다 물리는 비용도 함께 들어 있어 분할 자체를 억제한다. 헤시안까지 쓰는 것은 수렴 쪽의 이득이다. 그래디언트만 쓰는 방식보다 뉴턴법에 가까워져 같은 트리 수에서 손실을 더 깎는다. 정규화를 목적 함수에 박아 넣고 깊이로 트리를 통제하는 이 보수적인 설계가 XGBoost의 성격을 만든다.
이 내용 다시 읽기 →