데이터 표현과 특성 공학

2018. 1. 11. 01:15ML(머신러닝)/BASIC

728x90

ㅇ우리는 이제까지 보통 2차원 실수행 배열로 데인터 포인트 특성 - 연속형 특성


하지만 일반적인 특성의 전형적인 형태는 범주형 특성  또는 이산형 특성 -> 보통 숫자가 아님


범주형 특성과 연속형 특성의 차이 분류와 회귀 차이와 비슷하지만, 출력이 아닌 입력에 대한 점


연속형 - 픽셀 밝기, iris 측정값 


범주형 - 제품의 브랜드, 색상, 판매분류 (연속된 값으로 나타나지 않는다) 


책과 옷사이에는 중간 값 x , 순서도 x 


하지만 데이터가 어떤 형태의 특성으로 구성되어 있는가 보다 -> 어떻게 표현하는지에 따라 머신러닝 성능의 영향을 크게 준다.


지도, 비지도에서는 스케일이 중요하다 함 / 스케일 조정 x -> 측정치가 cm / m 에 따라 차이가 생긴다.


상호작용 이나 다항식을 추가 특성으로 넣는 것도 도움이 된다.


가장 적합한 데이터 표현을 찾는 것을 특성공학 / 실제 문제를 풀기 위해 주요 작업 중 하나


올바른 데이터 표현 > 매개변수 보다 성능에 더 큰 영향 줌




범주형 변수


남자 / 여자, 직업별 = 정성적 데이터  -> 다른 방식으로 표현 해야함


1) one-hot-encoding(가변수)


=one-out-of-N encoding = dummy variable


dummy variable 은 범주형 변수 -> 0 / 1 값을 가진 하나 이상의 새로운 특성


0, 1 변수 -> 선형 이진 분류 공식에 적용 할 수 있어서 -> 개수에 상관없이 범주마다 하나의 특성으로 표현


a ,b ,c ,d -> a (1,0,0,0) b(0,1,0,0) ... 이런식으로 

print("원본 특성:\n", list(data.columns), "\n")
data_dummies = pd.get_dummies(data)
print("get_dummies 후의 특성:\n", list(data_dummies.columns))

features = data_dummies.loc[:, 'age':'occupation_ Transport-moving']
# NumPy 배열 추출
X = features.values
y = data_dummies['income_ >50K'].values
print("X.shape: {}  y.shape: {}".format(X.shape, y.shape))


from sklearn.linear_model import LogisticRegression

from sklearn.model_selection import train_test_split
X_train, X_test, y_train, y_test = train_test_split(X, y, random_state=0)
logreg = LogisticRegression()
logreg.fit(X_train, y_train)
print("테스트 점수: {:.2f}".format(logreg.score(X_test, y_test)))
테스트 점수: 0.81

scikit-learn 에서는 문자열도 타깃값으로 가능하다 - 그대로 사용 가능


get_dummies() = 훈련과 테스트 세트 범주형이 같은 방식으로 표현되어야 함


-> 안그러면 성능이 나빠질 경우가 생김




2) 숫자로 표현된 범주형 특성


범주형 변수 -> 숫자로 분류 함


a,b,c -> 0 ,1, 2


예컨데 별 다섯개 만점 같은 데이터 경우 적절한 인코딩 방법은 풀려는 문제나 데이터, 알고리즘에 따라서 판단


pandas get_dummies -> 숫자 특성은 모두 -> 연속형이라고 생각해서 -> 가변수 안만듬


대신 어떤 열이 연속형인지 범주형인지 지정 scikit-learn -> OneHotEncoder


숫자로 된 열 -> 문자열로 바꿀 수 있다.


# 숫자 특성과 범주형 문자열 특성을 가진 DataFrame을 만듭니다
demo_df = pd.DataFrame({'숫자 특성': [0, 1, 2, 1],
                        '범주형 특성': ['양말', '여우', '양말', '상자']})
display(demo_df)




display(pd.get_dummies(demo_df))

demo_df['숫자 특성'] = demo_df['숫자 특성'].astype(str)
display(pd.get_dummies(demo_df, columns=['숫자 특성', '범주형 특성']))





구간 분할, 이산화 그리고 선형 모델, 트리 모델


데이터 가장 잘표현 하는 법 -> 데이터가 가진 의미 + 어떤 모델 사용할지 


폭넓게 사용하는 알고리즘 -> 1. 선형모델 2. 트리기반(결정 ,그래디언트 등등) 


-> 표현 방식으로 인해 미치는 영향이 매우 다름 !!


from sklearn.linear_model import LinearRegression
from sklearn.tree import DecisionTreeRegressor

X, y = mglearn.datasets.make_wave(n_samples=100)
line = np.linspace(-3, 3, 1000, endpoint=False).reshape(-1, 1)

reg = DecisionTreeRegressor(min_samples_split=3).fit(X, y)
plt.plot(line, reg.predict(line), label="결정 트리")

reg = LinearRegression().fit(X, y)
plt.plot(line, reg.predict(line), '--', label="선형 회귀")

plt.plot(X[:, 0], y, 'o', c='k')
plt.ylabel("회귀 출력")
plt.xlabel("입력 특성")
plt.legend(loc="best")


선형 모델 - 선형 관계 - 직선


결정트리 - 훨씬 복잡한 모델 그러나 데이터 표현 형태에 따라 굉장히 달라짐


