1. 사용 데이터

 -사이킷런에서 제공하는 MNIST 데이터셋(손글씨 데이터셋)

# 사이킷런에서 기본적으로 제공하는 MNIST 데이터셋 불러오기
from sklearn.datasets import fetch_openml
mnist=fetch_openml('mnist_784',version=1,as_frame=False)
mnist.keys()

### 결과 ###
dict_keys(['data', 'target', 'frame', 'categories', 'feature_names', 'target_names', 'DESCR', 'details', 'url'])

 

 -MNIST 데이터셋 배열 확인

X,y=mnist['data'],mnist['target']
X.shape     # (70000, 784)
y.shape     # (70000,)

# 이미지가 70000개이고 각 이미지는 784개의 특성을 가짐(28*28 픽셀의 이미지이므로 28*28=784개의 특성을 가짐)
# 각 특성은 0(흰색)~255(검은색)까지의 픽셀 강도

 

2. 다중 분류기 훈련

 -서포트 벡터 머신(SVC) 분류기 사용

from sklearn.svm import SVC
svm_clf=SVC()
svm_clf.fit(X_train, y_train)
svm_clf.predict([some_digit])

### 결과 ###
array([5], dtype=uint8)
# 이진 분류기에서 5인지 아닌지에 따라 True, False로 결과가 나온 것과 달리 0~9까지 숫자 중 5라고 분류해냄

 

 -점수 확인하기

 -0~9까지 각 레이블에 대한 점수를 계산하여 가장 높은 점수를 가진 레이블로 예측함

 -레이블이 5일때 가장 높은 점수일 것으로 예상

some_digit_scores=svm_clf.decision_function([some_digit])
some_digit_scores

### 결과 ###
array([[ 1.72501977,  2.72809088,  7.2510018 ,  8.3076379 , -0.31087254,
         9.3132482 ,  1.70975103,  2.76765202,  6.23049537,  4.84771048]])

 -5일때 9.31점으로 가장 높은 점수가 나와 분류 결과가 5로 출력됨

 

 -분류한 모든 클래스 출력하기

svm_clf.classes_

### 결과 ###
array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9], dtype=uint8)

 

 

 -OvO(OneVsOneClassifier): 0과1 구별, 1과 2 구별..과 같이각 숫자 조합마다 이진 분류를 통해 분류기를 훈련시킴(N*(N-1)/2 개의 분류기 필요)

 -OvR(OneVsRestClassifier): 모든 숫자를 훈련시킨 후, 가장 점수가 높은 것을 선택

 -서포트 벡터 머신 같은 일부 알고리즘에서는 큰 훈련세트에서 몇 개의 분류기를 훈련시키는 것보다는 작은 훈련세트에서 많은 분류기 훈련시키는 것 선호

 -이진 분류 알고리즘에서는 대부분 OvR 선호

 -OvO나 OvR 강제로 사용하기

# OvO나 OvR 사용을 강제하려면 OneVsOneClassifier나 OneVsRestClassifier 사용
from sklearn.multiclass import OneVsRestClassifier
ovr_clf=OneVsRestClassifier(SVC())
ovr_clf.fit(X_train, y_train)
ovr_clf.predict([some_digit])

### 결과 ###
array([5], dtype=uint8)

# SGD 분류기는 직접 샘플을 다중 클래스로 분류할 수 있으므로 별도로 OvO 또는 OvR 적용할 필요 없음
# SGDClassifier 훈련
from sklearn.linear_model import SGDClassifier
sgd_clf=SGDClassifier(random_state=42)
sgd_clf.fit(X_train, y_train)
sgd_clf.predict([some_digit])

### 결과 ###
array([3], dtype=uint8)

sgd_clf.decision_function([some_digit])

### 결과 ###
array([[-31893.03095419, -34419.69069632,  -9530.63950739,
          1823.73154031, -22320.14822878,  -1385.80478895,
        -26188.91070951, -16147.51323997,  -4604.35491274,
        -12050.767298  ]])

 -SGDClassifier에서 5를 3으로 분류해버림

 -클래스마다 부여한 점수확인 결과 3에 부여한 점수가 1823으로 가장 높고 5에 부여한 점수는 그 다음으로 높은 -1385

 

# SGDClassifier의 성능 평가
from sklearn.model_selection import cross_val_score
cross_val_score(sgd_clf, X_train, y_train, cv=3, scoring='accuracy')    # array([0.87365, 0.85835, 0.8689 ])

# 스케일 조정을 하면 정확도를 높일 수 있음
from sklearn.preprocessing import StandardScaler
scaler=StandardScaler()
X_train_scaled=scaler.fit_transform(X_train.astype(np.float64))
cross_val_score(sgd_clf,X_train_scaled,y_train,cv=3,scoring='accuracy') # array([0.8983, 0.891 , 0.9018])

 

 

 

3. 에러 분석

 -모델의 성능을 향상시키기 위해 에러의 종류를 분석하여 확인하는 것

from sklearn.model_selection import cross_val_predict
from sklearn.metrics import confusion_matrix

y_train_pred=cross_val_predict(sgd_clf, X_train_scaled, y_train, cv=3)
conf_mx=confusion_matrix(y_train, y_train_pred)
conf_mx
# 첫 행부터 실제 0일 때 0으로 예측한 개수, 1로 예측한 개수, 2로 예측한 개수...

 -각 숫자를 정확히 예측한 개수가 가장 많지만 숫자 5에 대해 5로 예측한 횟수는 4444로 다른 숫자에 비해 낮음

 -시각화 해보기

import matplotlib.pyplot as plt
plt.matshow(conf_mx, cmap=plt.cm.gray)
plt.show()

 -숫자 5부분만 조금 진한색으로 표시되어 다른 숫자들보다 정확히 예측해낸 횟수가 적음을 의미

 

 -에러의 개수가 아닌 비율을 시각화해보기

 -단순히 5의 전체 개수가 적어서 생긴 현상일 수 있으므로 전체 개수 대비 정확히 예측한 비율을 시각화

# 각 행의 합계 계산
row_sums=conf_mx.sum(axis=1,keepdims=True)
# 각 값을 행의 전체 합계로 나누어 비율 확인
norm_conf_mx=conf_mx / row_sums

# 대각원소는 0으로 채워 무시하고 나머지 값에서 에러 비율의 크기 확인
np.fill_diagonal(norm_conf_mx,0)
plt.matshow(norm_conf_mx, cmap=plt.cm.gray)
plt.show()

 -8열이 밝은 것으로 보아 많은 이미지가 8로 잘못 분류됨

 -3행 5열과 5행 3열이 밝은 것은 3과 5를 서로 잘못 분류한 비율이 높음을 의미

 -3과 5에 대해 3을 3으로, 3을 5로, 5를 3으로, 5를 5로 예측한 데이터들을 한번에 살펴보기

# 그림 그리기 기능 함수
import matplotlib as mpl

def plot_digit(data):
    image=data.reshape(28,28)
    plt.imshow(image,cmap=mpl.cm.binary,interpolation='nearest')
    plt.axis('off')

def plot_digits(instances, images_per_row, **options):
    size=28
    images_per_row=min(len(instances),images_per_row)
    n_rows=(len(instances)-1) // images_per_row +1

    # 필요하면 그리드의 끝을 채우기 위해 빈 이미지 추가
    n_empty=n_rows*images_per_row-len(instances)
    padded_instances=np.concatenate([instances, np.zeros((n_empty, size*size))],axis=0)

    # 배열의 크기를 바꿔 28*28 이미지를 담은 그리드로 구성
    image_grid=padded_instances.reshape((n_rows, images_per_row, size, size))

    # 축 0(이미지 그리드의 수직축)과 2(이미지의 수직축)를 합치고 축 1과 3(그리드와 이미지의 수평축)을 합침
    # transpose()를 통해 결합하려는 축을 옆으로 이동한 다음 합침
    big_image=image_grid.transpose(0, 2, 1, 3).reshape(n_rows*size,images_per_row*size)

    # 하나의 큰 이미지 출력
    plt.imshow(big_image, cmap=mpl.cm.binary, **options)
    plt.axis('off')


# 개개의 오류 살펴보며 왜 잘못되었는지 생각해보기(3과 5를 예시로)
cl_a, cl_b=3,5
X_aa=X_train[(y_train==cl_a) & (y_train_pred==cl_a)]    # 실제 3을 3으로 예측
X_ab=X_train[(y_train==cl_a) & (y_train_pred==cl_b)]    # 실제 3을 5로 예측
X_ba=X_train[(y_train==cl_b) & (y_train_pred==cl_a)]    # 실제 5를 3으로 예측
X_bb=X_train[(y_train==cl_b) & (y_train_pred==cl_b)]    # 실제 5를 5로 예측

