5. 데이터 준비
데이터를 변환할 때는 자동화하는 함수를 만들어 진행한다.
새로운 데이터셋을 불러와도 데이터 변환을 하기에 유용하고 여러 가지 데이터 변환으로 어떤 조합이 가장 최적한지 확인할 수 있다.
1. 데이터 정제
머신러닝은 누락된 특성을 다루지 못하므로 이를 처리할 수 있는 함수를 만들 것이다.
housing.dropna(subset=["total_bedrooms"], inplace=True) # 옵션 1
housing.drop("total_bedrooms", axis=1) # 옵션 2
median = housing["total_bedrooms"].median() # 옵션 3
housing["total_bedrooms"].fillna(median, inplace=True)
옵션 1 : 해당 구역을 제거합니다.
옵션 2 : 전체 특성을 삭제합니다.
옵션 3 : 누락된 값을 다른 값으로 채웁니다. (0, 평균, 중간값) => 대체
옵션 3이 데이터를 최대한 유지하므로 이를 선택한다.
옵션 3은 사이킷런 안에 있는 SimpleImputer 클래스를 사용한다. 이 클래스는 중간값으로 대체한다.
from sklearn.impute import SimpleImputer
imputer = SimpleImputer(strategy="median")
housing_num = housing.select_dtypes(include=[np.number]) # imputer 객체의 fit() 메서드 사용
imputer.fit(housing_num)
strategy는 중간값 뿐만 아니라 mean, most_frequent, constant 등으로 바꿀 수 있다.
housing_tr = pd.DataFrame(X, columns=housing_num.columns,
index=housing_num.index)
사이킷런 변환기는 데이터프레임이 입력 되어도 넘파이 배열을 출력한다.
그래서 X를 데이터프레임으로 감싸서 열 이름과 인덱스를 복원한다.
2. 범주형 자료 다루기
머신러닝은 숫자를 다루므로 범주형 자료들은 숫자로 변환해야 한다.
이를 위해 사이킷런의 OrdinalEncoder를 사용한다.
housing_cat = housing[["ocean_proximity"]]
housing_cat.head(8)
from sklearn.preprocessing import OrdinalEncoder
ordinal_encoder = OrdinalEncoder()
housing_cat_encoded = ordinal_encoder.fit_transform(housing_cat)
housing_cat_encoded[:8]
>>>
array([[3.],
[0.],
[1.],
[1.],
[4.],
[1.],
[0.],
[3.]])
이러한 표현 방식의 문제는 가까이 있는 두 값이 비슷하다고 생각하는 점이다.
이 문제를 해결하는 방법은 카테고리 이진 특성을 만들어서 해결한다.
한 특성만 1이고 나머지는 0으로 만드는 원-핫 인코딩이다.
cat_encoder = OneHotEncoder(sparse_output=False)
housing_cat_1hot = cat_encoder.fit_transform(housing_cat)
housing_cat_1hot
OnehotEncdoer 출력은 넘파이 배열이 아닌 희소행렬 (Sparse Matrix)이다.
희소 행렬은 0이 대부분인 행렬로 효율적으로 표현할 수 있다.
# 넘파이 배열 출력
cat_encoder = OneHotEncoder(sparse_output=False)
housing_cat_1hot = cat_encoder.fit_transform(housing_cat)
housing_cat_1hot
>>>
array([[0., 0., 0., 1., 0.],
[1., 0., 0., 0., 0.],
[0., 1., 0., 0., 0.],
...,
[0., 0., 0., 0., 1.],
[1., 0., 0., 0., 0.],
[0., 0., 0., 0., 1.]])
# categories_ : 카테고리 리스트 얻기
cat_encoder.categories_
>>> [array(['<1H OCEAN', 'INLAND', 'ISLAND', 'NEAR BAY', 'NEAR OCEAN'],
dtype=object)]
# 범주형 특성을 카테고리마다 원-핫 표현으로 바꾸기
df_test = pd.DataFrame({"ocean_proximity": ["INLAND", "NEAR BAY"]})
pd.get_dummies(df_test)
cat_encoder.transform(df_test)
>>>
array([[0., 1., 0., 0., 0.],
[0., 0., 0., 1., 0.]])
fits_transform() 이 아니라 transform()으로 적용한 데이터프레임에 적용되었을 때 결과이다.
filts_transform()은 두 개의 카테고리만 보았기 때문에 두 개 열만 출력하고 밑은 학습된 카테고리마다 하나의 열을 출력한다.
cat_encoder.handle_unknown = "ignore"
cat_encoder.transform(df_test_unknown)
>>> array([[0., 0., 0., 0., 0.],
[0., 0., 1., 0., 0.]])
OnehotEncoder는 알 수 없는 카테고리를 감지하고 예외를 발생시킨다.
handle_unknown 매개변수를 'ignore'로 지정하여 알 수 없는 카테고리를 0으로 정할 수 있다.
cat_encoder.feature_names_in_ # 사이킷런 추정기가 열 이름을 저장하는 곳
>>> array(['ocean_proximity'], dtype=object)
cat_encoder.get_feature_names_out() # 동일한 열 이름 갖는지 확인
>>> array(['ocean_proximity_<1H OCEAN', 'ocean_proximity_INLAND',
'ocean_proximity_ISLAND', 'ocean_proximity_NEAR BAY',
'ocean_proximity_NEAR OCEAN'], dtype=object)
3. 특성 스케일과 변환
데이터에서 중요한 변환은 특성 스케일링이다.
모든 특성의 범위를 같게 만들어주는 대표적인 방법은 min-max 스케일링과 표준화가 사용된다.
스케일링을 진행할 땐 훈련 데이터로만 수행한다.
- min-max 스케일링 (정규화)
각 특성에 대해서 0~1 범위에 들도록 조정한다.
데이터에서 최솟값을 뺀 후 최댓값과 최솟값의 차이로 나눈다.
from sklearn.preprocessing import MinMaxScaler
min_max_scaler = MinMaxScaler(feature_range=(-1, 1))
housing_num_min_max_scaled = min_max_scaler.fit_transform(housing_num)
feature_range 매개변수로 0~1이 아닌 다른 범위로 변경할 수 있다.
- 표준화
데이터에서 평균을 뺀 후 표준편차로 나눈다 => 평균은 0이 되고 표준편차는 1이 된다.
표준화는 스케일링과 달리 특정 범위로 값을 제한하지 않는다
from sklearn.preprocessing import StandardScaler
std_scaler = StandardScaler()
housing_num_std_scaled = std_scaler.fit_transform(housing_num)
분포 별로 스케일링 하는 법
--------- 입력 특성 변환 ---------
- 특성 분포의 꼬리가 두꺼울 때 : 데이터 변환 후, 분포를 대칭으로 만들기
- 멱법칙 분포 (특성 분포의 꼬리가 길고 두꺼울 때) : 특성을 로그값으로 바꾸기 or 버킷타이징
* 버킷타이징 : 특성값의 해당하는 버킷을 인덱스로 변경
- 멀티모달 분포 (모드라 부르는 정점이 두 개 이상인 분포) : 버킷타이징 => 버킷 인덱스를 인코딩, 주요 모드와 특정 모드 사이의 유사도를 나타내는 특성 추가 => 방사 기저 함수 (입력값과 고정 포인트 사이의 거리에만 의존)
# 중간 주택 연도와 유사도를 재는 RBF
from sklearn.metrics.pairwise import rbf_kernel
age_simil_35 = rbf_kernel(housing[["housing_median_age"]], [[35]], gamma=0.1)
--------- 타깃값 변환 ---------
- 타깃분포의 꼬리가 두꺼울 때 : 로그값으로 변환한다. 하지만 이렇게 되면 타깃값의 로그값을 예측하기 때문에 모델에 지수함수를 적용해야 한다.
from sklearn.linear_model import LinearRegression
target_scaler = StandardScaler()
scaled_labels = target_scaler.fit_transform(housing_labels.to_frame())
model = LinearRegression()
model.fit(housing[["median_income"]], scaled_labels)
some_new_data = housing[["median_income"]].iloc[:5] # 새로운 데이터라고 가정합니다
scaled_predictions = model.predict(some_new_data)
predictions = target_scaler.inverse_transform(scaled_predictions) # inverse_transform()으로 원본 스케일로 되돌리기
from sklearn.compose import TransformedTargetRegressor
model = TransformedTargetRegressor(LinearRegression(),
transformer=StandardScaler())
model.fit(housing[["median_income"]], housing_labels)
predictions = model.predict(some_new_data)
위보다 더 간단한 방법이 TransformedTargetRegressor이다.
객체를 생성하고 다음 회귀 모델과 레이블 변환기를 전달하고 원본 레이블을 사용해 훈련 세트로 훈련한다.
4. 사용자 정의 변환기
- 로그 변환기
from sklearn.preprocessing import FunctionTransformer
log_transformer = FunctionTransformer(np.log, inverse_func=np.exp)
log_pop = log_transformer.transform(housing[["population"]])
TransformedTargetRegressor에 변환기를 사용하려면 inverse_func 매개변수에 역변환 함수를 지정할 수 있다.
- RBF 유사도 변환기
rbf_transformer = FunctionTransformer(rbf_kernel,
kw_args=dict(Y=[[35.]], gamma=0.1))
age_simil_35 = rbf_transformer.transform(housing[["housing_median_age"]])
RBF 커널은 고정 포인트애서 일정 거리만큼 떨어진 값이 항상 두 개이기 때문에 역함수가 없다.
sf_coords = 37.7749, -122.41
sf_transformer = FunctionTransformer(rbf_kernel,
kw_args=dict(Y=[sf_coords], gamma=0.1))
sf_simil = sf_transformer.transform(housing[["latitude", "longitude"]])
rbf_kermel() 특성은 개별적으로 처리하지 않는다.
ratio_transformer = FunctionTransformer(lambda X: X[:, [0]] / X[:, [1]])
ratio_transformer.transform(np.array([[1., 2.], [3., 4.]]))
사용자 정의 변환기는 특성을 합칠 때도 유용하다.
from sklearn.cluster import KMeans
class ClusterSimilarity(BaseEstimator, TransformerMixin):
def __init__(self, n_clusters=10, gamma=1.0, random_state=None):
self.n_clusters = n_clusters
self.gamma = gamma
self.random_state = random_state
def fit(self, X, y=None, sample_weight=None):
# 사이킷런 1.2버전에서 최상의 결과를 찾기 위해 반복하는 횟수를 지정하는 `n_init` 매개변수 값에 `'auto'`가 추가되었습니다.
# `n_init='auto'`로 지정하면 초기화 방법을 지정하는 `init='random'`일 때 10, `init='k-means++'`일 때 1이 됩니다.
# 사이킷런 1.4버전에서 `n_init`의 기본값이 10에서 `'auto'`로 바뀝니다. 경고를 피하기 위해 `n_init=10`으로 지정합니다.
self.kmeans_ = KMeans(self.n_clusters, n_init=10, random_state=self.random_state)
self.kmeans_.fit(X, sample_weight=sample_weight)
return self # 항상 self를 반환합니다!
def transform(self, X):
return rbf_kernel(X, self.kmeans_.cluster_centers_, gamma=self.gamma)
def get_feature_names_out(self, names=None):
return [f"클러스터 {i} 유사도" for i in range(self.n_clusters)]
하나의 사용자 변환기가 구현 안에서 다른 추정기를 사용하는 경우도 있다.
위 코드 같은 경우는 KMeans 클래스를 사용하는 사용자 변환기를 보여준다.
그 다음 transform() 메서드에서 rbf_kernel() 을 사용해서 각 샘플이 클러스터 중심과 얼마나 유사한지 측정한다.
* k-평균
cluster_simil = ClusterSimilarity(n_clusters=10, gamma=1., random_state=42)
similarities = cluster_simil.fit_transform(housing[["latitude", "longitude"]],
sample_weight=housing_labels)
이 코드는 클러스터 개수를 10으로 지정해서 만든다.
주택 가격으로 가중치를 부여하고 모든 구역에 위도와 경도로 fit_transform()을 호출한다.
=> k-평균 클러스터를 찾고 각 구역과 10개의 클러스터 중심 사이의 가우스 RBF 유사도를 측정한다.
similarities[:3].round(2)
>>> array([[0. , 0.14, 0. , 0. , 0. , 0.08, 0. , 0.99, 0. , 0.6 ],
[0.63, 0. , 0.99, 0. , 0. , 0. , 0.04, 0. , 0.11, 0. ],
[0. , 0.29, 0. , 0. , 0.01, 0.44, 0. , 0.7 , 0. , 0.3 ]])
5. 변환 파이프라인
사이킷런은 순서대로 처리하도록 도와주는 Pipeline 클래스 제공한다. 수치 특성에서 누락된 값을 대체하고 스케일을 조정하는 역할을 한다.
from sklearn.pipeline import Pipeline
num_pipeline = Pipeline([
("impute", SimpleImputer(strategy="median")),
("standardize", StandardScaler()),
])
Pipeline 생성자는 연속적인 단계를 정의하는 이름/추정기 쌍의 리스트를 받는다. (2개의 원소를 가진 튜플)
from sklearn.pipeline import make_pipeline
num_pipeline = make_pipeline(SimpleImputer(strategy="median"), StandardScaler())
변환기 이름을 짓는게 귀찮다면 make_pipeline() 함수를 사용해도 된다.
파이프라인의 fit() 메서드를 출력하면 모든 변환기의 fit_transform() 메서드를 출력하면서 한 단계의 출력을 다음 단계의 입력으로 전달한다.
마지막에서는 fit() 메서드만 출력한다.
파이프라인의 transform() 메서드를 호출하면 모든 변환을 순서대로 적용하면 마지막 추정기가 변환기가 아니라 예측기라면 transform() 대신 predict() 메서드를 가진다. 데이터의 모든 변환을 순서대로 적용하고 그 결과를 예측기의 predict() 메서드에 전달하는 것이다.
밑에는 지금까지 설명한 작업을 수행하는 파이프라인을 만드는 코드이다.
def column_ratio(X):
return X[:, [0]] / X[:, [1]]
def ratio_name(function_transformer, feature_names_in):
return ["ratio"] # get_feature_names_out에 사용
def ratio_pipeline():
return make_pipeline(
SimpleImputer(strategy="median"),
FunctionTransformer(column_ratio, feature_names_out=ratio_name),
StandardScaler())
log_pipeline = make_pipeline(
SimpleImputer(strategy="median"),
FunctionTransformer(np.log, feature_names_out="one-to-one"),
StandardScaler())
cluster_simil = ClusterSimilarity(n_clusters=10, gamma=1., random_state=42)
default_num_pipeline = make_pipeline(SimpleImputer(strategy="median"),
StandardScaler())
preprocessing = ColumnTransformer([
("bedrooms", ratio_pipeline(), ["total_bedrooms", "total_rooms"]),
("rooms_per_house", ratio_pipeline(), ["total_rooms", "households"]),
("people_per_house", ratio_pipeline(), ["population", "households"]),
("log", log_pipeline, ["total_bedrooms", "total_rooms", "population",
"households", "median_income"]),
("geo", cluster_simil, ["latitude", "longitude"]),
("cat", cat_pipeline, make_column_selector(dtype_include=object)),
],
remainder=default_num_pipeline) # 남은 특성: housing_median_age
'ML' 카테고리의 다른 글
[Machine Learning][3] 분류 (2) (0) | 2025.03.11 |
---|---|
[Machine Learning][3] 분류 (1) (0) | 2025.03.10 |
[Machine Laerning][2] 머신러닝 주요 단계 (3) (0) | 2025.03.07 |
[Machine Learning][2] 머신러닝 주요 단계 (1) (0) | 2025.03.04 |
[Machine Learning][1] 머신러닝 (0) | 2025.03.03 |