비지도_PCA, NMF, 매니폴드 학습(T-SNE)

2018. 1. 7. 23:04ML(머신러닝)/BASIC

728x90

도움이 되셨다면, 광고 한번만 눌러주세요.  블로그 관리에 큰 힘이 됩니다 ^^

차원축소, 특성 추출, 매니폴드 학습

변환하는 이유 -> 시각화하거나 , 데이터를 압축, 추가적인 처리를 위해서

주성분 분석, 특성 추출에 널리 사용되는 비음수 행렬 분해(NMF)

2차원 산점도를 이용해 시각화 용도로 많이 사용하는 t-SNE

## 주성분 분석(PCA)

통계적으로 상관관계가 없도록 데이터 셋을 회전 시키는 기술

-> 회전한 뒤에 데이터를 설명하는데 얼마냐 중요하냐에 따라 새로운 특성 중 일부만 선택됩니다.

 

첫번째 사진 "성분1" 분산이 가장 큰 방향 = 데이터에서 가장 많은 정보를 담고 있는 방향

                                        =특성들의 상관관계가 가장 큰 방향

그다음으로 첫번째 방향과 직각인 방향중에서 가장 많은 정보를 담는 방향 찾기

 

2차원에서는 직각방향 하나 뿐이지만 다차원에서는 많은 직각 방향 존재

사실 화살표의 머리와 꼬리는 큰 의미 없다.

즉 이런과정을 거쳐 찾은 방향을 데이터에 있는 주된 분산의 방향 = 주성분

일반적으로 원본 특성 개수만큼의 주성분이 있다.

 

두번째 그래프는 같은 데이터지만 주성분 1과 2 기준으로 회전시킨 것

회전하기 전에 평균을 빼서 중심을 원점에 맞춘 모습

 

PCA에 의해 회전된 두축은 연관되어 있지 않으므로 변환된 데이터의 상관관계 행렬이 대각선 방향 제외하고 "0"

 

PCA 는 주성분의 일부만 남긴느 차원 축소 용도로 사용 가능 

3번째 그림 - 첫번째 주성분만 유지 -> 2차원 => 1차원 

그러나 단순히 원복 틍성 하나만 남긴 것은 아니다 -> 가장 유용한 방향 찾아서 방향의 성분 즉) 첫번째 주성분 유지

 

마지막으로 데이터에 다시 평균을 + 반대로 회전 -> 포인터들은 원래 특성 공간에 놓여 있지만 

-> 첫번째 주성분 유저만 담고 있다 

(이 변환은 데이터에서 노이즈제거나 주성분에서 유지되는 정보를 시각화 할 때 사용)

 

PCA -고차원 데이터 시각화하기 

-> 히스토그램을 사용하여 -> 겹쳐지지 않은 그래프 찾기 

-> 단점 : 특성 간의 상호작용이나 클래스와 어떤 관계있는지 전혀 모름

from sklearn.datasets import load_breast_cancer
cancer = load_breast_cancer()

scaler = StandardScaler()
scaler.fit(cancer.data)
X_scaled = scaler.transform(cancer.data)
from sklearn.decomposition import PCA
# 데이터의 처음 두 개 주성분만 유지시킵니다
pca = PCA(n_components=2)
# 유방암 데이터로 PCA 모델을 만듭니다
pca.fit(X_scaled)

# 처음 두 개의 주성분을 사용해 데이터를 변환합니다
X_pca = pca.transform(X_scaled)
print("원본 데이터 형태: {}".format(str(X_scaled.shape)))
print("축소된 데이터 형태: {}".format(str(X_pca.shape)))
 
원본 데이터 형태: (569, 30)
축소된 데이터 형태: (569, 2)
# 클래스를 색깔로 구분하여 처음 두 개의 주성분을 그래프로 나타냅니다.
plt.figure(figsize=(8, 8))
mglearn.discrete_scatter(X_pca[:, 0], X_pca[:, 1], cancer.target)
plt.legend(["악성", "양성"], loc="best")
plt.gca().set_aspect("equal")
plt.xlabel("첫 번째 주성분")
plt.ylabel("두 번째 주성분")

 

 