plt.figure(figsize=(8,8))
plt.subplot(221); plot_digits(X_aa[:25], images_per_row=5)  # X_aa에 해당하는 데이터를 처음 25개만 불러와서 5행으로 정렬
plt.subplot(222); plot_digits(X_ab[:25], images_per_row=5)  # X_ab에 해당하는 데이터를 처음 25개만 불러와서 5행으로 정렬
plt.subplot(223); plot_digits(X_ba[:25], images_per_row=5)  # X_ba에 해당하는 데이터를 처음 25개만 불러와서 5행으로 정렬
plt.subplot(224); plot_digits(X_bb[:25], images_per_row=5)  # X_bb에 해당하는 데이터를 처음 25개만 불러와서 5행으로 정렬
plt.show()

 -5를 3으로 잘못 예측해낸 것(제3사분면) 중 첫 행 2열은 사람이 봐도 3같을 정도로 잘못 분류할 확률이 높아보임

 -위 방식으로 에러를 확인하여 어디서, 왜 오차가 나는지 확인하고 해결방법 고안하기

  ex) 3과 5는 위의 선분과 아래의 원을 잇는 수직선의 위치가 왼쪽, 오른쪽으로 다르다는 점 등을 이용하여 다시 학습시키기

 

 

4. 다중 레이블 분류

 -분류해내야 하는 타겟변수가 여러 개일 때, 여러 개를 한 번에 분류

 -KNeighborsClassifier, DecisionTreeClassifier, RandomForestClassifier, OneVsRestClassifier에서 다중 분류 지원

from sklearn.neighbors import KNeighborsClassifier

y_train_large=(y_train>=7)                      # 분류한 결과가 7보다 큰지
y_train_odd=(y_train%2==1)                      # 분류한 결과가 홀수인지
y_multilabel=np.c_[y_train_large,y_train_odd]   # 위의 두 개의 사항에 대해 예측하는 다중 레이블

knn_clf=KNeighborsClassifier()
knn_clf.fit(X_train,y_multilabel)

knn_clf.predict([some_digit])   # 숫자 5에 대해 예측결과 반환

### 결과 ###
array([[False, True]])

# f1_score를 통해 얼마나 정확한지 확인
from sklearn.metrics import f1_score
y_train_knn_pred=cross_val_predict(knn_clf,X_train,y_multilabel,cv=3)
f1_score(y_multilabel, y_train_knn_pred,average='macro')

### 결과 ###
0.976410265560605

-숫자 5를 7보다 크지 않고,(False), 홀수(True)라고 정확히 분류해냄 

 

 

5. 다중 출력 분류

 -다중 레이블 분류에서 한 레이블이 값을 두 개이상 가질 수 있는 분류

 -MNIST 숫자 이미지 데이터는 한 픽셀당 한 레이블이므로 레이블이 784개인 다중 레이블임

 -각 레이블은 0~255까지의 숫자를 가질 수 있는 다중 출력 분류가 가능한 데이터셋임

# MNIST 이미지의 픽셀 강도에 잡음 추가
noise = np.random.randint(0, 100, (len(X_train), 784))
X_train_mod = X_train + noise       # 독립변수는 잡음이 섞인 데이터
noise = np.random.randint(0, 100, (len(X_test), 784))
X_test_mod = X_test + noise
y_train_mod = X_train               # 예측해야하는 변수는 원래 데이터
y_test_mod = X_test

some_index = 5500
plt.subplot(121); plot_digit(X_test_mod[some_index])
plt.subplot(122); plot_digit(y_test_mod[some_index])
plt.show()

 

 -좌측은 노이즈를 섞은 데이터로 훈련기에 넣으면 노이즈를 제거하여 원래 이미지를 예측해낼 것

 -우측은 원래의 이미지

knn_clf.fit(X_train_mod,y_train_mod)
clean_digit=knn_clf.predict([X_test_mod[some_index]])
plot_digit(clean_digit)

 -노이즈가 있던 왼쪽의 이미지를 분류기에 넣어 분류기가 원래 이미지를 분류해낸 모습

1. 사용 데이터

 -사이킷런에서 제공하는 MNIST 데이터셋(손글씨 데이터셋)

# 사이킷런에서 기본적으로 제공하는 MNIST 데이터셋 불러오기
from sklearn.datasets import fetch_openml
mnist=fetch_openml('mnist_784',version=1,as_frame=False)
mnist.keys()

### 결과 ###
dict_keys(['data', 'target', 'frame', 'categories', 'feature_names', 'target_names', 'DESCR', 'details', 'url'])

 

 -MNIST 데이터셋 배열 확인

X,y=mnist['data'],mnist['target']
X.shape     # (70000, 784)
y.shape     # (70000,)

# 이미지가 70000개이고 각 이미지는 784개의 특성을 가짐(28*28 픽셀의 이미지이므로 28*28=784개의 특성을 가짐)
# 각 특성은 0(흰색)~255(검은색)까지의 픽셀 강도

 

 -가장 첫번째 데이터 출력해보기

 -가로 28개, 세로 28개의 픽셀로 이루어짐

import matplotlib as mpl
import matplotlib.pyplot as plt
import numpy as np

# 가장 첫번째 데이터 불러와서 28*28의 배열로 재배열
some_digit=X[0]
some_digit_image=some_digit.reshape(28,28)

# imshow()함수를 통해 이미지 확인
plt.imshow(some_digit_image, cmap='binary')
plt.axis('off')
plt.show()

 -이미지 여러개 확인해보기

import matplotlib as mpl

# 그림 그리기 기능 함수
def plot_digit(data):
    image=data.reshape(28,28)
    plt.imshow(image,cmap=mpl.cm.binary,interpolation='nearest')
    plt.axis('off')

def plot_digits(instances, images_per_row, **options):
    size=28
    images_per_row=min(len(instances),images_per_row)
    n_rows=(len(instances)-1) // images_per_row +1

    # 필요하면 그리드의 끝을 채우기 위해 빈 이미지 추가
    n_empty=n_rows*images_per_row-len(instances)
    padded_instances=np.concatenate([instances, np.zeros((n_empty, size*size))],axis=0)

    # 배열의 크기를 바꿔 28*28 이미지를 담은 그리드로 구성
    image_grid=padded_instances.reshape((n_rows, images_per_row, size, size))

    # 축 0(이미지 그리드의 수직축)과 2(이미지의 수직축)를 합치고 축 1과 3(그리드와 이미지의 수평축)을 합침
    # transpose()를 통해 결합하려는 축을 옆으로 이동한 다음 합침
    big_image=image_grid.transpose(0, 2, 1, 3).reshape(n_rows*size,images_per_row*size)

    # 하나의 큰 이미지 출력
    plt.imshow(big_image, cmap=mpl.cm.binary, **options)
    plt.axis('off')

# 이미지 샘플 100개 출력
plt.figure(figsize=(9,9))
example_images=X[:100]
plot_digits(example_images,10)
plt.show()

 -레이블 확인

y[0]

### 결과 ###
'5'

 -레이블이 문자형으로 되어있고 머신러닝 알고리즘은 대부분 숫자형의 레이블을 계산하므로 숫자형으로 변환이 필요

y=y.astype(np.uint8)

# train 데이터와 test 데이터 분리(train 6만개, test 1만개)
X_train,X_test,y_train,y_test=X[:60000],X[60000:],y[:60000],y[60000:]

 

 

 

2. 이진 분류기 훈련

 -5인지 5가 아닌지만 분류해보기

# 5인 것만 True, 다른 숫자는 False
y_train_5=(y_train==5)
y_test_5=(y_test==5)

# 확률적 경사하강법(SGD) 사용
from sklearn.linear_model import SGDClassifier
sgd_clf=SGDClassifier(random_state=42)
sgd_clf.fit(X_train,y_train_5)

# 앞에서 살펴봤던 가장 첫번째 데이터를 예측기에 넣어본 결과 True(5)로 예측
sgd_clf.predict([some_digit])

### 결과 ###
array([ True])

 

 

3. 교차 검증을 통한 성능 측정

from sklearn.model_selection import StratifiedKFold
from sklearn.base import clone

# StratifiedKFold는 클래스별 비율이 유지되도록 계층적 샘플링 수행
# 3개의 서브셋으로 나눔
skfolds=StratifiedKFold(n_splits=3,random_state=42,shuffle=True)