연속형 데이터 - 아주 강력한 선형 모델을 만드는 방법 하나는  -> 구간 분할(이산화)(binding)



bins = np.linspace(-3, 3, 11)
print("bins: {}".format(bins))


bins: [-3. -2.4 -1.8 -1.2 -0.6 0. 0.6 1.2 1.8 2.4 3. ]

which_bin = np.digitize(X, bins=bins)

which_bin = np.digitize(X, bins=bins)
print("\n데이터 포인트:\n", X[:5])
print("\n데이터 포인트의 소속 구간:\n", which_bin[:5])
데이터 포인트:
 [[-0.753]
 [ 2.704]
 [ 1.392]
 [ 0.592]
 [-2.064]]

데이터 포인트의 소속 구간:
 [[ 4]
 [10]
 [ 8]
 [ 6]
 [ 2]]

np.digitize 첫구간 포함 종료점 포함 x -3<= x < -2.4 -3보다 작은 구간 0 3보다 큰 구간 11 그사이는 1~10

==> 이렇게 함으로 써 -> 연속형 특성 -> 각 데이터 포인트 어느 구간에 속했는지 -> 인코딩한 범주형 특성


scikit-learn 모델 적용하기 위해 - preprocessing 모듈의 OneHotEncoder로 이산적인 특성으로 -> onehot-encoding으로 변환


onehotencoder 는 pandas.get_dummies 와 같지만 -> 숫자로 된 범주형 변수에만 적용


from sklearn.preprocessing import OneHotEncoder
# 변환을 위해 OneHotEncoder를 사용합니다
encoder = OneHotEncoder(sparse=False)
# encoder.fit은 which_bin에 나타난 유일한 값을 찾습니다
encoder.fit(which_bin)
# 원-핫-인코딩으로 변환합니다
X_binned = encoder.transform(which_bin)
print(X_binned[:5])
[[ 0.  0.  0.  1.  0.  0.  0.  0.  0.  0.]
 [ 0.  0.  0.  0.  0.  0.  0.  0.  0.  1.]
 [ 0.  0.  0.  0.  0.  0.  0.  1.  0.  0.]
 [ 0.  0.  0.  0.  0.  1.  0.  0.  0.  0.]
 [ 0.  1.  0.  0.  0.  0.  0.  0.  0.  0.]]
print("X_binned.shape: {}".format(X_binned.shape))
X_binned.shape: (100, 10)
line_binned = encoder.transform(np.digitize(line, bins=bins))

reg = LinearRegression().fit(X_binned, y)
plt.plot(line, reg.predict(line_binned), label='구간 선형 회귀')

reg = DecisionTreeRegressor(min_samples_split=3).fit(X_binned, y)
plt.plot(line, reg.predict(line_binned), '--', label='구간 결정 트리')
plt.plot(X[:, 0], y, 'o', c='k')
plt.vlines(bins, -3, 3, linewidth=1, alpha=.2)
plt.legend(loc="best")
plt.ylabel("회귀 출력")
plt.xlabel("입력 특성")

==> 선형 트리와 결정 트리 실선이 완전히 겹친다.


이 두 모델이 예측한 것은 상숫값 / 각 구간 안에서는 특성의 값이 상수 


-> 어떤 모델이든 그 구간 포인트에 대해서는 같은 값을 예측 


-> 구간 나눈 것을 보면 -> 선형 트리 훨씬 유리 / 결정 트리 덜 유연 해짐


트리 모델은 데이터를 자유롭게 나눌 수 있어서 -> 오히려 덜 유연해짐


결정 트리는 한번에 여러 특성 고려 가능 / 선형트리는 나눔에 따라서 큰 이득


용량이 크고 고차원 -> 선형 모델 사양한다면 구간분할 모델 성능 높이는데 도움 준다.




 상호작용과 다항식


특성을 풍부하게 -> 원본데이터 상호 작용(interaction) 과 다항식(polynomial) 추가 


-> 통계적 모델링에 주로 사용 -> 일반 어플리케이션에도 많이 적용


X_combined = np.hstack([X, X_binned])
print(X_combined.shape)
(100, 11)
reg = LinearRegression().fit(X_combined, y)

line_combined = np.hstack([line, line_binned])
plt.plot(line, reg.predict(line_combined), label='원본 특성을 더한 선형 회귀')

for bin in bins:
    plt.plot([bin, bin], [-3, 3], ':', c='k', linewidth=1)
plt.legend(loc="best")
plt.ylabel("회귀 출력")
plt.xlabel("입력 특성")
plt.plot(X[:, 0], y, 'o', c='k')



각 구간 -> 절편과 기울기 학습 


-> 학습된 기울기 음수 -> 모든 구간 동일 -> 즉 x 축 특성이 하나이므로 기울기도 하나 


-> 기울기가 모두 같으니 유익해보이지 않음 -> 다른 기울기 효과 주고 싶음


-> 상호작용 추가하기

X_product = np.hstack([X_binned, X * X_binned])
print(X_product.shape)
# 즉 이 값은 구간  안에서는 원본 특성 다른 곳에서는 0 
# X_binned = encoder.transform(which_bin) 이므로 해당 구간 외에는 0
(100, 20)
reg = LinearRegression().fit(X_product, y)

line_product = np.hstack([line_binned, line * line_binned])
plt.plot(line, reg.predict(line_product), label='원본 특성을 곱한 선형 회귀')

