Posts [Encoding] 1. One Hot Encoding
Post
Cancel

[Encoding] 1. One Hot Encoding


데이터를 처리하다보면 범주형 자료를 수치형 자료로 바꾸어야할 필요성이 많습니다. 이러한 변환을 인코딩이라고 하는데, 다양한 목적과 자료의 특징에 맞추어 올바르게 인코딩한 범주형 자료는 모델의 퍼포먼스와 효율에 상당한 영향을 끼칩니다. 특히 최근 각광받는 머신러닝과 딥러닝에서 범주형 자료에 대한 인코딩은 필수적입니다. 그러나 인코딩은 생각만큼 단순하지 않습니다. One-Hot-Encoding/ Ordinal-Encoding/ Label Encoding/ Target Encoding… 등 종류도 다양할 뿐더러, 비슷한 인코딩도 library에 따라 크고작은 차이가 있습니다. 인코딩 특집 글에서는 여러 인코딩 기법 중 자주 쓰이고, 중요한 방식들에 대해서 포스팅하겠습니다.


One Hot Encoding

One Hot Encoding은 범주형 자료를 0과 1로 이루어진 벡터로 바꾸는 가장 기본적인 방식입니다. scikit-learn에서는 이를 위해 sklearn.preprocessing.OneHotEncoder를 제공합니다. 유사한 기능을 python의 내장함수, get_dummies도 제공하기 때문에 더미화라고도 많이 부릅니다. 두 방식은 매우 흡사하지만 쓰임에 따라 장단점이 명확합니다. 오늘 글에서는 범주형 자료를 수치형으로 변환하는 두 방식의 차이와 더불어, 실제 자료에 이를 적용해보는 것으로 이번 글을 마치겠습니다.

nominal VS ordinal

들어가기에 앞서, 저는 앞에서 범주형 자료(categorical data)에 대한 구체적인 정의를 하지 않았습니다. 그러나 데이터는 범주형과 수치형으로만 구분하지 않습니다. 여러 기준이 존재하며 각 성질에 따라 적용해야하는 인코딩 기법 또한 달라지게 됩니다. 특히 오늘 다룰 one hot encoding은 전적으로 nominal data에 관한 인코딩 기법입니다. 따라서 같은 범주형 자료일지라도 순서가 존재하는 ordinal 데이터와 구분할 수 있어야 합니다. 가장 대표적인 예시가 사회학에서 응답자의 상태를 하나의 축 위의 4개 또는 5개 점으로 찍어서 표현하는 리커트 척도(Likert scale)가 있습니다. 실제로 scikit-learn에서는 이러한 ordinal data를 위해서 sklearn.preprocessing.OrdinalEncoder를 제공합니다. 그러나 모든 ordinal data가 OrdinalEncoder를 쓰기 적절한 것은 아닙니다.

조금 더 구체적으로 살펴보겠습니다. 같은 ordinal data라도 아래와 같은 두 가지 경우가 있습니다.

  1. 범주 간 간격이 ‘비교적’ 일정하다.
  2. 그렇지 못한 경우.

만약 ordianl data의 범주 사이 간격이 일정하거나 그 차이를 어느 정도 알 수 있다면, 우리는 OrdinalEncoder을 사용할 수 있습니다. 다음 포스팅에 더 자세히 다루겠지만, 그 상하 순서를 살려서 숫자로 변환하는 것을 의미합니다. 그러나 그 간격을 알지 못하거나 확신할 수 없는 경우가 많습니다. 예를 들어 low/ middle/ high는 0, 1, 2도 해당하지만, 0, 1, 100도 해당합니다. 만약 실제 응답자의 middle과 high 간격이 매우 컸다면, OrdinalEncoder을 사용한 모델의 성능은 좋지 않을 수 밖에 없습니다. 이 때에는 아무리 순서 상의 상하 관계가 존재한다고 하여도 단순한 nominal한 범주형 자료로 봐주고 OneHotEncoderget_dummies 인코딩을 하는 편이 안전합니다. 물론 이렇게 하면 데이터가 담고 있는 관계성을 잃어버릴지라도 말입니다.