# X_train과 y_train_5를 3개의 서브셋으로 나누고 각 서브셋의 인덱스를 받아 해당 인덱스의 데이터만으로 훈련과 검증을 반복(총 세번 반복)
# clone은 훈련되지 않은 새로운 분류기를 생성해줌
for train_index,test_index in skfolds.split(X_train,y_train_5):
     clone_clf=clone(sgd_clf)
     X_train_folds=X_train[train_index]        # X_train 데이터에서 train으로 분류된 데이터
     y_train_folds=y_train_5[train_index]     # y_train 데이터에서 train으로 분류된 데이터
     X_test_fold=X_train[test_index]           # X_train 데이터에서 test로 분류된 데이터
     y_test_fold=y_train_5[test_index]        # y_train 데이터에서 test로 분류된 데이터

     clone_clf.fit(X_train_folds,y_train_folds)     # 훈련되지 않은 새로운 분류기에 train데이터 훈련
     y_pred=clone_clf.predict(X_test_fold)        # test 데이터 예측
     n_correct=sum(y_pred==y_test_fold)       # 정확하게 예측한 데이터 개수
     print(n_correct/len(y_pred))                     # (정확하게 예측한 데이터 개수 / 전체 데이터 개수)로 정확도 계산

### 결과 ###
0.9669
0.91625
0.96785

 -세 번의 교차 검증 결과, 정확하게 예측한 데이터 개수의 비율이 0.9 이상으로 정확도가 90% 이상임

 

4. 오차 행렬을 통한 성능 측정

 -오차 행렬 구하기

# cross_val_predict는 cross_val_score가 평가 점수를 반환하는 것과 다르게 각 테스트셋의 예측값을 반환(훈련에 쓰이지 않은 값들의 예측값)
from sklearn.model_selection import cross_val_predict

# 앞에서 쓴 확률적 경사하강법 모델에 X_train으로 학습시키는 과정을 3번 반복
y_train_pred=cross_val_predict(sgd_clf,X_train,y_train_5,cv=3)

# 실제 y_train_5값과 예측된 y_train_pred값을 비교하여 오차행렬 생성
from sklearn.metrics import confusion_matrix
confusion_matrix(y_train_5,y_train_pred)

### 결과 ###
array([[53892,   867],
          [1891,   3530]])

# True Negative(TN, 실제: 음성, 예측: 음성) : 53892     False Positive(FP, 실제: 음성, 예측: 양성)  : 687
# False Negative(FN, 실제: 양성, 예측: 음성): 1891      True Positive(TP, 실제: 양성, 예측: 양성)   : 3530

 

 -정밀도, 재현율 구하기

from sklearn.metrics import precision_score,recall_score
# 정밀도: TP/(TP+FP)
precision_score(y_train_5,y_train_pred) # 3530 / (3530+687)

# 재현율: TP/(TP+FN)
recall_score(y_train_5, y_train_pred)   # 3530 / (3530+1891)

# F-1 점수: 정밀도와 재현율의 조화 평균, 2 / (1/정밀도)+(1/재현율) = 2*(정밀도*재현율) / (정밀도+재현율)
from sklearn.metrics import f1_score
f1_score(y_train_5,y_train_pred)

 -정밀도와 재현율은 같이 높일 수 없고 한쪽이 높아지면 다른 한 쪽은 낮아지는 트레이드오프 관계이므로 적절한 선에서 타협을 봐야함

 -f1-score는 정밀도와 재현율의 조화 평균으로 f1-score가 클수록 정밀도와 재현율이 비슷함

 -예시 1) 어린이에게 안전한 동영상만 컬러내는 분류기 훈련 가정

  →재현율이 높고 정밀도가 낮음: 실제 양성 중 양성으로 예측한 비율이 높음

                                                      →예측한 것 중에 나쁜 동영상이 있을수 있음

  →재현율이 낮고 정밀도가 높음: 양성으로 예측한 것 중 실제 양성인 비율이  높음

                                                     →예측한 것에 좋은 동영상의 비율이 높으므로 안전한 동영상만 있을 확률이 더 높음

 

 -예시 2) 감시 카메라로 좀도둑을 잡는 분류기 훈련 가정

  →재현율이 높고 정밀도가 낮음: 실제 양성 중 양성으로 예측한 비율이 높음

                                                      →실제 도둑을 도둑으로 예측할 확률이 높아 도둑을 잡을 확률이 높아지지만 경비원이 오인 출동할 확률도 있음

  →재현율이 낮고 정밀도가 높음: 양성으로 예측한 것 중 실제 양성인 비율이  높음

                                                     →도둑이라고 예측했다면 진짜 도둑일 확률이 높이만 애초에 도둑이라고 예측할 비율은 예상할 수 없음

 

 -데이터의 점수에 대한 정밀도와 재현율 그래프 그리기

# 그래프 그리기 전에 먼저 모든 데이터의 점수 받아오기
y_scores=cross_val_predict(sgd_clf,X_train,y_train_5, cv=3,method='decision_function')

# 가능한 모든 임계값에 대한 정밀도와 재현율 계산하는 함수
from sklearn.metrics import precision_recall_curve
precisions,recalls,thresholds=precision_recall_curve(y_train_5,y_scores)

def plot_precision_recall_vs_threshold(precisions,recalls,threshods):
    plt.plot(thresholds,precisions[:-1],'b--',label='정밀도')   # x축에 임계값, y축에 정밀도, 색깔은 blue, 선 종류는 긴 점선
    plt.plot(thresholds,recalls[:-1],'g',label='재현율')        # x축에 임계값, y축에 재현율, 색깔은 green, 선 종류는 실선

plot_precision_recall_vs_threshold(precisions,recalls,thresholds)
plt.show()
# 재현율은 임계값이 올라감에 따라 무조건 줄어들지만 정밀도는 가끔 낮아질 때가 있어 울퉁불퉁한 그래프가 그려짐

# 재현율과 정밀도에 관한 그래프
# 정밀도가 급격하게 줄어드는 지점(이 그래프에서는 재현율이 80%인 지점 쯤) 직전에서 정밀도와 재현율을 결정하는 것이 좋음(이 그래프에서는 재현율이 60%인 지점 쯤)
plt.plot(recalls[:-1],precisions[:-1])

 -정밀도와 재현율 사이에서 임계값 찾기

 -정밀도와 재현율 중 한 점수에 대해 목표를 잡고 그 목표에 대한 임계값 구하기

# 정밀도 90% 달성이 목표라고 했을 때 정밀도가 최소 90%가 되는 가장 낮은 임계값 찾기
# np.argmax()는 최대값의 첫번째 인덱스 반환(여기서는 True의 첫번째 인덱스 반환)
threshold_90_precision=thresholds[np.argmax(precisions>=0.90)]

# 훈련 세트에 대한 예측
y_train_pred_90=(y_scores>=threshold_90_precision)
# 예측에 대한 정밀도와 재현율
precision_score(y_train_5,y_train_pred_90)  # 0.9000345901072293
recall_score(y_train_5,y_train_pred_90)     # 0.4799852425751706
# 재현율이 너무 낮으므로 정밀도가 높아도 유용하지 않은 분류기임

 -정밀도가 90% 이상인 점을 찾아 정밀도는 90%가 넘도록 나왔지만 재현율은 0.48정도로 낮음

 -유용하지 않은 분류기

 

 

5. ROC 곡선과 AUC 점수

 -ROC 곡선은 재현율(진짜 양성 비율)과 FPR(거짓 양성 비율, FP / (FP+TN), 1-특이도)의 곡선

from sklearn.metrics import roc_curve
fpr, tpr, thresholds=roc_curve(y_train_5, y_scores)

def plot_roc_curve(fpr, tpr, label=None):
    plt.plot(fpr, tpr, linewidth=2, label=label)
    plt.plot([0, 1],[0, 1], 'k--')

plot_roc_curve(fpr, tpr)
plt.show()

 -점선은 완전 랜덤 분류기의 성능으로 가장 안좋은 상황

 -좋은 분류기일수록 점선에서 떨어져 왼쪽 위의 모서리로 가까워짐

 

 -AUC 점수는 곡선 아래의 면적(Area Under Curve)으로 분류기의 성능을 비교하기 위해 계산

 -완전 랜덤일 때(점선일 때) 0.5, 가장 좋을 때(왼쪽 위의 모서리가 꼭짓점으로 있을 때) 1

from sklearn.metrics import roc_auc_score
roc_auc_score(y_train_5,y_scores)

### 결과 ###
0.9604938554008616