for bin in bins:
    plt.plot([bin, bin], [-3, 3], ':', c='k', linewidth=1)

plt.plot(X[:, 0], y, 'o', c='k')
plt.ylabel("회귀 출력")
plt.xlabel("입력 특성")
plt.legend(loc="best")


구간 나누기 -> 연속형 특성을 확장하는 방법 중 하나 / 원본 특성의 다항식을 추가하는 방법도 있습니다.




from sklearn.preprocessing import PolynomialFeatures
# x ** 10까지 고차항을 추가합니다
# 기본값인 "include_bias=True"는 절편에 해당하는 1인 특성을 추가합니다
poly = PolynomialFeatures(degree=10, include_bias=False)
poly.fit(X)
X_poly = poly.transform(X)

# include_bias=True 이면 -> 절편을 고려하여 11개의 특성이 만들어 진다.

print("X_poly.shape: {}".format(X_poly.shape))
X_poly.shape: (100, 10) # 10차원 사용
print("X 원소:\n{}".format(X[:5]))
print("X_poly 원소:\n{}".format(X_poly[:5]))
X 원소:
[[-0.753]
 [ 2.704]
 [ 1.392]
 [ 0.592]
 [-2.064]]
X_poly 원소:
[[    -0.753      0.567     -0.427      0.321     -0.242      0.182
      -0.137      0.103     -0.078      0.058]
 [     2.704      7.313     19.777     53.482    144.632    391.125
    1057.714   2860.36    7735.232  20918.278]
 [   



1.392 1.938 2.697 3.754 5.226 7.274 10.125 14.094 19.618 27.307] [ 0.592 0.35 0.207 0.123 0.073 0.043 0.025 0.015 0.009 0.005] [ -2.064 4.26 -8.791 18.144 -37.448 77.289 -159.516 329.222 -679.478 1402.367]]
print("항 이름:\n{}".format(poly.get_feature_names()))
항 이름:
['x0', 'x0^2', 'x0^3', 'x0^4', 'x0^5', 'x0^6', 'x0^7', 'x0^8', 'x0^9', 'x0^10']
reg = LinearRegression().fit(X_poly, y)

line_poly = poly.transform(line)
plt.plot(line, reg.predict(line_poly), label='다항 선형 회귀')
plt.plot(X[:, 0], y, 'o', c='k')
plt.ylabel("회귀 출력")
plt.xlabel("입력 특성")
plt.legend(loc="best")


데이터가 부족한 영역에서는 민간함게 동작


from sklearn.svm import SVR

for gamma in [1, 10]:
    svr = SVR(gamma=gamma).fit(X, y)
    plt.plot(line, svr.predict(line), label='SVR gamma={}'.format(gamma))

plt.plot(X[:, 0], y, 'o', c='k')
plt.ylabel("회귀 출력")
plt.xlabel("입력 특성")
plt.legend(loc="best")


아무런 변환하지 않은 원본 데이터 -> SVM -> 다항 회귀와 비슷한 복잡도로 가진 예측 만듬



from sklearn.datasets import load_boston
from sklearn.model_selection import train_test_split
from sklearn.preprocessing import MinMaxScaler

boston = load_boston()
X_train, X_test, y_train, y_test = train_test_split(boston.data, boston.target,
                                                    random_state=0)

# 데이터 스케일 조정
scaler = MinMaxScaler()
X_train_scaled = scaler.fit_transform(X_train)
X_test_scaled = scaler.transform(X_test)

poly = PolynomialFeatures(degree=2).fit(X_train_scaled) # DEFAULT = include_bias=True

# DEGREE=2 -> 원본 특성에서 2개를 뽑아 모든 곱을 얻는다 X_train_poly = poly.transform(X_train_scaled) X_test_poly = poly.transform(X_test_scaled) print("X_train.shape: {}".format(X_train.shape)) print("X_train_poly.shape: {}".format(X_train_poly.shape))

X_train.shape: (379, 13)
X_train_poly.shape: (379, 105)
print("다항 특성 이름:\n{}".format(poly.get_feature_names()))
다항 특성 이름:
['1', 'x0', 'x1', 'x2', 'x3', 'x4', 'x5', 'x6', 'x7', 'x8', 'x9', 'x10', 'x11', 'x12', 
'x0^2', 'x0 x1', 'x0 x2', 'x0 x3', 'x0 x4', 'x0 x5', 'x0 x6', 'x0 x7', 'x0 x8', 'x0 x9', 
'x0 x10', 'x0 x11', 'x0 x12', 'x1^2', 'x1 x2', 'x1 x3', 'x1 x4', 'x1 x5', 'x1 x6', 'x1 x7',
 'x1 x8', 'x1 x9', 'x1 x10', 'x1 x11', 'x1 x12', 'x2^2', 'x2 x3', 'x2 x4', 'x2 x5', 'x2 x6', 
'x2 x7', 'x2 x8', 'x2 x9', 'x2 x10', 'x2 x11', 'x2 x12', 'x3^2', 'x3 x4', 'x3 x5', 
'x3 x6', 'x3 x7', 'x3 x8', 'x3 x9', 'x3 x10', 'x3 x11', 'x3 x12', 'x4^2', 'x4 x5', 'x4 x6', 
'x4 x7', 'x4 x8', 'x4 x9', 'x4 x10', 'x4 x11', 'x4 x12', 'x5^2', 'x5 x6', 'x5 x7', 'x5 x8',
 'x5 x9', 'x5 x10', 'x5 x11', 'x5 x12', 'x6^2', 'x6 x7', 'x6 x8', 'x6 x9', 'x6 x10', 'x6 x11', 
'x6 x12', 'x7^2', 'x7 x8', 'x7 x9', 'x7 x10', 'x7 x11', 'x7 x12', 'x8^2', 'x8 x9', 'x8 x10', 
'x8 x11', 'x8 x12', 'x9^2', 'x9 x10', 'x9 x11', 'x9 x12', 'x10^2', 'x10 x11', 'x10 x12', 'x11^2', 'x11 x12', 'x12^2']