나머지 데이터의 종류에 관해서는 읽을 거리로 대체하겠습니다. interval, ratio 등의 분류는 매우 중요하니 꼭 읽어보실 것을 추천드립니다.


get_dummies()

get_dummies()는 pandas의 내장함수이니만큼 pandas의 Series나 DataFrame 등에서 사용하기 편리합니다. 특히 OneHotEncoder가 instance라는 개념을 사용하기 때문에 처음 사용하기 어려운 반면, get_dummies()는 즉각적으로 값을 변환하여 주기 때문에 직관적으로 다가옵니다.

WealthbldQual
Jai27AMsc
Princi27BMA
Salah22AMA
Anuj32OMsc

방법은 매우 간단합니다. pandas.get_dummies() 함수 안에 본인이 변환하고자 dataframe을 넣어주면 됩니다.

1
2
dummy = pd.get_dummies(data)
dummy
Wealthbld_Abld_Bbld_OQual_MAQual_Msc
Jai2710001
Princi2701010
Salah2210010
Anuj3200101

보다시피, Wealth라는 수치형 변수는 그대로 나오지만 범주형 변수에 해당하는 다른 column들은 모두 범주 갯수에 해당하는 자릿수만큼 추가적인 column이 생기고, 각 해당되는 값에 1이 찍힘을 알 수 있습니다. 주의해야할 것은 이는 pandas가 신출귀몰한 재주로 주어진 자료에서 범주형 데이터를 골라낼 수 있기 때문이 아니라 isinstance()라는 함수를 활용해서 string일 때에 모두 더미화를 시키기 때문입니다.1 따라서 주어진 수치형 자료가 string 형태로 들어가있지 않도록 주의해야합니다. 더불어 missing_value가 있는 경우에도 get_dummies()는 문제가 생길 수 있습니다. 그러나 가장 큰 단점은 새로운 데이터셋에서 작업을 할 때마다 모든 차원과 ordering이 바뀐다는 점입니다. 이에 대한 자세한 내용은 깃헙에 올려놨습니다.

sklearn.preprocessing.OneHotEncoder

1
2
3
4
from sklearn.preprocessing import OneHotEncoder
UserName = OneHotEncoder()
UserName.fit_transform(UserInput)
...

sklearn에서 제공하는 OneHotEncoder는 위에서 설명한 get_dummies()에 비해 복잡하게 느껴지는 것은 사실입니다. 특히 익숙한 dataframe에 즉각적으로 표현이 잘 되지 않을 뿐 아니라 비교적 생소한 sparse matrix나 array 형태로 값을 반환하기 때문입니다. 그럼에도 불구하고 저는 OneHotEncoder를 적극 추천하는 바입니다. 다양한 옵션들을 활용하면 sklearn의 단점으로 여겨지는 것들을 쉽게 해결할 수 있을 뿐아니라, 오히려 강력한 장점 때문에 쓰지 않을 수가 없기 때문입니다.

array

array는 메모리 사용량과 처리 속도에 있어서 뛰어난 강점을 가집니다. 실제로 연산량이 많은 머신러닝에서는 거의 모든 모델링이 array 형태로 데이터를 입력받을 뿐 아니라, 효율적인 코드를 짜기위해서도 list나 DataFrame보다 array로 형태를 바꿔서 많이 작업합니다.

sklearn.preprocessing.OneHotEncoder의 첫번째 장점은 바로 array형태로 작업을 한다는 것입니다. 물론 위에서 소개한대로 모든 하이퍼파라미터를 default로 한다면 결과값이 array로 나오지 않는 것처럼 보입니다. 대신에 sparse matrix로 결과값이 등장을 하는데, 이 때에는 다음의 두 가지 방법으로 해결이 가능합니다.

toarray()
sparse = False