데이터: 핸즈온 머신러닝(2판)의 예제 데이터(https://github.com/rickiepark/handson-ml2/blob/master/datasets/housing/housing.csv) 사용

 

 

1. 문제 정의

예제 데이터는 캘리포니아의 블록 그룹마다 인구, 중위소득, 중간주택가격 등의 변수를 담은 데이터

※블록 그룹: 미국 인구조사국이 샘플 데이터 발표에 사용하는 최소한의 지리적 단위, 보통 600명~3,000명의 인구

 

● 첫번째 질문: 비즈니스의 목적이 무엇인가?

 -모델을 이용해 어떻게 이익을 얻으려는 건지?

 -문제를 어떻게 구성할지, 어떤 알고리즘을 선택할지, 어떤 성능 지표로 모델을 평가할지, 모델 튜닝을 어느 정도로 빡세게 할지 경정하는데 중요

 

ex 답변) 다른 블록 그룹의 데이터가 주어졌을 때 중간 주택 가격을 예측하여 해당 지역에 투자할 가치가 있는지 평가하는 것이 문제

 

 

● 두번째 질문: 현재 솔루션은 어떻게 구성되어 있는가?

 -문제 해결 방법에 대한 정보 또는 참고 성능으로 활용 가능

 

ex 답변) 한 팀이 블록 그룹의 최신 정보를 모으고 전문가들이 복잡한 규칙을 통해 수동으로 예측 중

→인구조사 데이터는 인구 관련 데이터에 더해 블록 그룹의 중간 주택 가격 데이터를 포함하므로 매우 적합한 데이터셋으로 보임

 

 

● 세번째 질문: 지도/비지도/강화 학습 중 무엇?

                        분류/회귀 중 무엇?

                        배치 학습/온라인 학습 중 무엇?

→중간 주택 가격이 나와있는 훈련 샘플이 있으므로 지도 학습 작업

→중간 주택 가격이라는 값을 예측해야하므로 회귀 문제

→예측에 사용할 특성이 인구, 중간 소득 등 한 개 이상이므로 다중 회귀

→중간 주택 가격 한 개의 값만 예측하므로 단변량 회귀(두 개이상 예측 시 다변량 회귀)

→데이터에 연속적 흐름 없이 고정된 데이터이고 크기도 작으므로 배치 학습이 적정

 

 

 성능 평가 지표 선택

 -회귀 성능 평가 지표: MAE / MSE / RMSE / MSLE / RMSLE / R²

 -분류 성능 평가 지표: 정확도 / 오차행렬 / 정밀도 / 민감도(재현율) / 특이도 / F1 Score / ROC / AUC

 

 

위에서 설정한 가정들 검사해보기

 

 

2. 데이터 가져오기

데이터를 다운로드한 뒤 파이썬 개발환경에 불러오기

import pandas as pd

housing=pd.read_csv('housing.csv',encoding='cp949')

 

데이터 구조 훑어보기

# 불러온 데이터의 처음 다섯 행 확인(괄호안에 숫자 지정하여 더 많은 데이터 확인 가능)
housing.head()

# 불러온 데이터의 마지막 다섯 행 확인(괄호안에 숫자 지정하여 더 많은 데이터 확인 가능
housing.tail()

# 데이터의 간략한 설명, 전체 행 수, 각 변수의 데이터 타입, 결측값(NULL) 개수 확인
housing.info()

# 범주형 변수의 확인
housing['ocean_proximity'].value_count()
# 각 범주별 개수 확인 가능

# 숫자형 변수의 특성(개수, 평균, 표준편차, 최소값, 최대값, 사분위수) 요약
housing.describe()
# 시각적으로 데이터의 분포 알아보기
import matplotlib.pyplot as plt
housing.hist(bins=50,figsize=(20,15))
plt.show()
# 각 변수의 히스토그램을 통해 분포 확인 가능

 

 

데이터 탐색과 시각화(EDA, 탐색적 자료분석)

 -지리적 데이터 시각화: 데이터에 위도(latitude)와 경도(longitude)가 있으므로 이를 이용해 산점도에 위치 데이터 시각화

# plot의 종류는 산점도로, x축에 경도 값, y축에 위도 값 설정, alpha=0.1로 설정하여 투명도 낮추어 밀집된 곳 파악
housing.plot(kind='scatter', x='longitude', y='latitude', alpha=0.1)

 -plot() 함수의 각종 매개변수를 설정해 다양한 값을 한번에 시각화

housing.plot(kind='scatter', x='longitude', y='latitude', alpha=0.4,
                   s=housing['population'],label='population',figsize=(10,7),
                   c='median_house_value', cmap=plt.get_cmap('jet'), colorbar=True,
                   sharex=False)
plt.legend()

 -주택가격은 바다와 밀접한 곳, 인구 밀도 등과 관련이 크다는 점 발견

 

 

 상관관계 조사

 -상관계수를 통해 확인

# housing 데이터의 상관계수를 median_house_value 변수에 대한 상관계수만 내림차순으로 출력
housing.corr()['median_house_value'].sort_values(ascending=False)

 

 -산점도를 통해 확인

# 숫자형 변수 사이의 산점도를 그려주는 판다스 함수
from pandas.plotting import scatter_matrix

# 상관계수 확인 결과 median_house_value와 상관관계가 높아보이는 3개의 변수만 확인
attributes=['median_house_value', 'median_income', 'total_rooms', 'housing_median_age']
scatter_matrix(housing[attributes], figsize=(12,8))

 

 -확인 결과  median_house_value와 median_income이 특히 선형적인 관계가 강한 것을 확인

housing.plot(kind='scatter', x='median_income', y='median_house_value', alpha=0.1)

 -위 그래프에서도 median_house_value가 500000, 450000, 350000일 때 수평선이 확인되며 알고리즘이 이 수평선을 학습하지 않도록 제거해야할 필요가 있음

 

 

-이외에도 특성의 분포에 따라 로그 스케일, Min-Max 스케일 등 변환 과정을 거쳐야 함

 

 특성 조합

 -분포를 정규화하고 상관관계를 확인한 뒤 마지막으로 해볼 수 있는 것은 특성 조합하여 새로운 특성(파생변수) 만들기

 -예시에서 '방 개수'보다는 '가구당 방 개수'가 더 유용할 것이므로 '가구 수'와 '방 개수' 변수를 조합해 '가구당 방 개수' 변수 만들기

housing['rooms_per_household']=housing['total_rooms']/housing['households']
housing['bedrooms_per_room']=housing['total_bedrooms']/housing['total_rooms']
housing['population_per_household']=housing['population']/housing['households']

 -새로 만든 bedrooms_per_room 변수가 전체 방 개수나 침실 개수보다 중간 주택 가격과 더 큰 상관관계를 가짐

 

 -여러 파생변수들을 만들고 결과를 확인하는 반복적인 과정을 통해 좋은 모델 만들 수 있음

 

 

 

3. 데이터 전처리

housing 데이터에서 예측해야하는 median_house_value 변수는 분리하기

housing_labels=housing['median_house_value']

 

수치형 변수 결측값 처리

 -해당 구역을 제거(dropna())

 -전체 특성 제거(drop())

 -어떤 값(0, 평균, 중간값 등)으로 채움(fillna(), SimpleImputer)

 -SimpleImputer 사용예시

from sklearn.impute import SimpleImputer

# SimpleImputer 객체 생성, 중간값을 채우는 것으로 설정
imputer=SimpleImputer(strategy='median')

# 숫치형 변수에 대해 중간값으로 결측값을 채울 것이므로 범주형 변수인 ocean_proximity와 예측할 변수인 median_house_value는 제거
housing_num=housing.drop(['ocean_proximity','median_house_value'],axis=1)

imputer.fit(housing_num)

# statistics_에 각 변수별로 채울 값을 저장(여기서는 중간값)
imputer.statistics_

# housing_num의 숫치형 변수를 피팅한 imputer를 housing_num에 적용
X=imputer.transform(housing_num)

# 결측값이 중간값으로 대체된 데이터에 원래의 변수명과 인덱스를 적용해 원래 데이터 형태로 만들기
housing_tr=pd.DataFrame(X,columns=housing_num.columns,index=housing_num.index)

 

 

 

 범주형 변수 인코딩

 -범주형 변수인 ocean_proximity의 특성 살펴보기

# 범주형 변수인 ocean_proximity만 housing_cat에 저장하고 확인
housing_cat=housing[['ocean_proximity']]
housing_cat

 -OrdinalEncoder, OneHotEncoder, LabelEncoder 등의 인코더를 사용하여 범주형 변수를 수치형 변수로 변환

 -OrdinalEncoder 사용예시