# 만약 degree=3 이면 -> 원본 특성,중복을 허용하여  2개를 뽑아 -> 곱의 항 중복을 허용 -> 3개를 뽑아 만들 수 있는 항


1+13+91+455 = 560 interation_only=True -> 거듭제곱이 포함된 항은 모두 제외 

from sklearn.linear_model import Ridge
ridge = Ridge().fit(X_train_scaled, y_train)
print("상호작용 특성이 없을 때 점수: {:.3f}".format(ridge.score(X_test_scaled, y_test)))
ridge = Ridge().fit(X_train_poly, y_train)
print("상호작용 특성이 있을 때 점수: {:.3f}".format(ridge.score(X_test_poly, y_test)))

상호작용 특성이 없을 때 점수: 0.621 상호작용 특성이 있을 때 점수: 0.753

#ridge 다항식과 상호작용으로 -> 성능 크게


from sklearn.ensemble import RandomForestRegressor
rf = RandomForestRegressor(n_estimators=100, random_state=0).fit(X_train_scaled, y_train)
print("상호작용 특성이 없을 때 점수: {:.3f}".format(rf.score(X_test_scaled, y_test)))
rf = RandomForestRegressor(n_estimators=100, random_state=0).fit(X_train_poly, y_train)
print("상호작용 특성이 있을 때 점수: {:.3f}".format(rf.score(X_test_poly, y_test)))

상호작용 특성이 없을 때 점수: 0.795 상호작용 특성이 있을 때 점수: 0.77


오히려 더 떨어짐 -> 특성 추가하면 오히려 성능이 줄어든다.





일변량 비선형 변환


앞에서 제곱 항, 세제곱 항 추가 -> 선형 회귀 모델에 도움이 된다 


-> log / exp / sin 수학 함수를 적용하는 방법도 특성 변환에 유용


트리기반 -특성의 순서에만 영향


선형 모델과 신경망 - 각 특성의 스케일과 분포에 밀접하게 연관


특성과 타깃값 사이 비선형 -> 특히 선형 회귀에서더 더욱 어려움


log / exp -> 데이터 스케일을 변경해 선형 모델과 신경망의 성능 도움 됨


sin / cos -> 주기적인 패턴이 들어있는 데이터 -> 편리


대부분의 모델 -> 정규분포와 비슷 할 때 최고의 성능을 냅니다.(종모양)


-> log / exp 사용하는 것은 편법어지만 -> 쉽고 효과적인 방법


도움 되는 경우 -> 정수 카운트 데이터 다룰 때 


카운트 데이터 (사용자가 얼마나 자주 로그인 하지는) ->음수값 x 

-> 특별한 통계 패턴을 따르는 경우가 많다 


확률적 요소를 가진 많은 알고리즘의 이론-> 정규분포 근간

rnd = np.random.RandomState(0)
X_org = rnd.normal(size=(1000, 3))
w = rnd.normal(size=3)

X = rnd.poisson(10 * np.exp(X_org))
y = np.dot(X_org, w)
print(X[:10, 0])
# 카운트 데이터 일반적으로 포아송분포 
[ 56  81  25  20  27  18  12  21 109   7]

print("특성 출현 횟수:\n{}".format(np.bincount(X[:, 0].astype('int'))))
특성 출현 횟수:
[28 38 68 48 61 59 45 56 37 40 35 34 36 26 23 26 27 21 23 23 18 21 10  9 17
  9  7 14 12  7  3  8  4  5  5  3  4  2  4  1  1  3  2  5  3  8  2  5  2  1
  2  3  3  2  2  3  3  0  1  2  1  0  0  3  1  0  0  0  1  3  0  1  0  2  0
  1  1  0  0  0  0  1  0  0  2  2  0  1  1  0  0  0  0  1  1  0  0  0  0  0
  0  0  1  0  0  0  0  0  1  1  0  0  1  0  0  0  0  0  0  0  1  0  0  0  0
  1  0  0  0  0  0  0  0  0  0  0  0  0  0  0  1]
plt.xlim(0, 160)
plt.ylim(0, 70)
bins = np.bincount(X[:, 0])
plt.bar(range(len(bins)), bins, color='grey')
plt.ylabel("출현 횟수")
plt.xlabel("값")

선형 모델 이런 데이터 잘 처리하지 못함

from sklearn.linear_model import Ridge

X_train, X_test, y_train, y_test = train_test_split(X, y, random_state=0)
score = Ridge().fit(X_train, y_train).score(X_test, y_test)
print("테스트 점수: {:.3f}".format(score))
테스트 점수: 0.622

log 스케일 변화가 도움이 많이 된다.