PCA 비지도 학습이므로 -> 회전축을 찾을 때 -> 어떤 클래스 정보도 사용 X 

단순히 상관관계만 고려한다. -> 클래스 정보를 이용하여 포인트 모양 구분

-> 두 클래스가 잘 구부되는 것을 확인 

 

PCA 단점 : 그래프의 두 축을 해석하기가 쉽지 않다는 점 

 

-> 주성분은 원본 데이터에 있는 어떤 방향에 대응하는 여러 특성이 조합된 형태

PCA 객체가 학습 될 때 COMPONENTS_ 속성에 주성분이 저장됩니다.

 

print("PCA 주성분 형태: {}".format(pca.components_.shape))
 
PCA 주성분 형태: (2, 30)

COMPONENTS_ 의 각행은  주성분 하나씩을 나타내며 중요도에 따라 정렬되어 있음

(맨처음 주성분이 가장 위에 나타남) 

 

열은 원본 데이터 특성에 대응하는 값

 

히트맵으로 시각화

 

 

# 첫번째 주성분의 모든 특서은 부호가 같습니다 (모두 양수, 화살표 방향은 의미 X) 

 

이 말은 모든 특성 사이에 공통의 상호관계가 있다는 뜻

 

따라서 한 특서의 값이 커지면 -> 다른 것도 커진다 

 

두번째 주성분은 부호가 섞여 있고 -> 모든 특성이 섞여 있기 때문에 축이 가지는 의미 설명하기가 쉽지 않다.

 

고유얼굴(Eigenface) 특성 추출

 

PCA : 특성 추출에 이용

 

원본데이터 표현 보다 -> 분석하기에 더 적합한 표현 찾기(출발점)

 

이미지 다루는 어플리케이션 -> 특성 추출 도움 됨

 

PCA -> LFW(Labeled faces in the wild) -> 이용하여 특성 추출

 

from sklearn.datasets import fetch_lfw_people
people = fetch_lfw_people(min_faces_per_person=20, resize=0.7)
image_shape = people.images[0].shape

fig, axes = plt.subplots(2, 5, figsize=(15, 8),
                         subplot_kw={'xticks': (), 'yticks': ()})
for target, image, ax in zip(people.target, people.images, axes.ravel()):
    ax.imshow(image)
    ax.set_title(people.target_names[target])

 

## 이 데이터에는 자체에 편중이 있기 때문에 사람마도 50개의 이미지만 선택하기

-> 이유 : 이렇게 하지 않으면 조지부시 이미지에 치우친 특성이 추출 된다)

 

 

mask = np.zeros(people.target.shape, dtype=np.bool)
for target in np.unique(people.target):
    mask[np.where(people.target == target)[0][:50]] = 1
    
X_people = people.data[mask]
y_people = people.target[mask]

# 0~255 사이의 흑백 이미지의 픽셀 값을 0~1 사이로 스케일 조정합니다.
# (옮긴이) MinMaxScaler를 적용하는 것과 거의 동일합니다.
X_people = X_people / 255.

 

하지만 얼굴 데이터에 대한 사람 수는 많지만 -> 사람 이미지 수는 적음

-> 훈련 데이터가 적음 -> 분류기 훈련시키기 매우 어렵다 

 

# KNN

from sklearn.neighbors import KNeighborsClassifier
# 데이터를 훈련 세트와 테스트 세트로 나눕니다
X_train, X_test, y_train, y_test = train_test_split(
    X_people, y_people, stratify=y_people, random_state=0)
# 이웃 개수를 한 개로 하여 KNeighborsClassifier 모델을 만듭니다
knn = KNeighborsClassifier(n_neighbors=1)
knn.fit(X_train, y_train)
print("1-최근접 이웃의 테스트 세트 점수: {:.2f}".format(knn.score(X_test, y_test)))
 
1-최근접 이웃의 테스트 세트 점수: 0.23

 

그러므로 PCA 가 필요하다.

 