from sklearn.preprocessing import OrdinalEncoder

# ordinalencoder 객체 생성
ordinal_encoder=OrdinalEncoder()

# 범주형 변수인 housing_cat을 ordinalencoder에 피팅하고 변환까지 적용
housing_cat_encoded=ordinal_encoder.fit_transform(housing_cat)
housing_cat_encoded

# 인코딩에 사용된 범주 확인
ordinal_encoder.categories_

 

 

 

 -OneHotEncoder 사용예시

from sklearn.preprocessing import OneHotEncoder

# OneHotEncoder 객체 생성
cat_encoder=OneHotEncoder()

# 범주형 변수인 housing_cat을 인코더에 피팅하고 변환
housing_cat_1hot=cat_encoder.fit_transform(housing_cat)
housing_cat_1hot

# Sparse Row(희소행렬) 형태로 출력되므로 toarray()를 사용하여 넘파이 배열의 형태로 출력할 수 있음
housing_cat_1hot.toarray()

# 인코딩에 사용된 범수 확인
cat_encoder.categories_

 

 

 특성 스케일링

 -모든 특성의 범위를 같게 해줌(0~1 사이 또는 -1~1 사이 등)

 -Min-Max 스케일링, 표준화(StandardScalling) 등

 

 

 변환 파이프라인

 -앞에서 했던 변환을 순서대로 처리할 수 있도록 파이프라인 생성 가능

from sklearn.pipeline import Pipeline
from sklearn.preprocessing import StandardScaler
num_pipeline=Pipeline([('imputer',SimpleImputer(strategy='median')),    # SimpleInputer를 사용해 결측값을 중간값으로 설정
                                   ('attribs_adder',CombinedAttributesAdder()),      # 변수 조합 생성
                                   ('atd_scaler', StandardScaler())                         # 정규화를 통해 범위 조정
                                  ])

housing_num_tr=num_pipeline.fit_transform(housing_num)

 

 -ColumnTransformer를 사용하면 파이프라인 하나에 범주형과 수치형을 동시에 넣어 열마다 처리할 수 있음

from sklearn.compose import ColumnTransformer
num_attribs=list(housing_num)	# 수치형 변수열
cat_attribs=['ocean_proximity']	# 범주형 변수열

full_pipeline=ColumnTransformer([('num',num_pipeline,num_attribs),          # 수치형 변수열에는 앞에서 생성한 수치형 변수 변환 파이프라인 적용
                                                  ('cat',OneHotEncoder(),cat_attribs)])       # 범주형 변수열에는 OneHotEncoder 적용하여 수치형 변수로 변환

housing_prepared=full_pipeline.fit_transform(housing)

 

 

 

 

4. 모델 선택과 훈련

 훈련 세트에서 훈련, 평가

 -전처리를 끝낸 데이터를 적절한 알고리즘을 선택해서 훈련시키기

# 선형 회귀 모델
from sklearn.linear_model import LinearRegression
# 선형 회귀 모델 객체 생성
lin_reg=LinearRegression()
# 데이터를 선형 회귀 모델에 훈련시키기
lin_reg.fit(housing_prepared,housing_labels)


# 의사결정나무 모델
from sklearn.tree import DecisionTreeRegressor
# 의사결정나무 모델 객체 생성
tree_reg=DecisionTreeRegressor()
# 데이터를 의사결정나무 모델에 훈련시키기
tree_reg.fit(housing_prepared, housing_labels)

 -훈련된 모델의 성능 평가

from sklearn.metrics import mean_squared_error

# 선형회귀모델의 성능 평가
# 훈련된 모델에 데이터를 넣었을 때 예측값 계산
housing_predictions=lin_reg.predict(housing_prepared)

# 평균제곱오차(mse) 계산
lin_mse=mean_squared_error(housing_labels,housing_predictions)

# mse에 루트를 씌운 값은 rmse 계산
lin_rmse=np.sqrt(lin_mse)

# rmse값
lin_rmse

### 결과 ###
1.6715935001871568e-10
from sklearn.metrics import mean_squared_error

# 의사결정나무 모델의 성능 평가
# 훈련된 모델에 데이터를 넣었을 때 예측값 계산
housing_predictions=tree_reg.predict(housing_prepared)

# 평균제곱오차(mse) 계산
tree_mse=mean_squared_error(housing_labels,housing_predictions)

# mse에 루트를 씌운 값은 rmse 계산
tree_rmse=np.sqrt(tree_mse)

# rmse값
tree_rmse

### 결과 ###
0.0

 -성능 평가 지표의 값이 거의 0에 가깝거나 0이 나온 것은 훈련 데이터에 모델이 과대적합(오버피팅) 되었기 때문

 

 

 교차 검증을 통한 평가

 -훈련 데이터를 정해진 개수(CV)만큼의 서브세트로 무작위로 분할하고 1개의 서브세트를 평가에 활용하고 나머지 서브세트를 훈련에 활용하는 방법

from sklearn.model_selection import cross_val_score

# 의사결정나무 모델에서 교차검증을 통해 mse계산
# 사이킷런의 교차검증 기능은 scoring 매개변수에 낮을수록 좋은 비용함수가 아닌 높을수록 좋은 효용함수를 기대하므로
# neg_mean_squared_error를 계산하여 낮을수록 좋은 점수로 만듦
scores=cross_val_score(tree_reg,housing_prepared,housing_labels,scoring='neg_mean_squared_error',cv=10)
tree_rmse_scores=np.sqrt(-scores)


# 서브세트 개수만큼 계산된 rmse의 평균과 표준편차를 계산
def display_scores(scores):
    print('점수:',scores)
    print('평균:',scores.mean())
    print('표준편차:',scores.std())

display_scores(tree_rmse_scores)

# 선형회귀 모델에도 적용하여 선형회귀 모델을 교차검증하였을 때 점수 계산
lin_scores=cross_val_score(lin_reg,housing_prepared,housing_labels,scoring='neg_mean_squared_error',cv=10)
lin_rmse_scores=np.sqrt(-lin_scores)
display_scores(lin_rmse_scores)

 -선형회귀 모델이 의사결정나무 모델보다 과대적합이 덜하여 교차 검증을 했을 때도 더 좋은 점수를 가짐

 

 

 

 

5. 모델 세부 튜닝

 -모델에 사용되는 하이퍼 파라미터 등을 조율하며 가장 좋은 점수를 도출하는 하이퍼 파라미터를 찾아 모델에 적용시키기

 

 그리드 탐색: 탐색하고자 하는 하이퍼 파라미터를 전부 지정하면 모든 하이퍼 파라미터 조합에 대해 교차 검증을 사용해 평가

 -랜덤 포레스트 사용 예시

from sklearn.ensemble import RandomForestRegressor
from sklearn.model_selection import GridSearchCV

# 그리드 서치에 적용하여 탐색할 파라미터들 지정
param_grid=[{'n_estimators':[3,10,30],'max_features':[2,4,6,8]},
            {'bootstrap':[False],'n_estimators':[3,10],'max_features':[2,3,4]}]
# 첫번째 딕셔너리에서 n_estimators가 각각 3, 10 ,30일 때와 max_features가 각각 2, 4, 6, 8일 때의 조합으로 총 12번 평가
# 두번째 딕셔너리에서는 총 6번 평가하며 bootstrap은 False로 설정
# 총 12+6=18개의 조합을 탐색하고 CV=5로 교차검증을 5번 시도하여 총 훈련횟수는 18*5=90번


# 랜덤포레스트 객체 생성
forest_reg=RandomForestRegressor()

# 랜텀포레스트 모델과 파라미터, 교차검증 횟수, 평가 지표 등을 매개변수로 지정한 그리드 서치 모델 객체 생성
grid_search=GridSearchCV(forest_reg,param_grid,cv=5,scoring='neg_mean_squared_error',return_train_score=True)

# 데이터를 그리드 서치 모델에 피팅하여 최적의 하이퍼 파라미터 도출
grid_search.fit(housing_prepared,housing_labels)

# 최적의 하이퍼 파라미터 출력
gird_search.best_params_

### 결과 ###
{'max_features': 8, 'n_estimators': 30}

 

랜덤 탐색: 지정한 하이퍼 파라미터를 랜덤으로 조합하여 교차 검증을 사용해 평가

 

 

 중요도 탐색: 모델이 정확한 예측을 하기 위한 각 변수의 상대적인 중요도