X_train_log = np.log(X_train + 1)
X_test_log = np.log(X_test + 1)
plt.hist(X_train_log[:, 0], bins=25, color='gray')
plt.ylabel("출현 횟수")
plt.xlabel("값")


score = Ridge().fit(X_train_log, y_train).score(X_test_log, y_test)
print("테스트 점수: {:.3f}".format(score))
테스트 점수: 0.875


데이터셋 + 모델의 조합 -> 최적의 변환 방법 찾기 -> 예술에 가까움


-> 이 예에서는 모든 특성이 같은 속성을 가지고 있다 -> 하지만 이런 경우 드뭄 -> 일부 특성만 변환하거나 모두 다르게 변환


-> 트리 모델에서는 불필요 / 선형 모델에서는 필수


가끔 회귀에서 y변수 -> 해주는 것도 좋음 -> 주문 횟수 같은 경우 -> log(y+1)를 사용하면 도움 됨


구간 분할 / 다항식/ 상호작용 주어진 상황에서 성능에 큰 영향 -> 선형 모델/ 나이브 베이즈 같은 경우


반면에 트리 기반 모델 -> 스스로 상호 작용 잘 찾음 / 변환 안해도 됨


svm . knn, 신경망 -> 구간 분할 /상호작용 /다항식 이득 그러나 선형모델이 가장 영향을 많이 받는다.




특성 자동 선택


새로운 특성 추가 -> 차원의 수 이상으로 증가 -> 모델 복잡 -> 과대 적합 가능성 UP


새로운 특성 추가/ 고차원 데이터 셋 -> 가장 유용한 특성만 선택, 나머지 무시해서 -> 특성의 수 주이기


-> 모델 간단 -> 일반화 성능 UP -> 하지만 어떻것이 좋은지 어떻게 알 수 있을까??


1. 일변량 통계(univariate statistics) 


2. 모델 기반 선택(model-based selection)


3. 반복적 선택(iterative selection)


모두 지도 학습 방법이므로 최적값 찾으려면 타깃값 필요


-> 그리고 훈련 세트/ 테스트 세트 나눈 다음 -> 훈련 데이터만 특성 선택에 사용




1. 일변량 통계(univariate statistics) 


개개의 특성과 타깃 사이에 중요한 통계적 관계가 있는지 계산


-> 깊게 관련되어 있는 것만 선택 


분류 - 분산 분석(anova)  == 데이터를 클래스 별로 나누어 평균을 비교하는 방법

F-통계값이 높으면 그 특성은 클래스별로 평균이 서로 다르다는 뜻


일변량 , 즉 각특성이 독립적으로 평가된다 


따라서 다른 특성과 깊게 연관된 특성은 선택 x -> 일변량 분석은 계산 매우 빠르고 평가를 위해 모델 만들 필요 X


한편으로 이 방식은 특성을 선택한 후 적용하려는 모델에 상관없이 사용


SCIKIT_LEARN 에서 일변량 분석 분류 : f_classif(기본값) /  회귀 : f_regression  선택


-> 테스트 하고 p-value 기초하여 특성을 제외하는 방식


높은 p-value 특성을 제외 할 수 있도록 임계값을 조정하는 매개변수를 사용한다


가장 간단 : SelectKBest 는 고정된 K개의 특성을 선택하고 -> SelectPercentile은 지정된 비율만큼 특성을 선택



sklearn.feature_selection - selectkbest 처럼 score_func 매개변수를 사용하여 지정


분류 문제 : f_classif 

에서는 클래스별 평균의 분산() 을 전체() 에서 

클래스별 평균 분산 ()  을 뺀 값으로 나누어 F-통게량을 계산 클래스가 K 개 / 샘플 N개



회귀 문제 : f_regression


  F- 통계값과  P-VALUE 계산 


p-values_ 속성에 f-통계값 이후의 오른쪽 꼬리 부분의 면적



p-values_ 값이 큰 특성은 클래스들의 평균과 비슷 -> 타깃에 미치는 영향이 적다고 판단 


SelectKBest SelectPercentile 선택한 기준 F-통계값이며 값이 클수록 평균의 분산의 비교적 크다는 것 


F-통계값 (scores_ 속석에 저장 된다)


from sklearn.datasets import load_breast_cancer
from sklearn.feature_selection import SelectPercentile, f_classif
from sklearn.model_selection import train_test_split

cancer = load_breast_cancer()

# 고정된 난수를 발생시킵니다
rng = np.random.RandomState(42)
noise = rng.normal(size=(len(cancer.data), 50))
# 데이터에 노이즈 특성을 추가합니다
# 처음 30개는 원본 특성이고 다음 50개는 노이즈입니다
X_w_noise = np.hstack([cancer.data, noise])

X_train, X_test, y_train, y_test = train_test_split(
    X_w_noise, cancer.target, random_state=0, test_size=.5)
# f_classif(기본값)와 SelectPercentile을 사용하여 특성의 50%를 선택합니다
select = SelectPercentile(score_func=f_classif, percentile=50)
select.fit(X_train, y_train)
# 훈련 세트에 적용합니다
X_train_selected = select.transform(X_train)

print("X_train.shape: {}".format(X_train.shape))
print("X_train_selected.shape: {}".format(X_train_selected.shape))
X_train.shape: (284, 80)
X_train_selected.shape: (284, 40)