얼굴 유사도 측정을 위해 -> 원본 픽셀 공간에서 거리 계산 -> 아주 나쁜 방법

 

각 픽셀 값은 다른 이미지와 동일한 위치에 있는 값 비교 -> 픽셀 있는 그래도 비교 하는 방식 -> 특성 잡기 어려움 

 

한 픽셀만 바껴도 큰 차이를 만든다 -> 그래서 주성분으로 변환하여 거리 계산하면 정확도 높여지지 않을까 기대

 

여기서는 PCA의 화이트닝(백색화) 옵션 사용해서 주성분의 스케일이 같아지도록 조정 

 

# 옵션을 사용해서 주성분 스케일이 같아 지도록 조정 -> 화이트닝 옵션 없이 -> STANDSCALER 적용하는 것과 같다.

(PCA로 변환된 데이터 표준편차 LINALG.SVD 함수에서 반환된 특잇값 배열 S 를 샘플 개수의 제곱근으로 나누어 구함)

# 화이트닝 옵션 : PCA로 변활 할 때 이 표준편차를 나누어 적용 -> PCA 변환은 데이터의 평균을 0으로 만듬

_> 화이팅을 적용하는 것은 PCA 변환 한 뒤에 STANDSCALER를 적용하는 것과 같다.

 

화이트닝 옵션으로 데이터가 회전 + 스케일 조정 된 그래프

 

 

pca = PCA(n_components=100, whiten=True, random_state=0).fit(X_train)
X_train_pca = pca.transform(X_train)
X_test_pca = pca.transform(X_test)

print("X_train_pca.shape: {}".format(X_train_pca.shape))
 
X_train_pca.shape: (1547, 100)
knn = KNeighborsClassifier(n_neighbors=1)
knn.fit(X_train_pca, y_train)
print("테스트 세트 정확도: {:.2f}".format(knn.score(X_test_pca, y_test)))
 
테스트 세트 정확도: 0.31

 


100개의 특성으로 해당하는 주성분 특성 만들고 WHITEN 하니 -> 데이터 잘 표현

 

이미지 데이터일 경우 -> 주성분 쉽게 시각화 -> 입력 데이터에서 어떤 공간 -> 흑백이미지

fig, axes = plt.subplots(3, 5, figsize=(15, 12),
                         subplot_kw={'xticks': (), 'yticks': ()})
for i, (component, ax) in enumerate(zip(pca.components_, axes.ravel())):
    ax.imshow(component.reshape(image_shape), cmap='viridis')
    ax.set_title("주성분 {}".format((i + 1)))

 

 

 

정확하게 구별을 할 수 없지만 -> 1번째는 얼굴과 배경의 명암

2번째 -> 오른쪽과 왼쪽의 조명의 차이

 이런 방식은 원본 사용보다는 의미 -> BUT 얼굴 인식하고는 다른 방향

 

알고리즘이 데이터를 해석하는 방식은 사람과의 방식과는 다르다.

 

PCA변환은 데이터를 회전시키고 분산이 작은 주성분 덜어낸 것 -> 또 다른 해석 방법

 

-> 테스트 포인트를 주성분의 가중치 합으로 나타내는 데 필요한 수치PCA) 변환 뒤에 새로운 특성값을 

찾는 것으로 해석 할 수 있다.

 

 

 

덧샘 항을 더 추가하는 것과 같습니다. 주성분 픽 픽셀 수  증가 -> 변환 후 -> 어떤 정보 X -> 완벽한 재구성

 


 

 

비음수 행렬 분해(NMF)

 

유용한 특성을 뽑아내기 위한 또 다른 비지도 학습 알고리즘

 

PCA와는 비슷 -> 차원 축소에도 가능 

 

PCA 처럼 어떤 성분의 가중치 합으로 각 데이터 포인트를 나타낼 수 있다.

 

하지만 PCA - 데이터 분산이 가장 크고 수직인 성분찾았다면

 

NMF - 음수가 아닌 성분과 계수 값을 찾습니다.

 

즉 주성분과 계수가 모드 0보다 크거나 같아야 한다

 