# 각 변수의 상대적인 중요도 계산
feature_importances=grid_search.best_estimator_.feature_importances_

# 계산된 중요도와 변수의 이름을 짝을 지어 표시
extra_attribs=['rooms_per_hhold', 'pop_per_hhold', 'bedrooms_per_room']

# 범주형 변수의 각 카테고리도 중요도를 탐색할 변수로 포함
cat_encoder=full_pipeline.named_transformers_['cat']
cat_one_hot_attribs=list(cat_encoder.categories_[0])

# 모든 변수들을 합쳐서 각 변수의 중요도와 짝을 지어 정렬
attributes=num_attribs+extra_attribs+cat_one_hot_attribs
sorted(zip(feature_importances,attributes),reverse=True)

 

 

 

 

6. 테스트 세트로 최종 모델 평가

pred=forest_reg.predict(test)

 -위에서 train 데이터셋으로 훈련시킨 랜덤 포레스트 모델에 test 데이터셋을 넣으면 test 데이터셋에 대한 예측값이 pred변수에 저장됨

1. 사용 데이터

 -행정경계(읍면동): 국가공간정보포털 오픈마켓 / SHP파일

 

 

 -토지이용현황(창원): 국가공간정보포털 오픈마켓 / SHP파일

 

 

 

2. 분석과정

1) 전국 읍면동 지도에서 창원시의 읍면동만 추출하기

 -읍면동 속성테이블 내의 BJCD(법정동 코드 이용)

 -창원시의 법정동코드 앞의 5자리는 48121(창원시 의창구), 48123(창원시 성산구), 48125(창원시 마산합포구), 48127(창원시 마산회원구), 48129(창원시 진해구)로 이루어짐

 -따라서 표현식을 "BJCD" >= 4812100000 and "BJCD" < 4813000000 으로 하여 객체를 선택하면 아래와 같이 창원시의 읍면동만 선택됨

 

 

 -이제 선택한 객체를 다른 이름으로 저장하면 창원시의 읍면동만 있는 SHP파일이 생성됨

 

 

 

2) 창원시 토지이용현황도에서 침엽수림, 활엽수림, 혼합수림만 추출

 -토지이용현황도와 함께 다운받은 토지이용현황도_분류항목(코드)에서 침엽수림(2210), 활엽수림(2220), 혼합수림(2230) 코드를 확인

 

 -표현식을 각각 "UCB"=2210, "UCB"=2220, "UCB"=2230으로 하여 침엽수림, 활엽수림, 혼합수림만 추출

 

 

3) 잘라내기를 사용하여 창원시 읍면동 내의 수림만 잘라내기

벡터 > 지리 정보 처리 도구 > 잘라내기

입력레이어: 창원시 읍면동  /  중첩레이어: 창원시 침엽수림

회색 부분인 실행의 산출물로 전체 침엽수림 중 창원시 읍면동 내부에 있는 부분만 선택됨

나머지 수림에 대해서도 잘라내기를 실행하고 산출물을 저장해주면 읍면동별 침엽수림, 활엽수림, 혼합수림 잘라내기는 완료

 

 

 

 

4) 읍면동별 침엽수림, 활엽수림, 혼합수림의 면적 계산하여 열 추가하기

수림별로 추출한 각 레이어의 속성테이블 > 필드 계산기에서 $area를 통해 면적을 계산한 열을 추가

 

 

 

5) 면적을 계산한 열을 읍면동 레이어에 결합하여 저장

창원시읍면동 레이어에서 속성 > 결합 > +버튼

 -결합 레이어는 읍면동별로 잘라내기한 각 수림의 레이어를 선택

 -결합필드와 대상필드는 각 레이어의 유일한 값인 UFID 또는 BJCD 선택

 -결합된 필드는 면적을 계산한 열만 선택

 -사용자 정의 필드명 접두어는 결합시 열 앞에 붙는 이름으로 있으면 복잡해지므로 공백으로 설정

 

 

 

6) 결과

창원시읍면동 레이어의 속성 테이블을 열어 확인하면 다음과 같이 면적을 계산한 열이 포함되어 있음

2-10. 고윳값과 고유벡터 찾기

고유벡터: 정방 행렬 A를 선형 변환으로 봤을 때, 선형 변환 A에 의한 변환 결과가 자기 자신의 상수 배가 되는 0이 아닌 벡터

고유값: 이때의 상수배 값

Av: v라는 열 벡터에 선형 변환 A를 해주었다

Av = λv: v라는 열 벡터에 선형 변환 A를 해준 결과가 열 벡터v의 상수 배(λ)와동일

               즉, 벡터 v에 대해 선형 변환 A를 해주었을 때, 벡터 v의 방향은 변하지 않고 크기만 변함

그림 1) 출처: 공돌이의 수학정리노트 유튜브
그림 2) 출처: 공돌이의 수학정리노트 유튜브

그림 2)는 그림 1)을 선형변환한 것

파란색 화살표는 크기는 변했지만 방향은 그대로이므로 변한 크기 비율인 λ를 고유값으로하는 고유벡터

분홍색 화살표도 방향은 그대로이며 크기도 그대로이므로 λ=1인 고유벡터

빨간색 화살표는 방향과 크기가 모두 변했으므로 고유벡터가 아님

 

 

고유벡터의 성질로 det(A - λI)=0이어야하며 이를 이용해 고유값과 고유벡터를 구할 수 있음

A는 주어진 행렬

λ는 구해야하는 고유값

I는 A와 같은 크기의 단위행렬

행렬식을 이용해 λ를 구하면 Av = λv식에 λ를 대입하여 v 구하기

 

파이썬에서는 linalg.eig 함수 사용

import numpy as np

matrix=np.array([[1, -1, 3],
                          [1, 1, 6],
                          [3, 8, 9]])

eigenvalues, eigenvectors=np.linalg.eig(matrix)
print('고유값: ', eigenvalues)
print('고유벡터: ', eigenvectors)

### 결과 ###
고유값: array([13.55075847, 0.74003145, -3.29078992])
고유벡터: array([[-0.17511017, -0.96677403, -0.53373322],
                        [-0.435951  , 0.2053623  , -0.64324848],
                        [-0.88254925, 0.15223105 , 0.54896288]])

대칭행렬일 경우 np.linalg.eigh(matrix)를 사용하면 더 빨리 계산(대칭행렬 여부를 확인하지는 않음)

 

 

 

2-11. 점곱 계산하기

점곱은 행렬의 같은 위치의 성분끼리 곱해준 것

import numpy as np

vector_a=np.array([1,2,3])
vector_b=np.array([4,5,6])

np.dot(vector_a,vector_b)

### 결과 ###
32


# np.dot 대산에 @를 사용할 수도 있음
vector_a @ vector_b

### 결과 ###
32

vector_a와 vector_b의 점곱은 1×4 + 2×5 + 3×6=32

 

 

 

2-12. 행렬 곱셈

행렬의 곱셈

첫번째 행렬 첫번째 행 × 두번째 행렬 첫번째 열      첫번째 행렬 첫번째 행 × 두번째 행렬 두번째 열

첫번째 행렬 두번째 행 × 두번째 행렬 첫번째 열      첫번째 행렬 두번째 행 × 두번째 행렬 두번째 열

 

파이썬에서는 np.dot사용

import numpy as np

matrix_a=np.array([[1,1],
                              [1,2]])

matrix_b=np.array([[1,3],
                              [1,2]])

np.dot(matrix_a,matrix_b)

### 결과 ###
array([[2,5],
          [3,7]])


# np.dot 대신에 @ 사용 가능
matrix_a @ matrix_b

### 결과 ###
array([[2,5],
          [3,7]])


# 원소별 곱셈을 하려면 * 사용
matrix_a * matrix_b

### 결과 ###
array([[1,3],
          [1,4]])

 

 

※ np.dot과 np.matmul의 차이

 -(a,b,c,D) 크기의 4차원 배열은 c행 D열의 행렬 b개의 있는 행렬이 a개 있는 행렬

 -(e,f,D,h)크기의 4차원 배열은 D행 h열의 행렬 f개 있는 행렬이 e개 있는 행렬

 -np.dot은 (a,b,c,D) 크기의 배열과 (e,f,D,h) 크기의 배열의 곱셈의 결과로 (a,b,c,e,f,h)크기의 배열을 만듦

 -@(np.matmul)는 (a,b,c,D) 크기의 배열과 (e,f,D,h) 크기의 배열의 곱셈의 결과로 (a',b',c,h)크기의 배열을 만듦

    →a와 e는 같거나 둘 중 하나는 1, b와 f는 같거나 둘 중 하나는 1이어야 함

    →a'와 b'는 각각 a와 e, b와 f 중 1이 아닌 값

 

 

 