mask = select.get_support() # 선택된 특성을 boolen 값 print(mask) # True는 검은색, False는 흰색으로 마스킹합니다 plt.matshow(mask.reshape(1, -1), cmap='gray_r') plt.xlabel("특성 번호") plt.yticks([0])

[ True  True  True  True  True  True  True  True  True False  True False
  True  True  True  True  True  True False False  True  True  True  True
  True  True  True  True  True  True False False False  True False  True
 False False  True False False False False  True False False  True False
 False  True False  True False False False False False False  True False
  True False False False False  True False  True False False False False
  True  True False  True False False False False]


마스킹된 그래프 -> 대부분 원본 특성 / 노이즈 특성이 거의 모두 제거


-> 그러나 원본 특성이 완벽하게 복원된 것은 아니다 

-> 전체와 선택된 특성 비교

from sklearn.linear_model import LogisticRegression

# 테스트 데이터 변환
X_test_selected = select.transform(X_test)

lr = LogisticRegression()
lr.fit(X_train, y_train)
print("전체 특성을 사용한 점수: {:.3f}".format(lr.score(X_test, y_test)))
lr.fit(X_train_selected, y_train)
print("선택된 일부 특성을 사용한 점수: {:.3f}".format(
        lr.score(X_test_selected, y_test)))
전체 특성을 사용한 점수: 0.930
선택된 일부 특성을 사용한 점수: 0.940




2. 모델 기반 특성 선택


지도 학습 머신러닝 모델 사용 -> 특성의 중요도 평가 -> 가장 중요한 것만 선택


지도학습 모델은 최종적으로 사용할 학습 모델과 같은 필요는 없다.


특성 선택을 위한 모델은 각 특성의 중요도 측정 -> 순서 매길 수 있다 


-> 결정 트리와 이를 기반으로 한 모델은 각 특성의 중성의 중요도 담겨 있는 feature_importances_ 속성을 제공


선형 모델 계수의 절대값도  특성의 중요도를 재는데 사용 할 수 있습니다.


L1 규제를 사용한 선형 모델 -> 일부 특성의 계수만 학습 -> 이를 그 모델 자체를 위해 특성이 선택된다고 생각하지만


-> 다른 모델의 특성 선택은 한 번에 모든 특성을 고려하믈 (상호 작용 잡아낼 수 있다면) -> 상호작용 부분을 반영


-> SelectFromModel 에 구현


from sklearn.feature_selection import SelectFromModel
from sklearn.ensemble import RandomForestClassifier
select = SelectFromModel(
    RandomForestClassifier(n_estimators=100, random_state=42),
    threshold="median")

# SelectFromModel 은 (지도 학습 모델로 계산된) 중요도가 지정한 임게치보다 큰 모든 특성 선택

# 일변량 분석으로 선택한 특성과 결과를 비교하기 위해 절반 가량의 특성이 선택될수 있도록 -> 중간값을 임계치로 사용한다.

# 트리 100개 랜덤 포레스트 분류 -> 특성 중요도 계산 -> 일변량 분석 보다는 강력한 모델

# L1 규제가 없는 모델을 사용하는 경우 -> SelectFromModel threshold 매개변수의 기본 값은 평균값을 나타내는 "mean" 이다.

# 1.2*mean . 1.3*mean 가능



select.fit(X_train, y_train)
X_train_l1 = select.transform(X_train)
print("X_train.shape: {}".format(X_train.shape))
print("X_train_l1.shape: {}".format(X_train_l1.shape))
X_train.shape: (284, 80)
X_train_l1.shape: (284, 40)
mask = select.get_support()
# True는 검은색, False는 흰색으로 마스킹합니다
plt.matshow(mask.reshape(1, -1), cmap='gray_r')
plt.xlabel("특성 번호")
plt.yticks([0])


이번에는 두 개를 제외한 모든 원본 특성이 선택 되었습니다. -> 특성을 40개 선택 -> 일부 노이즈도 선택 


X_test_l1 = select.transform(X_test)
score = LogisticRegression().fit(X_train_l1, y_train).score(X_test_l1, y_test)
print("Test score: {:.3f}".format(score))
Test score: 0.951
특성 선택이 더 잘 되어 있으므로 -> 성능 조금 향상




3. 반복적 특성 선택


일변량 분석 -> 모델 사용 x 


모델기반 선택 -> 하나의 모델 사용해 특성 선택


반복적 특성 선택 -> 특성의 수가 각기 다른 일련의 모델


기본적으로 2가지 방법 


1. 특성을 하나도 선택하지 않은 상태로 시작해 -> 어떤 종료 조건을 도달 할때까지 하나씩 추가하는 방법


2. 모든 특성을 가지고 시작해 -> 어떤 종료 조건이 될 때까지 하나씩 추가하는 방법


모델이 만들어지기 때문에 -> 계산 비용이 훨씬 많이 듬


재귀적 특성 제거가 이런 방법의 하나


이 방법은 모든 특성으로 시작해 모델 만들고 가장 낮은 특성부터 제거 


-> 그런 다음 제거한 특성 빼고 나머지 특성 전체로 새로운 모델 -> 미리 정의한 특성개수 남을 때 까지 계속


-> 특성의 중요도를 결정하는 방법 제공

from sklearn.feature_selection import RFE select = RFE(RandomForestClassifier(n_estimators=100, random_state=42), n_features_to_select=40) # 40번 실행 select.fit(X_train, y_train) # 선택된 특성을 표시합니다 mask = select.get_support() plt.matshow(mask.reshape(1, -1), cmap='gray_r') plt.xlabel("특성 번호") plt.yticks([0])