-> 음수가 아닌 주서분과 계수의 가중치 합은 당연히 음수가 아님

-> 이방식은 음수가 아닌 특성을 가진 데이터에만 적용이 가능하다.

 

음수 아닌 가중치 합으로 데이터를 분해하는 기능 -> 여러 사람 목소리 단김 오디오 트랙

 

-> 여러 악기로 이뤄진 음악처롬 독립된 소스 추가 -> 덮어써서 특성을 유용

 

음수로 된 성분이나 계수로 만드는 상쇄 효과를 이용하기 어려운 PCA 보다 대체로 NMF의 주성분이 이해하기 쉬움

 

예) 고유얼굴은 양수의 음수 값을 모두 가지고 있지만, PCA를 설명 할 때 언급 했듯이 이 부호는 실제로 아무 규칙 X

 

# 인위적인 데이터에 NMF 적용하기

 

NMF 로 데이터 다루려면 데이터가 양수인지 확인

= 이 말은 데이터가 원점 (0, 0) 에서 상대적으로 어디에 놓여 있는지가 NMF에선 중요

 

그렇기 때문에 원점 (0,0) 에서 데이터로 가는 방향을 추출한 것으로 음수 미포함 성분을 이해 할 수 있습니다.

 

 

왼쪽 성분 둘  NMF 데이터 셋 모든 포인터 양수 -> 두개의 성분으로 표현

 

데이터를 완벽하게 재구성 할 수 있을 만큼 -> 성분 많다면 (특성개수만큼 많다면) 

 

-> 알고리즘은 데이터의 각 특서으이 끝에 위치한 포인트를 가리키는 방향 선택할 수 있다.

 

하나의 성분만 사용한다면 ,

 

NMF는 가장 잘 표현할 수 있는 평균으로 향하는 성분 

 

PCA 와는 반대로 성분 개수 줄이면 -> 특정 방향 제거되는 것 뿐만 아니라 -> 전체 성분이 완전히 바뀜

 

NMF 에서 성분은 특정 방식으로 정렬 X -> 첫 번째 비음수 성분 같은 것이 없다 -> 모두 동등하게 취급

 

NMF -> 무작위 초기화 -> 난수 생성 초기 값에 따라 다름 -> 복잡한 경우 일수록 큰 차이 만듬

 

SCIKIT-LEARN NMF 알고리즘은 X : 입력데이터 / W : 변환데이터 / H : 성분 / X=WH 만족하는 행렬,(W, H) 구하기 위해

 

행렬의 L2 노름인 프로베니우스 노름(Forbenius norm)제곱으로 만든 목적 함수 

 

 를 좌표 하강법으로 최소화

 

구해진 성분 H 는 NMF 의 components_ 속성에 저장

 

NMF 에서 기본 초기화 방식 :

 

데이터 평균을 성분의 개수로 나눈 후 제곱근을 구하고 그런 다음 정규분포의 난수를 발생시켜

 

앞에서 구한 제곱근을 곱하여 H와 W 행렬을 만듭니다. 이는 데이터 평균값을 각 성분과 두개의 행렬에 나누어 놓는 효과

 

 

 

 

from sklearn.decomposition import NMF
nmf = NMF(n_components=15, random_state=0)
nmf.fit(X_train)
X_train_nmf = nmf.transform(X_train)
X_test_nmf = nmf.transform(X_test)

fig, axes = plt.subplots(3, 5, figsize=(15, 12),
                         subplot_kw={'xticks': (), 'yticks': ()})
for i, (component, ax) in enumerate(zip(nmf.components_, axes.ravel())):
    ax.imshow(component.reshape(image_shape))
    ax.set_title("성분 {}".format(i))

 

 

 

변환을 되돌린 결과는 PCA 를 사용 했을 때와 비슷하지만 품질이 떨어짐

PCA 측면에서 최선의 방향을 찾기 때문

 

NMF 데이터를 인코딩하거나 재구성하는 용도로 사용하기 보다는 주로 데이터에 유용한 패턴찾는데 활용

 

  이 성분들은 모두 양수값이어서 -> PCA 성분 보다 훨씬 더 원형처럼 보입니다.