2-13. 역행렬

정방행렬의 역행렬은 정방행렬과 곱했을 때 단위행렬 I가 되는 행렬

파이썬에서는 np.linalg.inv 사용

import numpy as np
matrix=np.array([[1,4],
                          [2,5]])

np.linalg.inv(matrix)

### 결과 ###
array([[-1.66666667,  1.33333333],
          [ 0.66666667, -0.33333333]])


# 행렬과 역행렬을 곱하면 대각원소가 전부 1이고 나머지 원소는 0인 단위행렬이 나옴
matrix @ np.linalg.inv(matrix)

### 결과 ###
array([[1., 0.],
          [0., 1.]])

 

정방행렬이 아닌 행렬의 역행렬을 유사 역행렬이라고 부르며 계산할 수 있음, np.linalg.pinv() 사용

import numpy as np

matrix=np.array([[1,4,7],
                          [2,5,8]])

np.linalg.pinv(matrix)

### 결과 ###
array([[-1.6666667,  1.],
          [-0.3333333,  0.3333333],
          [ 0.5      , -0.3333333]])

'Python > 기본문법' 카테고리의 다른 글

데이터 랭글링 (2)  (0) 2023.06.26
데이터 랭글링 (1)  (0) 2023.06.23
데이터 적재  (0) 2023.06.22
Numpy로 배열, 벡터, 행렬 이해하기 (2)  (0) 2022.10.19
Numpy로 배열, 벡터, 행렬 이해하기 (1)  (0) 2022.10.17

2-5. 행렬 전치

행렬의 전치: 행과 열의 인덱스를 바꿈(1열→1행, 2열→2행, ..., 1행→1열, 2행→2열, ...), T 또는 transpose() 사용

import numpy as np
matrix=np.array([[1,2,3],
                          [4,5,6],
                          [7,8,9]])

# 행렬 전치
matrix.T

### 결과 ###
array([[1,4,7],
          [2,5,8],
          [3,6,9]])

 

벡터의 전치는 행벡터를 열벡터로 또는 열벡터를 행벡터로 바꾸는 것

벡터는 값의 모음이기 때문에 원래 기술적으로 전치할 수 없지만 대괄호를 두번 사용하면 전치 가능

matrix=np.array([[1,2,3,4,5,6]])

matrix.T

### 결과 ###
array([[1],
       [2],
       [3],
       [4],
       [5],
       [6]])

 

 

T 대신 transpose() 사용가능, transpose((차원의 튜플))을 통해 바꿀 차원을 직접 지정도 가능

# transpose() 사용
matrix=np.array([[1,2,3],
                          [4,5,6],
                          [7,8,9]])

# 행렬 전치
matrix.transpose()

### 결과 ###
array([[1,4,7],
          [2,5,8],
          [3,6,9]])


# transpose((차원 튜플)) 사용
matrix=np.array([[[1,2],
                            [3,4],
                            [5,6]],
                  
                           [[7,8],
                            [9,10],
                            [11,12]]])

matrix.transpose((0,2,1))

### 결과 ###
array([[[1,3,5],
            [2,4,6]],
           [[7,9,11],
            [8,10,12]]])

2×3×2 행렬을 transpose((0,2,1))을 적용하면 2×2×3 행렬로 전치됨, (0,2,1)은 차원의 순서를 나타내는 것으로 원래 차원의 (첫번째 숫자. 세번째 숫자, 두번째 숫자)를 의미

 

 

2-6. 행렬 펼치기

행렬을 1차원 배열로 펼치기, flatten()사용

matrix=np.array([[1,2,3],
                          [4,5,6],
                          [7,8,9]])

# 행렬 펼치기
matrix.flatten()

### 결과 ###
array([1,2,3,4,5,6,7,8,9])



# reshape() 사용으로도 똑같은 결과를 만들 수 있음
matrix.reshape(1,-1)

### 결과 ###
array([1,2,3,4,5,6,7,8,9])

reshape()는 원본의 뷰를 반환하여 원본의 값을 바꾸면 reshape()로 바꾼 배열의 값도 바뀜

flatten()은 새로운 배열을 만드는 것이므로 원본의 값을 바꿔도 값이 바뀌지 않음

 

 

 

2-7. 행렬의 랭크

행렬의 차원 수를 뜻함

 

 

 

2-8. 행렬식 계산

행렬식은 행렬을 대표하는 값으로 다음과 같이 계산

 

2×2 행렬의 행렬식

 

3×3 행렬의 행렬식

 

파이썬에서는 numpy의 선형대수 메서드 det 사용

import numpy as np
matrix=np.array([[1,5,0],
                          [2,4,-1],
                          [0,-2,0]])

# 행렬식 계산
np.linalg.det(matrix)

### 결과 ###
-1.9999999999999998

 

 

 

2-9. 행렬의 대각원소

정방행렬에서 대각선에 위치한 원소(1행1열, 2행2열, 3행3열...에 해당하는 원소), diagnoal() 사용

import numpy as np
matrix=np.array([[1,2,3],
                          [2,4,6],
                          [3,8,9]])

# 행렬의 대각원소 반환
matrix.diagonal()

### 결과 ###
array([1,4,9])

offset 매개변수를 이용하면 대각원소 하나 위 또는 아나 아래의 원소를 반환

matrix=np.array([[1,2,3],
                          [2,4,6],
                          [3,8,9]])

# 행렬의 대각원소 하나 위의 원소 반환
matrix.diagonal(offset=1)

### 결과 ###
array([2,6])


# 행렬의 대각원소 하나 아래의 원소 반환
matrix.diagonal(offset=-1)

### 결과 ###
array([2,8])

 

 

np.diag()에서 ()에 1차원 배열을 넣으면 1차원 배열을 대각원소로 하는 2차원 대각행렬을 생성

a=np.diagonal(matrix)
print(a)

### 결과 ###
array([1,4,9])


# 1차원 배열의 대각원소 사용하여 2차원 대각행렬 만들기
np.diag(a)

### 결과 ###
array([[1,0,0],
          [0,4,0],
          [0,0,9]])

 

 

trace()를 사용하여 대각원소의 합인 대각합 계산

matrix=np.array([[1,2,3],
                          [2,4,6],
                          [3,8,9]])

matrix.traace()

### 결과 ###
14
# matrix의 대각원소는 [1,4,9]이므로 1+4+9=14

'Python > 기본문법' 카테고리의 다른 글

데이터 랭글링 (2)  (0) 2023.06.26
데이터 랭글링 (1)  (0) 2023.06.23
데이터 적재  (0) 2023.06.22
Numpy로 배열, 벡터, 행렬 이해하기 (3)  (0) 2022.10.20
Numpy로 배열, 벡터, 행렬 이해하기 (1)  (0) 2022.10.17

1. 사용데이터

KTDB 국가교통데이터베이스(https://www.ktdb.go.kr/www/index.do)에서 철도망, 철도역, 행정경계 데이터 다운로드

 

-자료신청 및 다운로드 방법

 1) KTDB 국가교통데이터베이스 회원가입

 2) 상단 메뉴에서 정보공개 > 자료신청

 

 

 3) 교통분석자료 신청

 

 

 4) 다음과 같이 원하는 자료를 선택

 

 

 5) 신청서 작성을 완료하고 몇 분 뒤 신청이 승인되었다는 문자가 오면

     마이페이지 > 자료신청내역에서 데이터 다운로드

 

 

2. QGIS에서 SHP파일 GeoJSON파일로 변환하기

ktdb에서 받은 파일은 shp 형태이고 folium에서 지도를 시각화하기 위해서 GeoJSON파일이 필요

따라서, QGIS에서 shp파일을 GeoJSON으로 변환

 

2-1. QGIS에서 shp파일 불러오기

 

 

 

2-2. 불러온 레이어 우클릭 > 내보내기 > 객체를 다른 이름으로 저장

 

2-3. 포맷을 GeoJSON으로 설정 후 이름 작성하고 좌표계는 EPSG: 4326-WGS 84로 설정

2-4. 확인 누른 뒤 rail_route_station에도 똑같이 적용하면 shp파일을 geojson파일로 저장 완료

2-5. 행정경계 파일도 위의 과정을 거쳐 GeoJSON파일로 변환

 

3. 파이썬에서 GeoJSON파일 불러오기

import json