X_train_rfe = select.transform(X_train)
X_test_rfe = select.transform(X_test)

score = LogisticRegression().fit(X_train_rfe, y_train).score(X_test_rfe, y_test)
print("테스트 점수: {:.3f}".format(score))
테스트 점수: 0.951

# 여전히 특성 한개를 놓쳤습니다.

print("테스트 점수: {:.3f}".format(select.score(X_test, y_test)))


테스트 점수: 0.951

RFE 안에 있는 랜덤 포레스트의 성능이 로지스틱 회귀의 성능과 같다.


다른 말로 특성 선택이 제대로 되면 -> 선형 모델의 성능은 랜덤 포레스트와 견줄만 한다.


어떤 입력값 넣을지 확신 X -> 특성 자동 선택이 도움이 된다.


->예측 속도를 높이거나 해석하기 더 쉬운 모델을 만드는데 필요한 만큼 특성의 수를 줄이는데 효과적


_> 대부분 실전에서는 큰 향상 끌어내진 못한다.


그래도 특정 선택은 중요한 도구이다.


# 반복적 선택 방법 1.전진 선택법 2. 후진 선택법 3. 교차 선택법 scikit-learn 제공 x


하지면 회귀 모델 score R^2 값을 어들 수 잇으므로 직접 구현 할 수있다.


전진 선택법 -> 하나씩 교대로 포함 -> 가장 높은 R^2 특성 추가해 나가고


후진 선택법은 -> 하나씩 빼면서 가장 낮은 R^2 되는 특성 제거하면 된다.




전문가 지식 활용


특성 공학 - 특정한 어플리케이션을 위해 전문가적 지식을 사용할 수 있는 중요한 영역


많은 경우 / 머신러닝의 목적 - 전문가가 설계하는 규칙을 만들지 않기 위해서지만


그 분야의 전문 지식이 무시된다는 뜻은 아님 / 종종 분야 전문가는 초기 데이터에서 더 유용한 선택 도움줌


여행사 / 항공료 예측 - 중요한 몇가지는 학습 X


EX) 휴가 성수기 공휴일 근처 -> 항공료 비싸다 일부 공휴일은 고정되어 잇어서 날짜로부터 학습가능

음력 공휴일도 있고 기관이 지정합니다. 


-> 이런 이벤트들은 데이터가 날짜를 사용해서만 기록되어서는 학습 X 


그러나 공휴일과 방학 전후에 비행 스케줄이 기로된 특성 추가하는 일 어렵지 않음 


-> 사전 지식이 특성으로 추가 -> 머신러닝 알고리즘에 도움이 된다

citibike = mglearn.datasets.load_citibike()
print("시티 바이크 데이터:\n{}".format(citibike.head()))
시티 바이크 데이터:
starttime
2015-08-01 00:00:00     3.0
2015-08-01 03:00:00     0.0
2015-08-01 06:00:00     9.0
2015-08-01 09:00:00    41.0
2015-08-01 12:00:00    39.0
Freq: 3H, Name: one, dtype: float64
plt.figure(figsize=(10, 3))
xticks = pd.date_range(start=citibike.index.min(), end=citibike.index.max(),
                       freq='D')
week = ["일", "월", "화","수", "목", "금", "토"]
xticks_name = [week[int(w)]+d for w, d in zip(xticks.strftime("%w"), 
                                              xticks.strftime(" %m-%d"))]
plt.xticks(xticks.astype(int), xticks_name, rotation=90, ha="left")
plt.plot(citibike, linewidth=1)
plt.xlabel("날짜")
plt.ylabel("대여횟수")



데이터 24시간 간격 낮과 밤 구분 가능 / 주중과 주말 패턴 꽤 차이 


시계열 가능 

어떤 날 까지는 훈련용 그 이후에는 테스트 데이터

# 타깃값 추출 (대여 횟수)
y = citibike.values
# POSIX 시간을 10**9로 나누어 변경
X = citibike.index.astype("int64").values.reshape(-1, 1) // 10**9
# 처음 184개 데이터 포인트를 훈련 세트로 사용하고 나머지는 테스트 세트로 사용합니다
n_train = 184

# 주어진 특성을 사용하여 평가하고 그래프를 만듭니다
def eval_on_features(features, target, regressor):
    # 훈련 세트와 테스트 세트로 나눕니다
    X_train, X_test = features[:n_train], features[n_train:]
    # 타깃값도 나눕니다
    y_train, y_test = target[:n_train], target[n_train:]
    regressor.fit(X_train, y_train)
    print("테스트 세트 R^2: {:.2f}".format(regressor.score(X_test, y_test)))
    y_pred = regressor.predict(X_test)
    y_pred_train = regressor.predict(X_train)
    plt.figure(figsize=(10, 3))

    plt.xticks(range(0, len(X), 8), xticks_name, rotation=90, ha="left")

    plt.plot(range(n_train), y_train, label="훈련")
    plt.plot(range(n_train, len(y_test) + n_train), y_test, '-', label="테스트")
    plt.plot(range(n_train), y_pred_train, '--', label="훈련 예측")

    plt.plot(range(n_train, len(y_test) + n_train), y_pred, '--',
             label="테스트 예측")
    plt.legend(loc=(1.01, 0))
    plt.xlabel("날짜")
    plt.ylabel("대여횟수")