첫번째 방식은 최종적으로 나온 결과값에 .toarray()를 추가해서 사후적으로 sparse matrix를 array꼴로 바꿔줍니다. 훨씬 더 간단한 방법은 두번째입니다. 이는 OneHotEncoder의 기본 옵션 중에 하나로 결과값을 항상 array로 보여줍니다. 따라서 아래와 같이 코딩을 한다면 수월할 것입니다.

1
UserName = OneHotEncoder(sparse = False)

보다 자세한 예시는 제 깃헙에 올려놨습니다.

handling unknown categorical features

OneHotEncoder의 가장 뛰어난 장점은 새로운 데이터셋에 쉽게 적용할 수 있다는 사실입니다. 위에서 get_dummies에서는 이것이 불가능하다고 언급했었습니다. 그러나 OneHotEncoder가 가지고 있는 handle_unknown 기능은 이를 가능케합니다. 따라서 이를 사용하면 input 데이터가 일정해야하는 많은 딥러닝 모델에서 손쉽게 사용할 수 있습니다. 이는 instance에 자신이 더미화한 명목형 변수의 값과 순서를 기억하기 때문에 새로운 데이터셋을 만나도 유연하게 대처할 수 있기 때문입니다. 구체적인 방식은 아래와 같습니다.

  1. handle_unknown = ‘error’
  2. handle_unknown = ‘ignore’

첫 번째 방식은 새로운 변수값을 마주할 때 error를 반환합니다. 이 방식은 아예 array를 생성하지 못하기 때문에 결국 문제를 해결하지 못합니다. 반면 두 번째 방식은 마주하는 새로운 명목형 변수들을 모두 0으로 처리하게끔 만듭니다. (이 경우에 inverse_tranform 방법을 쓰면 0으로 이루어진 새로운 값들이 모두 None으로 반환되는 것을 확인할 수 있습니다.) 자세한 사용법은 저의 깃헙에서 참고하시길 바랍니다.

다중공선성 문제

마지막으로 다중공선성 문제를 짚고 넘어가려합니다. 더미변수를 만들어 준 뒤에 다중공선성 문제는 주로 회귀(regression) 상황에서 중요한 이슈입니다. 물론 머신러닝에서도 일정정도 걸림돌이 될 수는 있으나 아직 자세한 연구는 없는 것으로 알고 있습니다.2 따라서 이 부분은 주로 선형회귀와 같은 상황에서 다루어질 내용입니다.

다중공선성을 해결하는 가장 쉬운 방법은 새로 생겨난 n개의 벡터를 한 차원 낮춰주는 것입니다. get_dummies()OneHotEncoder 모두 이를 위한 간단한 코딩방법이 있습니다. 사소하지만 두 가지 사용법은 아래와 같은 차이점이 있으니 유의할 필요가 있습니다.

get_dummies() : drop_first = True
OneHotEncoder() : drop = "First"


두 가지 방식 모두 많이 쓰입니다만, 각각의 경우 장단점을 잘 알고 활용하는 것이 중요합니다. 특히 train 데이터셋에서 새로운 변수를 처리할 때 대다수 sklearn 방식을 많이 사용합니다. neural network를 사용할 때처럼 data 갯수가 클 경우, arrray로 처리하여 속도를 높일 뿐 아니라 train과 test 데이터를 merge할 필요도 없기 때문에 더욱 그렇습니다. 구체적인 사례는 저의 깃헙에서 확인하실 수 있습니다.

이것으로 첫 번째 Encoding편을 마치겠습니다.



각주 및 참고문헌

  1. https://docs.python.org/3/library/functions.html#isinstance 

  2. tree-based 모델이 아닌 이상 모든 모델에서 더미화를 한 범주형 자료는 반드시 n-1을 해야한다고 들었으나 이 부분 역시 확실하지 않습니다. 나중에 논문을 참고하여 내용을 보충할 예정입니다. 

HTML on Github pages

[Encoding] 2. Ordinal/ Label Encoding

Comments powered by Disqus.