피처 추출, 변환 및 선택
피처 추출, 변환 및 선택 (Extracting, transforming and selecting features)
피처(feature)는 머신러닝 모델에 들어가는 데이터의 입력 단위예요. 이 페이지에서는 원시 데이터에서 피처를 뽑아내는 추출(Extraction), 피처를 스케일링·변환·수정하는 변환(Transformation), 많은 피처 중 일부를 고르는 선택(Selection), 그리고 이와 결합된 Locality Sensitive Hashing (LSH) 알고리즘까지, Spark ML에서 피처를 다루는 전 과정을 예제 코드와 함께 설명해 드릴게요.
출처: 문서
본문
이 섹션은 피처를 다루기 위한 알고리즘을 다루며, 대략 다음 그룹으로 나뉩니다:
- 추출 (Extraction): "원시" 데이터에서 피처를 추출
- 변환 (Transformation): 피처를 스케일링, 변환 또는 수정
- 선택 (Selection): 더 큰 피처 집합에서 일부를 선택
- Locality Sensitive Hashing (LSH): 피처 변환의 측면을 다른 알고리즘과 결합하는 알고리즘 클래스
목차 (Table of Contents)
- 피처 추출기 (Feature Extractors)
- 피처 변환기 (Feature Transformers)
- Tokenizer
- StopWordsRemover
- $n$-gram
- Binarizer
- PCA
- PolynomialExpansion
- Discrete Cosine Transform (DCT)
- StringIndexer
- IndexToString
- OneHotEncoder
- TargetEncoder
- VectorIndexer
- Interaction
- Normalizer
- StandardScaler
- RobustScaler
- MinMaxScaler
- MaxAbsScaler
- Bucketizer
- ElementwiseProduct
- SQLTransformer
- VectorAssembler
- VectorSizeHint
- QuantileDiscretizer
- Imputer
- 피처 선택기 (Feature Selectors)
- Locality Sensitive Hashing
피처 추출기 (Feature Extractors)
TF-IDF
용어 빈도-역문서 빈도 (TF-IDF)는 텍스트 마이닝에서 문헌집(corpus)의 문서에 어떤 용어가 얼마나 중요한지를 반영하기 위해 널리 쓰이는 피처 벡터화 방법입니다. 용어를 $t$, 문서를 $d$, 문헌집을 $D$로 표기할게요. 용어 빈도 $TF(t, d)$는 용어 $t$가 문서 $d$에 나타난 횟수이고, 문서 빈도 $DF(t, D)$는 용어 $t$를 포함하는 문서의 수예요. 용어 빈도만으로 중요도를 측정하면 아주 자주 나타나지만 문서에 대한 정보를 거의 담지 못하는 용어("a", "the", "of" 등)를 지나치게 강조하기 쉽습니다. 용어가 문헌집 전체에 아주 자주 나타난다면 그 용어는 특정 문서에 대한 특별한 정보를 담지 않는다는 뜻이에요.
역문서 빈도는 용어가 제공하는 정보량을 측정하는 수치입니다:
\[ IDF(t, D) = \log \frac{|D| + 1}{DF(t, D) + 1}, \]
여기서 $|D|$는 문헌집의 총 문서 수예요. 로그를 사용하므로 용어가 모든 문서에 나타나면 IDF 값이 0이 됩니다. 문헌집 밖의 용어에서 0으로 나누는 것을 피하기 위해 평활(smoothing) 항이 적용된다는 점을 참고하세요. TF-IDF 측정값은 단순히 TF와 IDF의 곱이에요:
\[ TFIDF(t, d, D) = TF(t, d) \cdot IDF(t, D). \]
용어 빈도와 문서 빈도의 정의에는 여러 변형이 있습니다. MLlib에서는 유연하게 만들기 위해 TF와 IDF를 분리해요.
TF: HashingTF와 CountVectorizer 둘 다 용어 빈도 벡터를 생성하는 데 사용할 수 있어요.
HashingTF는 용어 집합을 가져와 이 집합을 고정 길이 피처 벡터로 변환하는 Transformer입니다. 텍스트 처리에서 "용어 집합"은 단어들의 모음(bag of words)일 수 있어요. HashingTF는 해싱 트릭(hashing trick)을 이용합니다. 원시 피처를 해시 함수를 적용해 인덱스(용어)로 매핑해요. 여기 사용된 해시 함수는 MurmurHash 3입니다. 그런 다음 매핑된 인덱스를 기반으로 용어 빈도를 계산해요. 이 방식은 대규모 문헌집에서 비쌀 수 있는 전역 용어-인덱스 맵 계산을 피하지만, 해시 후 서로 다른 원시 피처가 같은 용어가 될 수 있는 해시 충돌 가능성이 있어요. 충돌 확률을 줄이려면 대상 피처 차원, 즉 해시 테이블의 버킷 수를 늘릴 수 있습니다. 해시된 값에 단순 모듈로를 사용해 벡터 인덱스를 결정하므로, 피처 차원은 2의 거듭제곱을 사용하는 것이 좋아요. 그렇지 않으면 피처가 벡터 인덱스에 고르게 매핑되지 않습니다. 기본 피처 차원은 $2^{18} = 262,144$이에요. 선택적 이진 토글 파라미터가 용어 빈도 수를 제어합니다. true로 설정하면 모든 0이 아닌 빈도 수가 1로 설정돼요. 이는 정수 개수가 아닌 이진 개수를 모델링하는 이산 확률 모델에 특히 유용합니다.
CountVectorizer는 텍스트 문서를 용어 개수 벡터로 변환해요. 자세한 내용은 CountVectorizer를 참고하세요.
IDF: IDF는 데이터셋에 피팅되어 IDFModel을 생성하는 Estimator입니다. IDFModel은 피처 벡터(보통 HashingTF나 CountVectorizer로 생성)를 받아 각 피처를 스케일링해요. 직관적으로 문헌집에 자주 나타나는 피처의 가중치를 낮춰줍니다.
참고: spark.ml은 텍스트 분할(segmentation) 도구를 제공하지 않아요. 사용자는 Stanford NLP Group과 scalanlp/chalk을 참고하세요.
예제 (Examples) — Tokenizer로 각 문장을 단어로 나눈 뒤 HashingTF로 해시하고 IDF로 재스케일링해, 텍스트로 학습하는 일반적인 흐름을 보여줍니다.
from pyspark.ml.feature import HashingTF, IDF, Tokenizer
sentenceData = spark.createDataFrame([
(0.0, "Hi I heard about Spark"),
(0.0, "I wish Java could use case classes"),
(1.0, "Logistic regression models are neat")
], ["label", "sentence"])
tokenizer = Tokenizer(inputCol="sentence", outputCol="words")
wordsData = tokenizer.transform(sentenceData)
hashingTF = HashingTF(inputCol="words", outputCol="rawFeatures", numFeatures=20)
featurizedData = hashingTF.transform(wordsData)
# alternatively, CountVectorizer can also be used to get term frequency vectors
idf = IDF(inputCol="rawFeatures", outputCol="features")
idfModel = idf.fit(featurizedData)
rescaledData = idfModel.transform(featurizedData)
rescaledData.select("label", "features").show()
Word2Vec
Word2Vec은 문서를 나타내는 단어 시퀀스를 받아 Word2VecModel을 훈련하는 Estimator입니다. 이 모델은 각 단어를 고유한 고정 크기 벡터로 매핑해요. Word2VecModel은 문서의 모든 단어의 평균을 사용해 각 문서를 벡터로 변환하며, 이 벡터는 예측, 문서 유사도 계산 등의 피처로 사용될 수 있습니다. 자세한 내용은 MLlib 사용자 가이드의 Word2Vec을 참고하세요.
예제 — 각각 단어 시퀀스로 표현된 문서를 피처 벡터로 변환합니다.
from pyspark.ml.feature import Word2Vec
documentDF = spark.createDataFrame([
("Hi I heard about Spark".split(" "), ),
("I wish Java could use case classes".split(" "), ),
("Logistic regression models are neat".split(" "), )
], ["text"])
word2Vec = Word2Vec(vectorSize=3, minCount=0, inputCol="text", outputCol="result")
model = word2Vec.fit(documentDF)
result = model.transform(documentDF)
for row in result.collect():
text, vector = row
print("Text: [%s] => \nVector: %s\n" % (", ".join(text), str(vector)))
CountVectorizer
CountVectorizer와 CountVectorizerModel은 텍스트 문서 컬렉션을 토큰 개수 벡터로 변환하는 데 도움을 줘요. 사전에 정의된 사전(dictionary)이 없을 때 CountVectorizer는 Estimator로 사용되어 어휘(vocabulary)를 추출하고 CountVectorizerModel을 생성합니다. 모델은 어휘에 대한 문서의 희소 표현을 만들며, 이는 LDA 같은 다른 알고리즘에 전달될 수 있어요.
피팅 과정에서 CountVectorizer는 문헌집 전체의 용어 빈도로 정렬된 상위 vocabSize 단어를 선택합니다. 선택적 파라미터 minDF는 용어가 어휘에 포함되기 위해 나타나야 하는 최소 문서 수(또는 값이 1.0 미만이면 비율)를 지정해 피팅 과정에 영향을 줘요. 또 다른 선택적 이진 토글 파라미터가 출력 벡터를 제어합니다. true로 설정하면 모든 0이 아닌 개수가 1로 설정됩니다. 정수 개수가 아닌 이진 개수를 모델링하는 이산 확률 모델에 특히 유용해요.
예제 — id와 texts 열을 가진 DataFrame을 가정합니다. 각 행은 Array[String] 문서입니다. CountVectorizer의 fit을 호출하면 어휘 (a, b, c)를 가진 CountVectorizerModel이 생성되고, 변환 후 출력 열 "vector"는 각 문서의 어휘에 대한 토큰 개수를 담습니다:
id | texts | vector
----|---------------------------------|---------------
0 | Array("a", "b", "c") | (3,[0,1,2],[1.0,1.0,1.0])
1 | Array("a", "b", "b", "c", "a") | (3,[0,1,2],[2.0,2.0,1.0])
FeatureHasher
피처 해싱(Feature hashing)은 범주형 또는 수치형 피처 집합을 지정된 차원의 피처 벡터로 투영해요(보통 원래 피처 공간보다 훨씬 작음). 해싱 트릭을 사용해 피처를 피처 벡터의 인덱스로 매핑하는 방식입니다. FeatureHasher 변환기는 여러 열에서 동작하며, 각 열은 수치형 또는 범주형 피처를 담을 수 있어요.
- 수치 열: 열 이름의 해시 값을 사용해 피처 값을 벡터 인덱스로 매핑. 기본적으로 수치 피처는 범주형으로 취급되지 않음. 범주형으로 다루려면
categoricalCols파라미터로 지정. - 문자열 열: "column_name=value" 문자열의 해시 값으로 매핑하며 표시 값은
1.0. 즉 범주형 피처는 one-hot 인코딩됨(OneHotEncoder +dropLast=false와 유사). - 불리언 열: 문자열 열과 같은 방식. "column_name=true"/"column_name=false"로 표현되며 표시 값은
1.0.
Null(결측) 값은 무시됩니다(결과 벡터에서 암시적으로 0). 여기 사용된 해시 함수는 HashingTF와 같은 MurmurHash 3입니다. numFeatures는 2의 거듭제곱을 권장합니다.
피처 변환기 (Feature Transformers)
Tokenizer
토큰화(Tokenization)는 텍스트(예: 문장)를 개별 용어(보통 단어)로 쪼개는 과정이에요. 간단한 Tokenizer 클래스가 이 기능을 제공합니다. RegexTokenizer는 정규 표현식 매칭 기반의 더 고급 토큰화를 허용합니다. 기본적으로 "pattern"(regex, 기본값: "\\s+") 파라미터가 입력 텍스트를 나누는 구분자로 사용됩니다. 또는 "gaps"를 false로 설정해 regex "pattern"이 "토큰"을 나타내고 모든 일치 항목을 토큰화 결과로 찾게 할 수 있어요.
StopWordsRemover
StopWordsRemover는 "the", "a" 같은 불용어(stop words)를 필터링하는 피처 변환기입니다. String 시퀀스 열(예: Tokenizer 출력)을 입력으로 받아, stopWords에서 제외한 모든 단어를 포함하는 String 시퀀스 열을 출력합니다.
$n$-gram
n-gram은 어떤 텍스트(예: 문장)에서 n개의 연속 항목(보통 단어)으로 이루어진 시퀀스예요. 2-gram(또는 bigram)은 두 단어의 n-gram입니다. NGram 변환기는 문자열 입력(예: Tokenizer 출력)을 n-gram 시퀀스로 변환합니다.
Binarizer
이진화(Binarization)는 수치 피처를 임계값으로 이진(0/1) 피처로 변환하는 과정이에요. Binarizer는 inputCol과 outputCol, 그리고 이진화를 위한 threshold를 가져요. 임계값보다 큰 값은 1.0, 같거나 작은 값은 0.0으로 이진화됩니다. inputCol에는 Vector와 Double 타입이 모두 지원됩니다.
PCA
PCA는 직교 변환을 사용해 상관될 수 있는 변수들의 관측 집합을 선형 비상관 변수인 주성분(principal components) 값으로 변환하는 통계 절차입니다. PCA 클래스는 PCA로 벡터를 저차원 공간에 투영하는 모델을 훈련합니다. 5차원 피처 벡터를 3차원 주성분으로 투영하는 방법을 예로 들 수 있어요.
PolynomialExpansion
PolynomialExpansion은 피처를 다항식 공간으로 확장하는 변환기입니다. 원래 피처의 순서 있는 곱(interaction)과 해당 곱을 포함한 벡터를 생성합니다.
Discrete Cosine Transform (DCT)
이산 코사인 변환(DCT)은 실수 값 시퀀스(time-domain)를 주파수 도메인(frequency-domain)으로 변환하는 변환기입니다. DCT 클래스는 벡터를 실수 값 시퀀스로 취급하고 DCT를 적용해 결과를 반환합니다. inverse 파라미터로 DCT 유형을 제어할 수 있어요.
StringIndexer
StringIndexer는 문자열 레이블 열을 레이블 인덱스 열로 인코딩합니다. 여러 열을 인코딩할 수 있으며, 인덱스는 [0, numLabels) 범위이고 네 가지 정렬 옵션이 지원됩니다:
- "frequencyDesc": 레이블 빈도 내림차순(가장 빈번한 레이블이 0)
- "frequencyAsc": 레이블 빈도 오름차순(가장 덜 빈번한 레이블이 0)
- "alphabetDesc": 알파벳 내림차순
- "alphabetAsc": 알파벳 오름차순 (기본값 = "frequencyDesc")
"frequencyDesc"/"frequencyAsc"에서 빈도가 같으면 문자열을 알파벳 순으로 더 정렬합니다. 유지하기로 선택하면 보이지 않는 레이블은 인덱스 numLabels에 놓입니다. 입력 열이 수치형이면 문자열로 캐스팅해 인덱싱합니다. 하위 파이프라인 구성 요소가 이 문자열 인덱스 레이블을 사용할 때는 해당 구성 요소의 입력 열을 이 문자열 인덱스 열 이름으로 설정해야 합니다.
IndexToString
StringIndexer와 대칭적으로, IndexToString은 레이블 인덱스 열을 원래 레이블이 문자열로 있는 열에 다시 매핑합니다. 일반적인 사용 사례는 StringIndexer로 레이블에서 인덱스를 생성하고 그 인덱스로 모델을 훈련한 다음, IndexToString으로 예측된 인덱스 열에서 원래 레이블을 검색하는 것입니다. 직접 자체 레이블을 제공해도 됩니다.
OneHotEncoder
원-핫 인코딩(One-hot encoding)은 레이블 인덱스로 표현된 범주형 피처를, 모든 값 중 특정 값의 존재를 나타내는 최대 하나의 1 값을 가진 이진 벡터로 매핑합니다. Logistic Regression처럼 연속 피처를 기대하는 알고리즘이 범주형 피처를 사용하게 해줘요. 문자열 타입 입력의 경우 보통 StringIndexer로 먼저 인코딩합니다.
OneHotEncoder는 여러 열을 변환할 수 있고, 각 입력 열에 대해 원-핫 인코딩된 출력 벡터 열을 반환합니다. VectorAssembler로 이런 벡터를 단일 피처 벡터로 병합하는 것이 일반적이에요. OneHotEncoder는 잘못된 입력을 처리하는 handleInvalid 파라미터를 지원하며 옵션은 'keep'(추가 인덱스 할당)과 'error'(오류 발생)입니다.
TargetEncoder
타깃 인코딩(Target Encoding)은 고카디널리티 범주형 피처를 회귀형 모델에 적합한 준연속 스칼라 속성으로 변환하는 데이터 전처리 기법입니다. 독립 피처의 개별 값을 스칼라에 매핑하는데, 이 스칼라는 종속 속성의 일부 추정을 나타냅니다.
Target Encoding은 범주형 피처와 타깃 변수 사이의 관계를 활용하므로 보통 One-Hot보다 성능이 좋고, 최종 이진 벡터 인코딩이 필요 없어 전체 차원을 줄여줘요. 단일 열은 inputCol과 outputCol, 다중 열은 inputCols와 outputCols(같은 크기)로 열 이름을 지정합니다. 이 열들은 범주형 인덱스(양의 정수)를 담을 것으로 예상되며 결측값(null)은 별도의 범주로 취급됩니다. label로 타깃 열을 지정하며 결측 레이블은 추정에서 제외됩니다. handleInvalid('keep'/'error')와 targetType('binary'/'continuous') 파라미터를 지원합니다. 'binary'는 $S_{i}=P(Y\mid X=X_{i})$(bin-counting), 'continuous'는 $S_{i}=E[Y\mid X=X_{i}]$(mean-encoding)로 매핑합니다. smoothing 파라미터는 클래스 내 통계와 전체 통계를 혼합해 드물게 보이는 범주의 과적합을 방지합니다.
VectorIndexer
VectorIndexer는 Vector 데이터셋에서 범주형 피처를 인덱싱합니다. 어떤 피처가 범주형인지 자동 결정하고 원래 값을 범주 인덱스로 변환해요:
- Vector 타입 입력 열과
maxCategories파라미터를 받음. - 고유 값 수를 기반으로, 최대
maxCategories개의 고유 값을 가진 피처를 범주형으로 선언. - 각 범주형 피처에 대한 0-기반 범주 인덱스 계산.
- 범주형 피처를 인덱싱하고 원래 값을 인덱스로 변환.
이 인덱싱은 Decision Trees와 Tree Ensembles가 범주형 피처를 적절히 처리하게 해 성능을 향상시킵니다. 변환된 데이터는 DecisionTreeRegressor 같은 범주형 피처를 처리하는 알고리즘에 전달될 수 있어요.
Interaction
Interaction은 벡터 또는 double 값 열을 가져와, 각 입력 열에서 하나의 값을 가진 모든 조합의 곱을 포함하는 단일 벡터 열을 생성하는 Transformer입니다. 예를 들어 각각 3차원인 벡터 열 2개가 입력이면 9차원 벡터를 출력합니다.
Normalizer
Normalizer는 각 Vector를 단위 노름으로 정규화하여 Vector 행 데이터셋을 변환하는 Transformer입니다. p-norm을 지정하는 p 파라미터를 가져요(기본값 $p = 2$). 입력 데이터를 표준화하고 학습 알고리즘의 동작을 개선하는 데 도움이 됩니다.
StandardScaler
StandardScaler는 각 피처를 단위 표준 편차 및/또는 0 평균으로 정규화합니다. 파라미터:
withStd: 기본값 True. 단위 표준 편차로 스케일링.withMean: 기본값 False. 스케일링 전에 평균으로 중심화. 조밀 출력을 만들므로 희소 입력에 주의.
StandardScaler는 데이터셋에 fit 하여 StandardScalerModel을 만드는 Estimator이며, 요약 통계 계산에 해당합니다. 피처의 표준 편차가 0이면 그 피처에 대해 0.0을 반환합니다.
RobustScaler
RobustScaler는 중앙값을 제거하고 데이터를 특정 분위수 범위(기본적으로 IQR, 1·3사분위수 사이)로 스케일링합니다. StandardScaler와 비슷하지만 평균 대신 중앙값, 표준 편차 대신 분위수 범위를 사용해 이상치에 강합니다. 파라미터: lower(0.25), upper(0.75), withScaling(True), withCentering(False, 조밀 출력 생성). 피처의 분위수 범위가 0이면 0.0을 반환합니다.
MinMaxScaler
MinMaxScaler는 각 피처를 특정 범위(보통 [0, 1])로 재스케일링합니다. 파라미터: min(0.0), max(1.0). 재스케일 값은 $Rescaled(e_i) = \frac{e_i - E_{min}}{E_{max} - E_{min}} * (max - min) + min$이며, $E_{max} == E_{min}$이면 $0.5 * (max + min)$입니다. 0 값이 0이 아닌 값으로 변환될 수 있으므로 희소 입력에도 출력은 DenseVector입니다.
MaxAbsScaler
MaxAbsScaler는 각 피처를 최대 절대값으로 나누어 [-1, 1] 범위로 재스케일링합니다. 데이터를 이동·중심화하지 않으므로 희소성을 유지합니다.
Bucketizer
Bucketizer는 연속 피처 열을 피처 버킷 열로 변환합니다. splits 파라미터로 불연속 경계를 지정하면 각 연속 값을 해당 버킷 인덱스로 매핑합니다. 예를 들어 splits가 [-Infinity, 0.5, 1.5, Infinity]라면 0.3은 0, 0.9는 1, 3.0은 2로 매핑됩니다. handleInvalid로 잘못된 입력(경계 밖 값)의 처리를 제어합니다.
ElementwiseProduct
ElementwiseProduct는 각 입력 벡터에 제공된 "가중치" 벡터를 요소별 곱(elment-wise multiplication)으로 곱합니다. 즉 데이터셋의 각 컬럼을 스칼라 승수로 스케일링해요. 입력 벡터 v와 변환 벡터 scalingVec 사이의 아다마르 곱(Hadamard product)을 나타냅니다.
SQLTransformer
SQLTransformer는 사용자가 정의한 SQL 문으로 행을 변환하는 변환기입니다. statement 파라미터로 SQL 문을 지정하며, 입력 DataFrame이 임시 테이블로 등록된 것처럼 SQL 문에서 SELECT를 실행합니다. 기본적으로 SELECT 문을 요구합니다.
VectorAssembler
VectorAssembler는 주어진 열 목록을 단일 벡터 열로 결합하는 변환기입니다. 원시 피처와 다른 변환기에서 생성된 다양한 피처를 하나의 피처 벡터로 병합해 Logistic Regression, Decision Trees 같은 ML 알고리즘에 전달하는 데 유용합니다. Vector, Double, Float 열을 받을 수 있습니다.
VectorSizeHint
VectorSizeHint는 벡터 유형 열에 크기 힌트를 지정합니다. 벡터 컬럼 크기를 설정하는 size 파라미터를 제공하며, handleInvalid(기본 'error')로 크기가 매칭되지 않는 벡터 처리를 제어합니다. 크기를 파악할 수 없어 다운스트림 변환기가 실패하는 것을 방지하는 데 유용해요.
QuantileDiscretizer
QuantileDiscretizer는 연속 피처 열을 지정된 개수의 버킷으로 이산화합니다. numBuckets로 버킷 수를 지정하고 근사 분위수를 계산해 경계를 정합니다(relativeError로 근사화). NaN 값은 handleInvalid 값으로 처리됩니다.
Imputer
Imputer는 결측값을 채우는 변환기로, 열의 평균, 중앙값 또는 최빈값으로 null이나 NaN 값을 대체합니다. strategy는 'mean', 'median', 'mode'(기본 'mean') 중 선택하며, missingValue로 결측 취급 값을 지정할 수 있어요(기본 Double.NaN). 여러 열을 동시에 처리할 수 있습니다.
피처 선택기 (Feature Selectors)
VectorSlicer
VectorSlicer는 피처 벡터 열에서 주어진 인덱스로 피처를 선택해 새 벡터 열을 만드는 변환기입니다. indices로 인덱스를, names로 입력 벡터의 feature attributes 이름을 사용해 기능을 선택할 수 있습니다.
RFormula
RFormula는 R의 모델 공식 구문으로 DataFrame 열을 피처로 지정합니다. ~, ., :, +, - 연산자를 지원해요. 예를 들어 y ~ a + b는 y를 레이블로, a, b를 피처로 지정합니다. 범주형 열을 인덱싱하고 연속형 열과 결합해 단일 피처 벡터를 생성합니다.
ChiSqSelector
ChiSqSelector는 카이-제곱(Chi-Squared) 피처 선택을 구현합니다. 범주형 피처가 있는 레이블된 데이터에 사용하며, 카이-제곱 독립성 검정으로 피처를 고릅니다. 다섯 가지 선택 방법을 지원해요: numTopFeatures, percentile, fpr, fdr, fwe.
numTopFeatures: 카이-제곱 검정에 따라 고정된 수의 상위 피처를 선택.percentile: 고정된 수 대신 전체 피처의 일정 비율을 선택.fpr: p-값이 임계값 아래인 모든 피처를 선택해 오탐율 제어.fdr: Benjamini-Hochberg 절차로 오발견율이 임계값 아래인 피처 선택.fwe: p-값이 임계값 아래인 모든 피처를 선택. 임계값은 1/numFeatures로 스케일링되어 가족별 오류율 제어.
기본 선택 모드는 numTopFeatures이고 기본 selectionThreshold는 50입니다.
UnivariateFeatureSelector
UnivariateFeatureSelector는 단변량 피처 선택을 구현합니다. featureType(categorical/continuous)과 labelType(categorical/continuous)의 조합에 따라 서로 다른 피처 선택 알고리즘을 지원합니다. selectionMode(numTopFeatures, percentile, fpr, fdr, fwe)와 selectionThreshold로 선택 방식을 지정할 수 있어요. 예를 들어 featureType=continuous, labelType=categorical, numTopFeatures=1이면 가장 유용한 피처 하나가 선택됩니다.
VarianceThresholdSelector
VarianceThresholdSelector는 저분산(low-variance) 피처를 제거하는 선택기입니다. (표본) 분산이 varianceThreshold 이하인 피처는 제거됩니다. 설정하지 않으면 varianceThreshold 기본값이 0이며, 분산이 0인 피처(모든 샘플에서 같은 값)만 제거됩니다.
Locality Sensitive Hashing
Locality Sensitive Hashing (LSH)은 대표적인 해싱 기법 클래스로, 대규모 데이터셋에서 클러스터링, 근사 최근접 이웃 검색, 이상치 탐지에 널리 사용됩니다. LSH의 일반적인 아이디어는 함수 계열("LSH families")을 사용해 데이터 포인트를 버킷으로 해시하는 것으로, 서로 가까운 데이터 포인트는 같은 버킷에 있을 확률이 높고 먼 데이터 포인트는 다른 버킷에 있을 확률이 매우 높도록 하는 것입니다.
거리 공간 (M, d)(M은 집합, d는 M의 거리 함수)에서 LSH family는 다음 속성을 만족하는 함수 h의 계열입니다:
\[ \forall p, q \in M,\\ d(p,q) \leq r1 \Rightarrow Pr(h(p)=h(q)) \geq p1\\ d(p,q) \geq r2 \Rightarrow Pr(h(p)=h(q)) \leq p2 \]
이 LSH family는 (r1, r2, p1, p2)-sensitive라고 합니다. Spark에서 서로 다른 LSH family는 별도의 클래스(예: MinHash)로 구현되며, 각 클래스에 피처 변환, 근사 유사도 조인, 근사 최근접 이웃 API가 제공됩니다. LSH에서 오탐지(false positive)는 같은 버킷으로 해시된 먼 피처 쌍($d(p,q) \geq r2$), 오탐지(false negative)는 다른 버킷으로 해시된 가까운 피처 쌍($d(p,q) \leq r1$)으로 정의합니다.
LSH 연산 (LSH Operations)
피팅된 LSH 모델은 각 연산에 대한 메서드를 가져요.
피처 변환 (Feature Transformation)
피처 변환은 해시 값을 새 열로 추가하는 기본 기능이며 차원 축소에 유용합니다. inputCol과 outputCol으로 열 이름을 지정합니다. LSH는 여러 해시 테이블을 지원하며 numHashTables로 수를 지정합니다. 이는 근사 유사도 조인과 근사 최근접 이웃의 OR 증폭에도 사용됩니다. 테이블 수를 늘리면 정확도가 높아지지만 통신 비용과 실행 시간도 늘어나요. outputCol 타입은 Seq[Vector]이며 배열 차원은 numHashTables와 같고 벡터 차원은 현재 1로 설정됩니다.
근사 유사도 조인 (Approximate Similarity Join)
근사 유사도 조인은 두 데이터셋을 받아 거리가 사용자 정의 임계값보다 작은 행 쌍을 근사적으로 반환합니다. 서로 다른 두 데이터셋 조인과 자기 조인(self-join)을 모두 지원하며, 자기 조인은 일부 중복 쌍을 만들 수 있어요. 변환된/변환되지 않은 데이터셋 둘 다 입력으로 받습니다. 변환되지 않은 데이터셋을 사용하면 자동으로 변환되고 해시 시그니처가 outputCol로 생성됩니다. 조인된 데이터셋에서 원래 데이터셋은 datasetA와 datasetB로 조회할 수 있고, 반환된 행 쌍의 실제 거리를 보여주는 distance 열이 추가됩니다.
근사 최근접 이웃 검색 (Approximate Nearest Neighbor Search)
근사 최근접 이웃 검색은 (피처 벡터의) 데이터셋과 키(단일 피처 벡터)를 받아 그 벡터에 가장 가까운 지정된 수의 행을 근사적으로 반환합니다. 변환된/변환되지 않은 데이터셋 둘 다 허용하며, 변환되지 않으면 자동으로 변환됩니다. 출력 데이터셋에 각 출력 행과 검색 키 사이의 실제 거리를 보여주는 distance 열이 추가됩니다. 해시 버킷에 후보가 충분하지 않으면 k개보다 적은 행을 반환합니다.
LSH 알고리즘 (LSH Algorithms)
유클리드 거리를 위한 Bucketed Random Projection
Bucketed Random Projection은 유클리드 거리를 위한 LSH family입니다. 유클리드 거리는 $d(\mathbf{x}, \mathbf{y}) = \sqrt{\sum_i (x_i - y_i)^2}$로 정의됩니다. 그 LSH family는 피처 벡터 $\mathbf{x}$를 임의의 단위 벡터 $\mathbf{v}$에 투영하고 결과를 해시 버킷으로 나눕니다:
\[ h(\mathbf{x}) = \Big\lfloor \frac{\mathbf{x} \cdot \mathbf{v}}{r} \Big\rfloor \]
여기서 r은 사용자 정의 버킷 길이이며 해시 버킷의 평균 크기(따라서 버킷 수)를 제어합니다. 더 큰 버킷 길이는 피처가 같은 버킷으로 해시될 확률을 높입니다(참/오탐지 수 증가). 임의의 벡터를 입력 피처로 받으며 희소·조밀 벡터 모두 지원해요.
자카드 거리를 위한 MinHash
MinHash는 입력 피처가 자연수 집합인 자카드 거리를 위한 LSH family입니다. 두 집합의 자카드 거리는 $d(\mathbf{A}, \mathbf{B}) = 1 - \frac{|\mathbf{A} \cap \mathbf{B}|}{|\mathbf{A} \cup \mathbf{B}|}$로 정의됩니다. MinHash는 집합의 각 요소에 임의 해시 함수 g를 적용하고 모든 해시 값의 최솟값을 취합니다:
\[ h(\mathbf{A}) = \min_{a \in \mathbf{A}}(g(a)) \]
MinHash 입력 집합은 이진 벡터로 표현되며, 벡터 인덱스는 요소 자체를, 0이 아닌 값은 그 요소의 존재를 나타냅니다. 조밀·희소 벡터 모두 지원하지만 효율을 위해 희소 벡터가 권장됩니다. 예를 들어 Vectors.sparse(10, Array[(2, 1.0), (3, 1.0), (5, 1.0)])은 공간에 10개의 요소가 있고 이 집합이 요소 2, 3, 5를 포함한다는 뜻입니다. 모든 0이 아닌 값은 이진 "1"로 취급됩니다. 참고: 빈 집합은 MinHash로 변환할 수 없으므로 입력 벡터는 0이 아닌 항목을 최소 1개 가져야 해요.
더 알아보기 (Learn more)
- MLlib 주요 가이드 (MLlib: Main Guide): 전체 파이프라인 구조를 이해해요.
- 피처 추출·변환 (RDD 기반): RDD 기반 API에서의 동일 개념을 비교해 봐요.
- 데이터 타입 (Data Types):
Vector와Matrix타입을 다시 확인해요.