반응형
- 참고 : 딥러닝을 이용한 자연어 처리 입문
- 서포트 벡터 머신(SVM)
- 매우 강력하고 선형이나 비선형 분류, 회귀, 이상치 탐색에도 사용할 수 있는 다목적 머신러닝 모델
- 특히 복잡한 분류 문제에 잘 들어맞으며 작거나 중간 크기의 데이터셋에 적합
선형 SVM 분류
- 두 class가 직선을 기준으로 잘 나눠져 있음
- 왼쪽 그래프는 세 개의 선형 분류기에서 만들어진 결정 경계
- 점선으로 나타낸 결정경계는 클래스를 적절하게 분류하고 있지 않음
- 오른쪽 그래프는 SVM 분류기의 결정경계
- 직선이 두 개의 클래스를 나누고 있고 제일 가까운 훈련 샘플로 부터 가능한 멀리 떨어져 있음
- 클래스 사이에 폭이 넓은 도로를 찾는 것과 같다고 하여 라지 마진 분류 라고 함
- 점선 경계에 위치한 샘플을 서포트 벡터, 서포트 벡터 밖의 샘플을 추가해도 결과 동일
- 왼쪽 그래프는 세 개의 선형 분류기에서 만들어진 결정 경계
SVM은 특성의 스케일에 민감함, 특성의 scale을 조절하면 결정 경계가 좋아짐
소프트 마진 분류
- 모든 샘플이 서포트 벡터 바깥쪽에 올바르게 분포 했다면 하드 마진 분류 라고 함
- 하드 마진 분류의 두가지 문제점
- 데이터가 선형적으로 구분될 수 있어야 잘 작동
- 이상치에 민감
- 하드 마진 분류의 두가지 문제점
- 위 그림에서 왼쪽 그래프에서는 하드 마진이 없고 오른쪽 그래프는 이상치 때문에 결정경계가 일반화 되기 어렵게 나타나 있습니다.
위와 같은 문제를 해결하기 위해 서포트 벡터 사이의 마진의 폭을 가능한 넓게 유지, 마진 오류 사이에 적절한 균형을 잡아야함 이를 소프트 마진 분류 라고 함
- sklearn 의 SVM 모델의 C 파라미터를 통해 조절이 가능, SVM이 과대적합이라면 C를 통해 규제를 추가할수 있음
- LinearSVC는 규제에 편향을 포함, 훈련 세트에서 평균을 빼서 중앙에 맞춰야함
- standardScaler를 적용해 해결
- loss를 hinge로 지정, 훈련 샘플보다 특성이 많지 않다면 성능을 높이기 위해 dual 매개변수 False 지정
비선형 SVM 분류
- 선형 SVM 분류기가 효율적이고 많은 경우에 잘 작동하지만, 선형적으로 분류할수 없는 경우가 있음
- 비선형 데이터셋을 다루는 한 가지 방법은 다항 특성과 같은 특성을 더 추가하는 것
-
- 왼쪽은 선형으로 특성 구분이 어렵지만 특성을 추가한 오른쪽은 선형적으로 구분 가능
다항식 커널
- 다항식 특성을 추가하는 것은 간단하고 모든 머신러닝 알고리즘에서 잘 작동
- 낮은 차수의 다항식은 매우 복잡한 데이터셋을 잘 표현하지 못함
- 매우 높은 차수는 다양한 특성이 있어서 모델이 느림
- SVM 커널트릭
- 실제로는 특성을 추가하지 않으면서 다항식 특성을 많이 추가한 것과 같은 결과를 얻는 법
from sklearn.svm import SVC poly_kernel_svm_clf = Pipeline([ ("scaler", StandardScaler()), ("svm_clf", SVC(kernel="poly", degree=3, coef0=1, C=5)) ]) poly_kernel_svm_clf.fit(X, y)
- 3차 다항식 커널을 이용해 SVM 훈련하는 코드
- 모델이 과대적합이면 차수를 줄이고 모델이 과소적합이면 차수를 늘려야함
- coef0은 모델이 높은 차수와 낮은 차수에 영향을 조절하는 것
-
- 왼쪽은 3차원 다항식 커널, 오른쪽은 10차원 다항식 커널
유사도 특성
- 비선형 특성을 다루는 기법중에 샘플이 특정 랜드마크와 얼마나 닮았는지 측정하는 유사도 함수로 계산한 특성을 추가하는 방법이 있음
- 가우시안 RBF(방사 기저 함수)
-
- 위 함수는 0 ~ 1 까지 변화하는 종모양(랜드마크에서 멀리 떨어지면 0, 가까우면 1)
- 랜드마크를 설정하는 간단한 방법은 모든 샘플위에 랜드마크 설정
- 차원 매우 커지고 선형적 구분될 가능성이 높아짐
- 훈련 세트에 있는 n개 특성을 가진 m개의 샘플이 m개의 특성을 가진 m개의 샘플로 변환된다는 단점
가우시안 RBF 커널
- 커널트릭이 유사도 특성을 많이 추가하는 것과 같은 비슷한 결과를 얻을 수 있음
- 가우시안 RBF 커널을 사용한 SVC 모델 예제
-
rbf_kernel_svm_clf = Pipeline([ ("scaler", StandardScaler()), ("svm_clf", SVC(kernel="rbf", gamma=5, C=0.001)) ]) rbf_kernel_svm_clf.fit(X, y)
-
- gamma, C를 바꾸면서 훈련시킨 모델 결과 이미지
- gamma 증가시키면 종 모양 그래프가 좁아져 각 샘플의 영향 범위가 작아짐
- 결정 경계가 조금 더 불규칙해지고 각 샘플을 따라 휘어짐
- gamma 값이 작으면 종 모양 그래프 생성, 샘플이 넓은 범위에 걸쳐 영향을 줘서 결정 경계가 더 부드러워 짐
-
결국 하이퍼파라미터 r 이 규제의 역할을 수행
모델이 과대적합일 경우에는 감소, 과소적합일 경우는 증가
SVM 회귀
- 회귀로 SVM을 하는 방법은 분류와 반대로 적용
- 일정한 마진 오류 안에서 두 클래스 간의 폭이 가능한 최대가 되도록 하는 대신, SVM 회귀는 제한된 마진 오류 안에서 도로 안에 가능한 많은 샘플이 들어가도록 학습 (앱실론으로 폭 조절)
-
- 앱실론의 차이를 보여주는 그래프
- 마진 안에서는 훈련 샘플이 추가되어도 모델의 예측에 영향이 없음, 앱실론에 민감하지 않음
- LinearSVR 예시
-
from sklearn.svm import LinearSVR svm_reg = LinearSVR(epsilon=1.5, random_state=42) svm_reg.fit(X, y)
-
- 비선형 회귀는 kernel SVM, 선형은 Linear SVM
- 왼쪽은 규제가 없고(C가 큼), 오른쪽은 규제가 큼(C가 작음)
-
SVM 이론
결정 함수와 예측
- 선형 SVM 분류기 모델은 단순히 결정 함수를 계산해서 새로운 샘플 x의 클래스를 예측
- 결과값이 0보다 크면 예측된 클래스는 양성클래스, 반대면 음성클래스
- 선형 SVM 분류기 예측 식
- SVM 결정 함수 이미지
-
- 특성이 두 개인 데이터셋이기 때문에 2차원 평면
- 결정 경계는 결정 함수의 값이 0인 점들로 이루어짐, 평면의 교차점이고 직선
- 점선은 결정 함수의 값이 1 or -1인 점들을 의미, 선분은 결정 경계에 나란하고 일정한 거리만큼 떨어져서 마진을 형성
- 선형 SVM 분류기를 훈련한다는 것은 마진 오류를 하나도 발생하지 않거나(하드 마진) 제한적인 마진 오류를 가지면서(소프트 마진) 가능한 마진을 크게하는 가중치 벡터(w) 와 편향(b)를 찾는 것
-
목적 함수
- 결정 함수의 기울기는 가중치 벡터의 노름인 과 같음
- 기울기를 2로 나누는 것은 결국 마진에 2를 곱하는 것과 같은 효과
- 가중치 벡터 w가 작을수록 마진은 커짐
- 마진을 크게 하기 위해서 를 최소화
- 결정 함수가 모든 양성 훈련 샘플에서는 1보다 커야하고 음성 훈련 샘플에서는 -1보다 작아야 함
- 위에 조건을 통해 음성 샘플일 때 t = -1, 양성 샘플일 때 t=1로 정의하면 모든 샘플에서 로 표현이 가능
- 하드 마진 선형 SVM 분류기의 목적함수를 제약이 있는 최적화 문제로 표현 가능
- 하드 마진 선형 SVM 분류기의 목적 함수
- 하드 마진 선형 SVM 분류기의 목적 함수
- 소프트 마진 분류기의 목적 함수를 구성하려면 각 샘플에 대해 슬랙 변수을 도입해야함
- 슬랙 변수는 i번째 샘플이 얼마나 마진을 위반할지 정하는 함수
- 마진 오류를 최소화 하기 위해 가능한 슬랙 변수의 값을 작게 만드는 것과 마진을 크게 하기 위해 를 가능한 작게 만드는 것
- 파라미터 C는 두 목표 사이의 트레이드오프
- 소프트 마진 선형 SVM 분류기의 목적 함수
커널 SVM
- 2차원 데이터셋에 2차 다항식 변환을 적용하고 선형 SVM 분류기를 변환된 이 훈련 세트에 적용한다면 우리가 적용하고자 하는 2차 다항식 매핑 함수는 아래와 같음
- 변환된 벡터는 2차원이 아닌 3차원
- 두 개의 2차원 벡터 a와b에 2차 다항식 매핑을 적용한 다음 변환된 벡터로 점곱을 하면
결과 적으로 변환된 벡터의 점곱이 원래 벡터의 점곱의 제곱과 같음
결과값은 훈련샘플을 어렵게 변환하여 적용하는 것과 같지만 계산량 측면에서 효율적 <- 커널 트릭
연습 문제
1. 서포터 벡터 머신의 근본 아이디어는 무엇인가요?
근본적인 아이디어는 클래스 사이에 가능한 한 가장 넓은 '도로' 를 내는 것이다. 두 클래스를 구분하는 결정 경계와 샘플 사이의 마진을 가능한 크게 내겠다는 것이다.
2. 서포트 벡터가 무엇인가요?
SVM이 훈련된 후에 경계를 포함해 도로에 놓인 어떤 샘플 (속해있다)결정 경계는 서포트 벡터에 의해 결정이 된다. 서포트 벡터가 아닌, 어떤 샘플도 영향을 주지 못한다. 예측할 때에는 전체 훈련 세트가 아니라 서포트 벡터만 관여됨.
3. SVM을 사용할 때 입력값의 스케일이 왜 중요한가요?
가장 큰 마진을 내는 결정 경계를 만드는 것이 목표인데, 훈련세트의 스케일이 맞지 않으면 크기가 작은 특성을 무시하는 경향이 있다.
4. SVM 분류기가 샘플을 분류할 때 신뢰도 점수와 확률을 출력할 수 있나요?
테스트 샘플과 결정 경계 사이의 거리를 출력할 수 있으므로 이를 신뢰도 점수로 사용할 수 있다. 하지만 점수를 바로 확률의 추정값으로는 변환하지 못한다. sklearn에서 SVM모델을 만들 때 probability = True 로 설정하면 훈련이 끝난 후 SVM의 점수에 로지스틱 회귀를 훈련시켜 확률을 계산한다. 이 설정은 predict_proba() 와 predict_log_proba() 메서드를 추가 시킨다.
5. 수백만 개의 샘플과 수백 개의 특성을 가진 훈련 세트에 SVM 모델을 훈련시키려면 원 문제와 쌍대 문제 중 어떤 것을 사용해야 하나요?
커널 SVM은 쌍대 형식만 사용 가능하기에 이 문제는 '선형 SVM'에만 해당한다. 원 문제의 계산 복잡도는 훈련 샘플 수(m)에 비례하지만, 쌍대 형식의 계산 복잡도는 m2 ~ m3 사이의 값에 비례한다. 그러므로 수백 만개의 샘플과 특성이 있다면 쌍대는 너무 느려서 원 문제를 사용해야 한다.
6. RBF 커널을 사용해 SVM 분류기를 훈련시켰더니 훈련 세트에 과소적합된 것 같습니다. γ(gamma)를 증가시켜야 할까, 감소시켜야 할까? C의 경우는 어떠한가?
훈련 세트에 과소 적합 되었다는 것은 규제가 너무 큰 것일 수도 있다. 따라서 규제를 줄이기 위해 γ와 C를 둘 다 증가시키는 것이 모델에 규제를 주어 과소적합을 해결할 수 있다.
7. 이미 만들어진 QP 알고리즘 라이브러리를 사용해 소프트 마진 선형 SVM 분류기를 학습 시키려면 QP 매개변수 (H,f,A,b)를 어떻게 저장해야 하나요?
하드 마진 문제에 관한 QP 파라미터를 H' , f' , A' , b' 라고 하자.
소프트 마진 문제의 QP 파라미터 m개의 추가적인 파라미터(np=n+1+m)와 m개의 추가적인 제약(nc=2m)을 가진다.
- 정리하면 H는 H'의 오른쪽에 0으로 채워진 m개의 열이 있고 아래로 0으로 채워진 m개의 열이 있는 행렬
- f는 f'에 하이퍼파라미터 C와 동일한 값의 원소 m개가 추가된 벡터입니다.
- b는 b'에 값이 0인 원소 m개가 추가된 벡터입니다.
- A는 A'의 오른쪽에 m x n 단위 행렬이 추가되고 그 아래에이 추가되며 나머지는 0으로 채워진 행렬입니다.
8. 선형적으로 분리되는 데이터셋에 LinearSVC
를 훈련시켜보세요. 그런 다음 같은 데이터셋에 SVC
와SGDClassifier
를 적용해보세요. 거의 비슷한 모델이 만들어지는지 확인해보세요.
- Iris 데이터셋을 사용하겠습니다. Iris Setosa와 Iris Versicolor 클래스는 선형적으로 구분이 가능
from sklearn import datasets iris = datasets.load_iris() X = iris["data"][:, (2, 3)] # 꽃잎 길이, 꽃잎 너비 y = iris["target"] setosa_or_versicolor = (y == 0) | (y == 1) X = X[setosa_or_versicolor] y = y[setosa_or_versicolor]
from sklearn.svm import SVC, LinearSVC from sklearn.linear_model import SGDClassifier from sklearn.preprocessing import StandardScaler C = 5 alpha = 1 / (C * len(X)) lin_clf = LinearSVC(loss="hinge", C=C, random_state=42) svm_clf = SVC(kernel="linear", C=C) sgd_clf = SGDClassifier(loss="hinge", learning_rate="constant", eta0=0.001, alpha=alpha, max_iter=1000, tol=1e-3, random_state=42) scaler = StandardScaler() X_scaled = scaler.fit_transform(X) lin_clf.fit(X_scaled, y) svm_clf.fit(X_scaled, y) sgd_clf.fit(X_scaled, y) print("LinearSVC: ", lin_clf.intercept_, lin_clf.coef_) print("SVC: ", svm_clf.intercept_, svm_clf.coef_) print("SGDClassifier(alpha={:.5f}):".format(sgd_clf.alpha), sgd_clf.intercept_, sgd_clf.coef_) >>LinearSVC: [0.28475098] [[1.05364854 1.09903804]] SVC: [0.31896852] [[1.1203284 1.02625193]] SGDClassifier(alpha=0.00200): [0.117] [[0.77714169 0.72981762]]
- 모델 3개 결정 경계 시각화
# 각 결정 경계의 기울기와 편향을 계산합니다 w1 = -lin_clf.coef_[0, 0]/lin_clf.coef_[0, 1] b1 = -lin_clf.intercept_[0]/lin_clf.coef_[0, 1] w2 = -svm_clf.coef_[0, 0]/svm_clf.coef_[0, 1] b2 = -svm_clf.intercept_[0]/svm_clf.coef_[0, 1] w3 = -sgd_clf.coef_[0, 0]/sgd_clf.coef_[0, 1] b3 = -sgd_clf.intercept_[0]/sgd_clf.coef_[0, 1] # 결정 경계를 원본 스케일로 변환합니다 line1 = scaler.inverse_transform([[-10, -10 * w1 + b1], [10, 10 * w1 + b1]]) line2 = scaler.inverse_transform([[-10, -10 * w2 + b2], [10, 10 * w2 + b2]]) line3 = scaler.inverse_transform([[-10, -10 * w3 + b3], [10, 10 * w3 + b3]]) # 세 개의 결정 경계를 모두 그립니다 plt.figure(figsize=(11, 4)) plt.plot(line1[:, 0], line1[:, 1], "k:", label="LinearSVC") plt.plot(line2[:, 0], line2[:, 1], "b--", linewidth=2, label="SVC") plt.plot(line3[:, 0], line3[:, 1], "r-", label="SGDClassifier") plt.plot(X[:, 0][y==1], X[:, 1][y==1], "bs") # label="Iris versicolor" plt.plot(X[:, 0][y==0], X[:, 1][y==0], "yo") # label="Iris setosa" plt.xlabel("Petal length", fontsize=14) plt.ylabel("Petal width", fontsize=14) plt.legend(loc="upper center", fontsize=14) plt.axis([0, 5.5, 0, 2]) plt.show()
9. MNIST 데이터셋에 SVM 분류기를 훈련시켜보세요. SVM 분류기는 이진 분류기라서 OvA 전략을 사용해 10개의 숫자를 분류해야 합니다. 처리 속도를 높이기 위해 작은 검증 세트로 하이퍼파라미터를 조정하는 것이 좋습니다. 어느 정도까지 정확도를 높일 수 있나요?
from sklearn.datasets import fetch_openml mnist = fetch_openml('mnist_784', version=1, cache=True) X = mnist["data"] y = mnist["target"].astype(np.uint8) X_train = X[:60000] y_train = y[:60000] X_test = X[60000:] y_test = y[60000:]
- 선형 SVM 분류기 <- 자동으로 OvA 또는 OVR 사용
lin_clf = LinearSVC(random_state=42) lin_clf.fit(X_train, y_train) from sklearn.metrics import accuracy_score y_pred = lin_clf.predict(X_train) accuracy_score(y_train, y_pred) >> 0.8348666666666666
- 스케일 조정
scaler = StandardScaler() X_train_scaled = scaler.fit_transform(X_train.astype(np.float32)) X_test_scaled = scaler.transform(X_test.astype(np.float32)) lin_clf = LinearSVC(random_state=42) lin_clf.fit(X_train_scaled, y_train) y_pred = lin_clf.predict(X_train_scaled) accuracy_score(y_train, y_pred) >> 0.9214
- RBF 커널 적용한 SVC 사용
svm_clf = SVC(gamma="scale") svm_clf.fit(X_train_scaled[:10000], y_train[:10000]) y_pred = svm_clf.predict(X_train_scaled) accuracy_score(y_train, y_pred) >> 0.9455333333333333
- 교차 검증을 사용한 랜덤 서치로 하이퍼파라미터 튜닝
from sklearn.model_selection import RandomizedSearchCV from scipy.stats import reciprocal, uniform param_distributions = {"gamma": reciprocal(0.001, 0.1), "C": uniform(1, 10)} rnd_search_cv = RandomizedSearchCV(svm_clf, param_distributions, n_iter=10, verbose=2, cv=3) rnd_search_cv.fit(X_train_scaled[:1000], y_train[:1000]) rnd_search_cv.best_estimator_ >> SVC(C=3.8786881587000437, gamma=0.0017076019229344522) rnd_search_cv.best_score_ >> 0.8599947252641863
반응형
'Study > Self Education' 카테고리의 다른 글
핸즈온 머신러닝 - 7 (0) | 2024.06.18 |
---|---|
핸즈온 머신러닝 - 6 (0) | 2024.06.18 |
핸즈온 머신러닝 - 4 (2) | 2024.06.17 |
핸즈온 머신러닝 - 3 (1) | 2024.06.15 |
핸즈온 머신러닝 - 2 (0) | 2024.06.13 |