예를 들면 성분 3은 오른쪽으로 조금 돌아간 얼굴이 성분 7은 왼쪽으로 조금 회전

 

compn = 3
# 4번째 성분으로 정렬하여 처음 10개 이미지를 출력합니다
inds = np.argsort(X_train_nmf[:, compn])[::-1]
fig, axes = plt.subplots(2, 5, figsize=(15, 8),
                         subplot_kw={'xticks': (), 'yticks': ()})
for i, (ind, ax) in enumerate(zip(inds, axes.ravel())):
    ax.imshow(X_train[ind].reshape(image_shape))
    
compn = 7
# 8번째 성분으로 정렬하여 처음 10개 이미지를 출력합니다
inds = np.argsort(X_train_nmf[:, compn])[::-1]
fig, axes = plt.subplots(2, 5, figsize=(15, 8),
                         subplot_kw={'xticks': (), 'yticks': ()})
for i, (ind, ax) in enumerate(zip(inds, axes.ravel())):
    ax.imshow(X_train[ind].reshape(image_shape))

 

# 성분 3의 계수가 큰 얼굴들

 

주로 오른쪽 얼굴로 회전

 

# 성분 7의 계수가 큰 얼굴들

 

주로 왼쪽으로 돌아간 얼굴

 

성분 11 에 대한 계수 값이 큰 얼굴들은 오른쪽으로 돌아가 있습니다 -> 이와 같은 패턴을 추출하는 것은 소리, 유전자 표현, 텍스트 데이터

 

다음은 서로 다른 입력으로부터 합성된 신호 

 

불행히도 우리는 원본 신호는 볼 수 없고 -> 3개의 섞인 신호만 볼 수 있는 상황

-> 합쳐진 신호를 분해해서 -> 원본 신호를 복원해야 합니다

# 원본 데이터를 사용해 100개의 측정 데이터를 만듭니다
A = np.random.RandomState(0).uniform(size=(100, 3))
X = np.dot(S, A.T)
print("측정 데이터 형태: {}".format(X.shape))
 
측정 데이터 형태: (2000, 100)
nmf = NMF(n_components=3, random_state=42)
S_ = nmf.fit_transform(X)
print("복원한 신호 데이터 형태: {}".format(S_.shape))
 
복원한 신호 데이터 형태: (2000, 3)
pca = PCA(n_components=3)
H = pca.fit_transform(X)
models = [X, S, S_, H]
names = ['측정 신호 (처음 3개)',
         '원본 신호',
         'NMF로 복원한 신호', 
         'PCA로 복원한 신호']

fig, axes = plt.subplots(4, figsize=(8, 4), gridspec_kw={'hspace': .5},
                         subplot_kw={'xticks': (), 'yticks': ()})

for model, name, ax in zip(models, names, axes):
    ax.set_title(name)
    ax.plot(model[:, :3], '-')
    ax.margins(0)

 

 

X 에 담긴 측정 데이터 100개중 처음 3개를 함께 그래프로 나타냈음 -> NMF는 원본 신호 잘 복원 / PCA 실패했고 대부분 첫번째 성분 사용

-> NMF 성분의 순서가 원본 신호와 같지만 순전히 우연 

 

PCA 나 NMF 처럼 데이터 포인트를 일정 개수의 성분을 사용해 -> 가중치 합으로 분해 할수 있는 알고리즘 많이 있음

 

더 관심이 있는 경우 독립성분분석(ICA) 요인분석(FA) 회소코딩(딕셔너리 학습) SCIKIT-LEARN 


 

T-SNE를 이용한 매니폴드 학습

T-Distributed Stochastic Neighbor Embedding

 

데이터 산점도로 시각활 수 있다는 이점 때문에 PCA 쓰지만 -> LFW 에서 산점도 에서 본것처럼 (회전하고 방향 제거하는) 유용성 떨어짐

 

매니폴드 학습 알고리즘 이라고 하는 시각화 알고리즘들은 훨씬 더 복잡한 매핑을 만들어 더 나은 시각화 제공

 