# 철도망
rail_route='rail_route_geojson.geojson'
rail_route_str=json.load(open(rail_route,encoding='utf-8'))

# 철도역
rail_route_station='rail_route_station_geojson.geojson'
rail_route_station_str=json.load(open(rail_route_station,encoding='utf-8'))

 

 

 

4. 파이썬에서 folium으로 지도 불러오기

import folium

m=folium.Map(location=[36.3435957365,127.7828455523],zoom_start=7)

 -location 내부의 좌표는 우리나라의 중심점 좌표

 -좌표를 중심으로 zoom을 7로 맞추어 지도의 처음 화면이 우리나라 전체를 보여줄 수 있도록 조정

 

 

5. folium 지도에 GeoJSON파일의 데이터 적용하기

import folium

m=folium.Map(location=[36.3435957365,127.7828455523],zoom_start=7)

# folium 지도 m에 앞서 불러온 도시철도 노선과 도시철도 역의 GeoJSON파일 적용시키기
folium.GeoJson(rail_route_str, name="도시철도 노선").add_to(m)
folium.GeoJson(rail_route_station_str, name="도시철도 역").add_to(m)

# 범례와 비슷한 역할로 지도위에 표시된 요소들을 알 수 있고 요소들을 끄고 켤수 있는 기능
folium.LayerControl().add_to(m)

# 지도 출력
m

 

확대해보면 다음과 같이 철도 노선과 철도 역들이 전부 표시되어있음

1. 벡터

1-1. 벡터는 1행 또는 1열로 이루어진 1차원 배열

import numpy as np

vector_row=np.array([1,2,3])    # 1행으로 이루어진 행벡터
vector_column=np.array([[1],    # 1열로 이루어진 열벡터
                        [2],
                        [3]])

 

1-2. asarray함수를 사용하여 배열을 만들수 있음

import numpy as np

new_row=np.asarray([1,2,3])    # np.array와 똑같이 1행으로 이루어진 행벡터 생성

 

np.asarray(넘파이 배열)은 새로운 배열 생성X

np.array(넘파이 배열)은 새로운 배열 생성

# array 사용

vector_row=np.array([1,2,3])    # 1행으로 이루어진 행벡터
new_row=np.array(vector_row)    # array의 copy 매개변수는 배열을 복사할지 선택, 
                                # 기본값은 True이므로 new_row에는 vector_row의 복사본 저장
                                
new_row[0]=10   # new_row의 첫번째 값 변경
print('new_row:',new_row)
print('vector_row:',vector_row)

### 결과 ###
new_row: [10,2,3]     # 복사본인 자기 자신에게만 영향을 주고
vector_row: [1,2,3]   # 원래 배열에는 영향을 주지 않음


# asarray 사용

new_row=np.asarray(vector_row)    # asarray는 새로운 배열을 생성하지 않고 원래의 배열 그 자체가 됨

new_row[0]=10   # new_row의 첫번째 값 변경
print('new_row:',new_row)
print('vector_row:',vector_row)

### 결과 ###
new_row: [10,2,3]      # 자기 자신에게 영향을 주고
vector_row: [10,2,3]   # 원래 배열에도 똑같이 영향을 줌

 

 

 

2. 행렬

2-1. 1개 이상의 행과 열로 이루어진 2차원 배열

import numpy as np

matrix=np.array([[1,2],    # 3행 2열의 행렬
                 [1,2],
                 [1,2]])

matrix_object=np.mat([[1,2],    # 3행 2열의 행렬, numpy의 행렬에 특화된 데이터 구조
                      [1,2],
                      [1,2]])

np.array()를 사용해 행렬 생성

np.mat가 행렬에 특화되어 있긴 하지만

   -numpy의 표준 데이터 구조는 배열이고

   -대부분의 넘파이 함수는 행렬 객체가 아닌 배열을 반환하므로

np.mat()보다는 np.array()를 사용

 

 

2-2. 희소행렬

데이터에서 0이 아닌 값이 매우 적을 때 이 데이터를 효율적으로 표현하는 방법

0이 아닌 값의 행과 열의 번호와 그 값만 저장됨

import numpy as np
from scipy import sparse

matrix=np.array([[0,0],
                 [0,1],
                 [3,0]])

matrix_sparse=sparse.csr_matrix(matrix)
print(matrix_sparse)

### 결과 ###
(1,1) 1    # 두번째 행, 두번째 열의 값 1
(2,0) 3    # 세번째 행, 첫번째 열의 값 3

위에서 사용한 것은 CSR(Compressed Sparse Row)이고

이외에도 CSC(Compressed Sparse Column), 리스트의 리스트, 키의 딕셔너리 등 여러 종류의 희소 행렬이 존재

가장 좋은 희소 행렬은 없고 각 희소 행렬 사이의 유의미한 차이를 통해 어떤 것을 적용하면 좋을 지 결정

    -키의 딕셔너리(Dictionary of Keys, DOK): 행렬에서 0이 아닌 값의 (행번호, 열번호)를 키, 행렬값을 값으로 하는 딕셔너리

    -리스트의 리스트(List of lists, LIL): 링크드 리스트 알고리즘 이용하여 추가와 삭제가 용이하지만 CSR과 CSC에 비해 메모리 낭비

    -좌표 리스트(Coordinate list, COO): (행, 열, 값)의 튜플로 저장, 임의 엑세스 시간을 향상시키기 위해 행 인덱스→열 인덱스 순으로 정렬 가능, 점진적 행렬 구성에 유용

    -CSR: 가로 순서대로 재정렬(행에 관해 정리 압축)

        →데이터(A): 0이 아닌 행렬 값과 값의 (행 번호, 열 번호)가 저장

        →열 인덱스 값(JA): 0행에서 값이 있는 열은 0, 3

                                          1행에서 값이 있는 열은 2, 4

                                          2행에서 값이 있는 열은 1

                                          3행에서 값이 있는 열은 2, 4

        →행 압축 정보(IA): (최초 시작행 번호, 시작행에 있는 데이터 개수, 두번째 행까지 데이터 누적 개수,..., 마지막행까지 데이터 누적 개수)

 

    -CSC: 열에 관해 정렬한 것, CSR과 저장 알고리즘은 동일, LIL에 비해 저장 메모리 70% 이상 줄일 수 있지만 추가와 삭제가 용이하지 않음

 

# toarray
print(matrix_sparse.toarray())

### 결과 ###
[[0,0],
 [0,1],
 [3,0]]    # 희소 행렬을 다시 밀집 배열로 변환
 
 
 # todense
 print(matrix_sparse.todense())
 
 ### 결과 ###
 matrix([[0,0],
         [0,1],
         [3,0]])    # 희소 행렬을 np.matrix 객체로 변환

 

 

2-3. 벡터화 연산

배열의 여러 원소에 어떤 함수 적용하기

import numpy as np

matrix=np.array([[1,2,3],
                 [4,5,6],
                 [7,8,9]])

# 100을 더하는 함수
add_100=lambda i:i+100

# 함수를 np.vectorize(함수)를 통해 벡터화 시킴
vectorized_add_100=np.vectorize(add_100)

# 행렬에 벡터화된 함수를 적용시키면 행렬의 모든 원소에 함수 적용
vectorized_add_100(matrix)

### 결과 ###
array([[101,102,103],
       [104,105,106],
       [107,108,109]])

벡터화 연산은 기본적으로 for 루프를 구현한 것이므로 성능이 향상되지는 않음

 

 

2-4. 브로드캐스팅

넘파이 배열이 차원이 달라도 배열간 연산을 수행할 수  있음을 이용한 브로드캐스팅을 사용하면 더 간단하게 배열의 각 원소에 연산 적용 가능

# 1. 모든 원소에 100을 더함
matrix+100

### 결과 ###
array([[101,102,103],
       [104,105,106],
       [107,108,109]])


# 2. 행을 따라 더해짐
matrix+[100,100,10]

### 결과 ###
array([[101,102,13],
       [104,105,16],
       [107,108,19]])


# 3. 열을 따라 더해짐
matrix+[[100],[100],[10]]

### 결과 ###
array([[101,102,103],
       [104,105,106],
       [17,18,19]])

 

'Python > 기본문법' 카테고리의 다른 글

데이터 랭글링 (2)  (0) 2023.06.26
데이터 랭글링 (1)  (0) 2023.06.23
데이터 적재  (0) 2023.06.22
Numpy로 배열, 벡터, 행렬 이해하기 (3)  (0) 2022.10.20
Numpy로 배열, 벡터, 행렬 이해하기 (2)  (0) 2022.10.19

+ Recent posts