- epsilon
- 주변 영역- min points
- 엡실론 영역에 포함되는 다른 데이터의 개수- Core Point(핵심 포인트)
- 주변 영역 내에 최소 데이터 개수 이상의 다른 데이터를 포함한 포인트- Neighbor Point(이웃 포인트)
- 주변 영역 내에 존재하는 다른 데이터- Border Point(경계 포인트)
- 주변 영역 내에 최소 데이터 개수 이상의 이웃 포인트를 가지고 있지 않지만, 핵심 포인트를 이웃으로 가지고 있는 데이터- Noise Point(잡음 포인트)
- 최소 데이터 개수 만큼의 이웃 포인트도 가지지 않고 햄심 포인트도 이웃 포인트로 가지지 않은 데이터
- 이상치 탐지의 원리
- 알고리즘이 각 샘플에서 epsilon 내에 샘플이 몇 개 놓였는지 세고, 이 지역을 샘플의이웃이라고 부릅니다.
- 이웃 내에 min_samples 개 이상의 데이터가 존재한다면, 핵심 포인트로 간주합니다.
- 핵심 샘플의 이웃에 있는 모든 샘플은 동일한 클러스터에 속하고, 이웃에는 다른 핵심 포인트가 포함될 수 있어서 핵심 포인트의 이웃의 이웃은 계속해서 하나의 클러스터를 형성
- 핵심 포인트도 아니고 이웃 포인트도 아닌 잡음 포인트는 이상치 처리
sklearn DBSCAN
클래스 이용합니다.
- 반달 모양 데이터 군집 생성
from sklearn.datasets import make_moons X,y=make_moons(n_samples=1000,noise=0.05, random_state=42) plt.scatter(x=X[:,0], y=X[:,1])
- eps 0.05 min_samples를 5로 설정해서 수행
# eps 0.05 min_samples를 5로 설정해서 수행 from sklearn.cluster import DBSCAN # 모델 생성 및 훈련 dbscan=DBSCAN(eps=0.05,min_samples=5) dbscan.fit(X) # 군집된 결과 확인 (-1)은 노이즈 print(dbscan.labels_[:20])
- 핵심 포인트 확인하기
# 핵심 포인트의 개수 확인해보자. print(len(dbscan.core_sample_indices_)) # 핵심 포인트의 인덱스 print(dbscan.core_sample_indices_) # 핵심 포인트의 좌표 print(dbscan.components_)
- 실제 클러스터 확인하기
# 클러스터의 종류 확인하기 print(np.unique(dbscan.labels_))
- 이상치를 제외하고 0~6까지 총 7개의 군집으로 나누어졌다.
- eps 값을 조금씩 바꾸면서 결과를 다시 확인해보자.
- eps 값이 0.5가 된다면 label은 0, 1로 완벽하게 군집화가 됩니다.
- eps 값 변화하면서 확인하기
def plot_dbscan(dbscan, X, size, show_xlabels=True, show_ylabels=True): # 데이터 개수만큼 배열 만들고 핵심 포인트만 True 설정 core_mask=np.zeros_like(dbscan.labels_,dtype=bool) core_mask[dbscan.core_sample_indices_]=True # 이상치는 반대로 설정 anomalies_mask=dbscan.labels_==-1 # 이웃 포인트(핵심, 이상도 아닌 포인트) non_core_mask=~(core_mask | anomalies_mask) cores=dbscan.components_ anomalies=X[anomalies_mask] non_cores=X[non_core_mask] # plt.scatter(cores[:0],cores[:,1], c=dbscan.labels_[core_mask], # makrer='o', s=size, cmap='Paired') # plt.scatter(cores[:0],cores[:,1], c=dbscan.labels_[core_mask], # makrer='*', s=20) plt.scatter(cores[:, 0], cores[:, 1], c=dbscan.labels_[core_mask], marker='o', s=size, cmap='Paired') plt.scatter(cores[:, 0], cores[:, 1], c=dbscan.labels_[core_mask], marker='+', s=20) # plt.scatter(anomalies[:0],anomalies[:,1], c=dbscan.labels_[anomalies_mask], # makrer='x', s=100, cmap='r') # plt.scatter(non_cores[:0],non_cores[:,1], c=dbscan.labels_[non_core_mask], # makrer='^',cmap='g') plt.scatter(anomalies[:, 0], anomalies[:, 1], c=dbscan.labels_[anomalies_mask], marker='x', s=100, cmap='prism') plt.scatter(non_cores[:, 0], non_cores[:, 1], c=dbscan.labels_[non_core_mask], marker='^', cmap='cool') if show_xlabels: plt.xlabel("X_1",fontsize=14) else: plt.tick.params(labelbottom=False) if show_ylabels: plt.ylabel("X_2", fontsize=14) else: plt.tick.params(labelleft=False) plt.title("eps{:.2f}, min_smaples={}".format(dbscan.eps,dbscan.min_samples),fontsize=14)
### 엡실론 조정 dbscan1=DBSCAN(eps=0.05, min_samples=5) dbscan2=DBSCAN(eps=0.1, min_samples=5) dbscan3=DBSCAN(eps=0.2, min_samples=5) dbscan1.fit(X) dbscan2.fit(X) dbscan3.fit(X) plt.figure(figsize=(12,4)) plt.subplot(131) plot_dbscan(dbscan1,X,size=100) plt.subplot(132) plot_dbscan(dbscan2,X,size=100) plt.subplot(133) plot_dbscan(dbscan3,X,size=100) plt.show()
# 새로운 데이터에 대한 예측 from sklearn.neighbors import KNeighborsClassifier # 군집한 결과를 가지고 분류 모델이 훈련 knn=KNeighborsClassifier(n_neighbors=50) knn.fit(dbscan3.components_,dbscan3.labels_[dbscan3.core_sample_indices_])
# 예측은 학습한 KNN으로 X_new=np.array([[-0.5, 0], [0,0.5]]) y_hat=knn.predict(X_new) print(y_hat) y_hat_proba=knn.predict_proba(X_new) print(y_hat_proba)
- 시각화 함수 - DB 클러스터링 결과 & DF
# K-Means때문에 istcenter를 넣었습니다. def visualize_cluster_plot(clusterobj,dataframe, label_name, iscenter=True): # 클러스터의 중앙점 찾기 if iscenter: centers=clusterobj.cluster_conters_ unique_labels=np.unique(dataframe[label_name].values) markers=['o','s','^','x','*'] # 이상치 여부 isNoise=False # 클러스터 순회 for label in unique_labels: label_cluster=dataframe[dataframe[label_name]==label] # 이 경우는 DBSCAN만 if label==-1: cluster_legend='Noise' isNoise=True else: cluster_legend='Cluster'+str(label) plt.scatter(x=label_cluster['ftr1'],y=label_cluster['ftr2'], s=70, edgecolor='k', marker=markers[label], label=cluster_legend) if iscenter: center_x_y=centers[label] plt.scatter(x=center_x_y[0],y=center_x_y[1],s=250, color='white',edgecolor='k',marker='$%d$'%label) if isNoise: legend_loc='upper center' else: legend_loc='upper right' plt.legend(loc=legend_loc) plt.show()
- 샘플데이터
from sklearn.datasets import make_circles X,y=make_circles(n_samples=1000, shuffle=True, noise=0.05, random_state=42, factor=0.5) clusterDF=pd.DataFrame(data=X, columns=['ftr1','ftr2']) clusterDF['target']=y print(np.unique(clusterDF['target'])) visualize_cluster_plot(None, clusterDF, 'target', iscenter=False)
- K-Means는 어떻게 처리 할까?
from sklearn.cluster import KMeans kmeans=KMeans(n_clusters=2, max_iter=1000, random_state=42) kmeans_labels=kmeans.fit_predict(X) clusterDF['kmeans_cluster']=kmeans_labels visualize_cluster_plot(kmeans, clusterDF, 'kmeans_cluster', iscenter=True)
- centroid 계산으로 반으로 나누어졌다.
- GMM은 어떻게 처리할까?
from sklearn.mixture import GaussianMixture gmm=GaussianMixture(n_components=2, random_state=42) gmm_label=gmm.fit(X).predict(X) clusterDF['gmm_cluster']= gmm_label visualize_cluster_plot(gmm, clusterDF, 'gmm_cluster', iscenter=False)
- 타원형으로 처리하더니, 결국 대각선 방향으로 반을 갈라버렸다.
- 이제 DBSCAN을 봐보자.
from sklearn.cluster import DBSCAN dbscan=DBSCAN(eps=0.2, min_samples=7) dbscan_labels=dbscan.fit_predict(X) clusterDF['dbscan_cluster']= dbscan_labels visualize_cluster_plot(dbscan, clusterDF, 'dbscan_cluster', iscenter=False)
- 이쁘게 분류했다.
from sklearn.datasets import load_iris iris=load_iris() X=iris.data from sklearn.cluster import Birch brc=Birch(n_clusters=3) brc.fit(X)
print(iris.target) y_hat=brc.predict(X) print(y_hat)
비지도 학습에서는 데이터를 적절하게 스케일링을 해야 합니다.
데이터 크기가 조정되지 않으면, PCA, K-Means 등은 큰 값을 갖는 변수들에 의해 결과가 좌우되고, 작은 값을 갖는 변수들은 무시되는 경우가 발생합니다.
범주형 데이터를 사용하게 된다면 KNN과 K-Means에서는 문제가 발생할 수 있습니다.
- 순서가 없는 데이터들은 원 핫 인코딩을 하게 되는데, 이렇게 되면 다른 feature와 크기가 다를 수 있고, 2가지 값만 가질 수 있기 때문에 영향을 많이 미치게 될 수 있습니다.
numpy.linal.svd
함수np.random.seed(42) m=60 angles=np.random.rand(m)*3*np.pi/2-0.5 X=np.empty((m,3)) # print(X) X_centered=X-X.mean(axis=0) # 특이값 분해 수행 U,s,Vt=np.linalg.svd(X_centered) # 주성분 c1=Vt.T[:,0] c2=Vt.T[:,1] print(c1) print(c2) # 실제 변환된 결과 W2=Vt.T[:,:2] X2D=X_centered.dot(W2) print(X2D)
- 이걸 직접 작업할 필요는 없고 sklearn의 PCA 클래스가 SVD를 이용한 주성분 분석을 구현하고 있다.
n_componentes
파라미터에 원하는 주성분의 개수만 설정하면 됩니다.np.random.seed(42) m=60 angles=np.random.rand(m)*3*np.pi/2-0.5 X=np.empty((m,3)) # print(X) X[:,0]=np.cos(angles)+np.sin(angles)/2+0.1*np.random.randn(m)/2 # 0.1, 0.7은 잡음을 섞이 위한 것이라 아무 숫자나 가능 X[:,1]=np.sin(angles)*0.7+0.1*np.random.randn(m)/2 # 잡음 처리 해주기 X[:,2]=X[:,0]*0.1+X[:,1]*0.3+0.1*np.random.randn(m) X_centered=X-X.mean(axis=0) # sklearn from sklearn.decomposition import PCA # 2개의 주성분을 추출해주는 PCA 인스턴스 pca=PCA(n_components=2) # 데이터를 2차원으로 줄이기 X2D=pca.fit_transform(X) print(X2D)
- 훈련을 하고 난 뒤, PCA 인스턴스의
explained_variance_ratio_
속성에 설명 가능한 분산의 비율을 저장합니다.- 이 개수는 주성분의 개수입니다.
# 분산의 비율 print("설명 가능한 분산의 비율 :",pca.explained_variance_ratio_) # 첫 번째 주성분이 0.854 만큼의 분산을 설명 # 두 번째 분산은 0.136만큼의 분산을 설명 # 두개의 분산이 있으면 0.99만큼의 분산을 설명하는 것 # 데이터 유실 print("잃어버린 분산의 비율 :", (1-pca.explained_variance_ratio_.sum()))
- PCA 인스턴스의
invers_transform
함수를 이용하면 원래의 데이터로 복원이 가능합니다.
- 완전 복원은 불가능합니다.
- 분산이 100%가 아니기 때문입니다.np.random.seed(42) m=60 angles=np.random.rand(m)*3*np.pi/2-0.5 X=np.empty((m,3)) X[:,0]=np.cos(angles)+np.sin(angles)/2+0.1*np.random.randn(m)/2 # 0.1, 0.7은 잡음을 섞이 위한 것이라 아무 숫자나 가능 X[:,1]=np.sin(angles)*0.7+0.1*np.random.randn(m)/2 # 잡음 처리 해주기 X[:,2]=X[:,0]*0.1+X[:,1]*0.3+0.1*np.random.randn(m) X_centered=X-X.mean(axis=0) print(X[:5]) # sklearn from sklearn.decomposition import PCA # 2개의 주성분을 추출해주는 PCA 인스턴스 pca=PCA(n_components=2) # 데이터를 2차원으로 줄이기 X2D=pca.fit_transform(X) # print(X2D) # 2차원으로 줄어든 data를 복원하고 싶다. X3D_inverse=pca.inverse_transform(X2D) print(X3D_inverse[:5])
- 차이가 존재하긴 하지만, 엄청 큰 차이는 아닌가..?
# 2개가 배열이 같은지 확인 print(np.allclose(X,X3D_inverse))
- return은 boolean
# 오차의 평균 print(np.mean(np.sum(np.square(X3D_inverse - X), axis=1))) # 각 행의 오차 구하기 -이를 이용해서 이상치 탐지 가능 print(np.sum(abs(X3D_inverse-X), axis=1)) # 주성분 확인 print(pca.components_)
- Data Load
# gz-> 압축 sp500_px=pd.read_csv('./data/sp500_data.csv.gz',index_col=0) oil_px=sp500_px[['XOM','CVX']] oil_px.head()
- 주성분 분석의 결과를 DataFrame으로 생성해서 출력
pca=PCA(n_components=2) pca.fit(oil_px) loadings=pd.DataFrame(pca.components_,columns=oil_px.columns) print(loadings)
- 결과 해석
- 2개의 주성분으로 생성한 경우, 첫 번째 성분은 상관관계를 의미하는 계수이고, 두 번째 성분은 값이 달라지는 지점을 의미합니다.- 주성분을 시각화
# 주성분 시각화 plt.scatter(oil_px['XOM'],oil_px['CVX'])
def abline(slope, intercept, ax): x_vals=np.array(ax.get_xlim()) return(x_vals,intercept+slope*x_vals) ax=oil_px.plot.scatter(x="XOM", y='CVX', alpha=0.3, figsize=(4,4)) ax.set_xlim(-8,8) ax.set_ylim(-8.8) ax.plot(*abline(loadings.loc[0,'CVX']/loadings.loc[0,'XOM'],0,ax), '--',color='C1') # C2 녹색 ax.plot(*abline(loadings.loc[1,'CVX']/loadings.loc[1,'XOM'],0,ax), '--',color='C2') plt.show()
- 여러 개의 속성으로 확장하는 것이나 여러 개의 주성분을 추출하는 것도 가능
- 주성분 분석은 수치형 데이터에만 사용 가능합니다
- 범주형, 문자열 그리고 날짜 타입은 분산이 근본적으로 없기에 사용 불가능 합니다.
- 범주형이나 문자열은 개수나 분포의 비율이 의미를 갖는 데이터이지, 평균이나 합계를 구하는 데이터가 아닙니다.
scree plot
이라고 합니다.
- feature의 중요도를 파악해보자.
syms=sorted(['AAPL','MSFT','CSCO','INTC','CVX', 'XOM','SLB','COP', 'JPM','WFC','USB', 'AXP','WMT','TGT','HD','COST']) top_sp=sp500_px.loc[sp500_px.index>='2011-01-01',syms] top_sp.head()
# feature의 중요도를 파악할 것이라서 주성분의 개수를 설정하지 않음 sp_pca=PCA() sp_pca.fit(top_sp)
# 설명 가능한 분산의 비율을 확인 print(sp_pca.explained_variance_)
explained_variance=pd.DataFrame(sp_pca.explained_variance_) ax=explained_variance.head(10).plot.bar(legend=False, figsize=(4,4)) ax.set_label('COMPONANT') plt.show()
# 5개의 주성분에 미치는 중요도를 DF로 생성
3
# 데이터 로드 from sklearn.datasets import fetch_openml from sklearn.model_selection import train_test_split mnist=fetch_openml('mnist_784', version=1, as_frame=False) mnist.target=mnist.target.astype(np.uint8) X=mnist['data'] y=mnist['target'] X_train,X_test,y_train,y_test=train_test_split(X,y) print(X_train.shape)
- 출력을 보면 784차원이다.
- 적절한 차원의 개수를 찾기 위해서 차원의 개수를 설정하지 않고 분산의 비율보다 큰 개수를 찾기 (권장 X)
pca=PCA() pca.fit(X_train) cumsum=np.cumsum(pca.explained_variance_ratio_) d=np.argmax(cumsum>=0.95)+1 print(d)
- 154개가 나왔다.
- 방법 2 - 결과를 시각화해서 elbow를 찾는 방식(그다지 권장 X)
plt.figure(figsize=(6,4)) plt.plot(cumsum,linewidth=3) plt.axis([0,400,0,1]) plt.xlabel('차원') plt.ylabel('분산 비율') plt.plot([d,d],[0,0.95],'k:') plt.plot([d,d],[0.95,0.95],'k:') plt.plot(d,0.95,'ko') plt.annotate("elbow", xy=(150,0.95), xytext=(70, 0.7), arrowprops=dict(arrowstyle="->"), fontsize=16) plt.show()
- 방법 3 : 분산비율이 0.95이상인 PCA 만들면 된다.(만들때 집어넣기)(권장)
pca=PCA(n_components=0.95) X_reduced=pca.fit_transform(X_train) print(pca.n_components_) print(np.sum(pca.explained_variance_ratio_))
- 빠르게 잘 나온다.
inverse_transform
이라는 함수를 이용하면 원래의 차원으로 복원
- 복원한 이미지와 원래 이미지
pca=PCA(n_components=154) X_reduced=pca.fit_transform(X_train) X_recovered=pca.inverse_transform(X_reduced) print(X_recovered.shape)
- shape으로 잘 복원되었는지 확인할 수 있다.
def plot_digits(instances, images_per_row=5, **options): size=28 # 한 줄에 출력할 이미지 개수 설정 image_per_row=min(len(instances),images_per_row) # 1차원 이미지를 2차원으로 재구성하기 images=[instances.reshape(size,size) for instances in instances] # 행의 개수 계산 n_rows=(len(instances)-1)//images_per_row+1 row_images=[] n_empty=n_rows*images_per_row-len(instances) images.append(np.zeros((size*size*n_empty))) for row in range(n_rows): rimages=images[row*images_per_row : (row+1)*images_per_row] row_images.append(np.concatenate(rimages,axis=1)) image=np.concatenate(row_images, axis=0) plt.imshow(image, cmap=mpl.cm.binary, **options) plt.axis('off')
plt.figure(figsize=(7,4)) plt.subplot(121) plot_digits(X_train[::2100]) plt.title("원본 이미지", fontsize=16) plt.subplot(122) plot_digits(X_recovered[::2100]) plt.title("압축 이미지", fontsize=16) plt.show()
- 손실이 크게 난것 같지는 않은데...
svd_solver
매개변수에 randomized를 설정하면, sklearn은 랜덤 PCA를 수행하는데, 랜덤 PCA는 처음 몇개의 주성분에 대한 근사값을 빠르게 찾는 알고리즘이다.svd_solver
는 기본 값 auto인데, atuo는 주성분 개수나 데이터의 개수나 차원의 개수의 80%보다 작으면 랜덤 PCA를 수행하고, 그렇지 않으면 완전 SVD 방식을 사용svd_solver
에 full을 설정하면 된다.%%time pca=PCA(n_components=154, svd_solver='full') X_reduced=pca.fit_transform(X_train)
%%time pca=PCA(n_components=154, svd_solver='randomized') X_reduced=pca.fit_transform(X_train)
- 확실하게 시간 차이가 난다.
%%time pca=PCA(n_components=154) X_reduced=pca.fit_transform(X_train)
- 80% 밑이기에 auto 는 randomized가 될 것이다.
IncrementalPCA
클래스를 이용해서 이 기능을 제공합니다.