특별히 t-sne 사용 많이 한다. 

 

매니폴드 학습 알고리즘은 목적이 시각화 3개이상의 특성을 뽑는 경우는 거의 없다.

 

t-SNE 를 포함해서 일부 매니폴드 알고리즘들은 훈련 데이터를 새로운 표현을 변환시키지만 

 

새로운 데이터에는 적용하지 못합니다. 그래서 매니폴도 학습은 탐색적 분석에서 유용하지만

 

지도학습용으로는 거의 사용하지 않는다. 

T-SNE 의 아이디어는 데이터 포인트 사의 거리를 잘 보존하는 2차원 표현을 찾는 것

 

먼저 T-SNE는 각 데이터 포인트를 2차원에 무작위로 표현한 후 원본 특성 공간에서 가까운 포인트는 가깝게,

 

멀리 떨어진 포인트는 멀어지게 만듭니다.

 

T-SNE 는 멀리 떨어진 포인트와 거리를 보존하는 것보다 가까이 잇는 포인트에 더 비중을 둡니다.

 

다시마해 이웃 데이터 포인트에 대한 정보를 보존하려고 노력한다.

 

손글씨 -> 매니폴드 학습 시키기(mnist와는 다른 데이터)

 

 skit-learn 에서 t-sne 은 KL (쿨백-라이블러 발산) 목적 함수를 최적하 하기 위해 모멘텀을 적용한 배치 경사 하강법

 

TSNE 의 방법 매개변수 기본 값은 barnes_hurt 로 그래디언트 계산의 복잡도를 0(n^2) -> 0(nlog(n)))으로 낮추는 반서 헛 방법

 

exact 옵션은 -> 정확한 계산 ->그러나 느림 -> 대량의 데이터에는 부적합

 

# PCA 모델을 생성합니다
pca = PCA(n_components=2)
pca.fit(digits.data)
# 처음 두 개의 주성분으로 숫자 데이터를 변환합니다
digits_pca = pca.transform(digits.data)
colors = ["#476A2A", "#7851B8", "#BD3430", "#4A2D4E", "#875525",
          "#A83683", "#4E655E", "#853541", "#3A3120","#535D8E"]
plt.figure(figsize=(10, 10))
plt.xlim(digits_pca[:, 0].min(), digits_pca[:, 0].max())
plt.ylim(digits_pca[:, 1].min(), digits_pca[:, 1].max())
for i in range(len(digits.data)):
    # 숫자 텍스트를 이용해 산점도를 그립니다
    plt.text(digits_pca[i, 0], digits_pca[i, 1], str(digits.target[i]),
             color = colors[digits.target[i]],
             fontdict={'weight': 'bold', 'size': 9})
plt.xlabel("첫 번째 주성분")
plt.ylabel("두 번째 주성분")

 

0,6,4 는 나름 분류가 잘되지만 두개의 주성분 만으로는 분석이 안되는 것 보인다 -> 겹치는 부분이 많다.

 

 

from sklearn.manifold import TSNE
tsne = TSNE(random_state=42)
# TSNE에는 transform 메소드가 없으므로 대신 fit_transform을 사용합니다
digits_tsne = tsne.fit_transform(digits.data)
plt.figure(figsize=(10, 10))
plt.xlim(digits_tsne[:, 0].min(), digits_tsne[:, 0].max() + 1)
plt.ylim(digits_tsne[:, 1].min(), digits_tsne[:, 1].max() + 1)
for i in range(len(digits.data)):
    # 숫자 텍스트를 이용해 산점도를 그립니다
    plt.text(digits_tsne[i, 0], digits_tsne[i, 1], str(digits.target[i]),
             color = colors[digits.target[i]],
             fontdict={'weight': 'bold', 'size': 9})
plt.xlabel("t-SNE 특성 0") 
plt.xlabel("t-SNE 특성 1")

완전히 비지도 학습 !!

매개변수 perplexity , early_exaggeration 를 변경 해 볼수 있지만 큰 효과는 X

728x90