regressor = RandomForestRegressor(n_estimators=100, random_state=0)
eval_on_features(X, y, regressor)
테스트 세트 R^2: -0.04

-> 거의 아무것도 학습 X 문제점 -> 랜덤포레스트와 특성 사이의 조합

테스트 세트에 있는 POSIX 시간 특성의 값은 훈련세트에 있는 특성 값의 밖에 있습니다


즉 - 테스트 세트에 있는 데이터 포인트는 훈련 세트에 있는 모든 데이터보다 뒤의 시간입니다.


랜덤포레스트는 훈련세트 있는 특성의 범위 밖으로 외삽(EXPRAPOLATION)하는 능력이 없습니다.


결국 이 모델은 테스트 세트와 가장 가까이 있는 마지막 훈련 세트 데이터의 타킷값을 에측으로 사용하게 된다.

eval_on_features(X, y, regressor) - POSIX 시간을 가진 특성 X 와 랜덤포레스트 회귀 모델을 eval_on_features(X, y, regressor) 함수에 전달



더 전문가 지식 들어가서


-> 훈련 데이터의 대여 데이터를 시간과 요일 이라는 두 요소가 중요한 것으로 보인다.


POSIX 학습 되지 않아서 -> 특성 제외


시간만 사용해보기


-> 주간 패턴 예측 X

X_hour = citibike.index.hour.values.reshape(-1, 1)
eval_on_features(X_hour, y, regressor)

테스트 세트 R^2: 0.60



요일 정보도 추가하기


-> 주기적인 패턴 정보 찾는다 학습 시킨거  (요일별 / 시간당 평균 대여 횟수)


-> 굳이 복잡한 랜덤 말고  더 간단한 선형


랜덤 포레스트 회귀로 만든 예측은 여러트리가 에측한 값들의 평균 -> 그래서 8.24~8.31일의 예측값은 동일하다.


X_hour_week = np.hstack([citibike.index.dayofweek.values.reshape(-1, 1),
                         citibike.index.hour.values.reshape(-1, 1)])
eval_on_features(X_hour_week, y, regressor)


from sklearn.linear_model import LinearRegression
eval_on_features(X_hour_week, y, LinearRegression())

테스트 세트 R^2: 0.13



성능 나쁘고 주기 패턴 이상 -> 요일과 시간이 정수로 인코딩 -> 연속형 변수로 해석 됨


-> 선형 모델은 시간을 선형 함술모나 학습해서 -> 하루에서 시간이 흐를 수록 대여수가 늘어나게 학습되 어있음


-> 하지만 실제 패턴 이보다 복잡 -> 이 패턴 잡기 위해 AUTOENCODER 사용 


-> 정수형을 범주형 변수로 해석

enc = OneHotEncoder()

X_hour_week_onehot = enc.fit_transform(X_hour_week).toarray()
eval_on_features(X_hour_week_onehot, y, Ridge())
테스트 세트 R^2: 0.62


연속형 특성일 때보다 훨씬 좋아짐 -> 선형 모델은 요일에 대해 하나의 계수를 학습하고 ->

시간에 대해서도 하나의 계수 학습 -> 시간 패턴이 모든 날에 걸쳐 공유된다는 뜻


-> 상호작용 특성을 사용하면 -> 시간과 요일 조합별 계수를 학습 가능


poly_transformer = PolynomialFeatures(degree=2, interaction_only=True,
                                      include_bias=False)
X_hour_week_onehot_poly = poly_transformer.fit_transform(X_hour_week_onehot)
lr = Ridge()
eval_on_features(X_hour_week_onehot_poly, y, lr)
테스트 세트 R^2: 0.85

이모델 가장 큰 장점 : 무엇이 학습됬는지 명확하다 

바로 각 날짜와 시간에 대해 하나의 계수를 학습 

랜덤 포레스트와는 달리 이 모델이 학습한 계수를 그래프로 나타낼수 잇다.


hour = ["%02d:00" % i for i in range(0, 24, 3)]
day = ["월", "화", "수", "목", "금", "토", "일"]
features =  day + hour

features_poly = poly_transformer.get_feature_names(features) # 모든 상호작용 특성에 이름을 달아줌

# 그리고 계수가 0이 아닌 것만 출력 features_nonzero = np.array(features_poly)[lr.coef_ != 0] coef_nonzero = lr.coef_[lr.coef_ != 0]

plt.figure(figsize=(15, 2))
plt.plot(coef_nonzero, 'o')
plt.xticks(np.arange(len(coef_nonzero)), features_nonzero, rotation=90)
plt.xlabel("특성 이름")
plt.ylabel("계수 크기")





요약 및 정리


범주형 변수 다루는 법 배움


ONE-HOT-ENCODING 범주형 변수처럼 -> 머신러닝 알고리즘에 적한 방식으로 표현하는 것이 아주 중요


그리고 새로운 특성을 만드는 것과 데이터 표현하는 것이 아주 중요함


그리고 새로운 특성을 만드는 것과 데이터 특성을 유도하기 위해 전문가 지식 활용하는 것 중요함


선형 모델 :


구간 분할 / 다항식과 상호작용 - 큰이득 가능


랜덤 포레스트, SVM (비선형) :


특성을 늘리지 않고도 복잡한 문제 학습 가능


실제로는 어떤 특성 사용하냐고 중요

728x90