---
# System prepended metadata

title: Ch13_1 Eigenface(AT&T)

---

# Ch13_1 Eigenface(AT&T)

이 장에서는 이미지 데이터를 이용하여 이미지 안의 고유 얼굴 검출, 
손글씨로 쓴 숫자 분류 시스템을 살펴본다.

<br>

Ch13_1 : http://202.31.200.194:8888/notebooks/ml_face/eigenface.ipynb#
<br>

___


### 주성분 분석을 이용한 사람 얼굴 인식
<br>

우선 '주성분 분석'을 이용하여 고유 얼굴 검출을 구현한다.
카테고리의 복수 이미지로부터 주성분을 추출함으로써 그 이미지들이 공통으로 가지는 피처를 추출한다. 사이킷런의 PCA 클래스를 이용하면 쉽게 구현 가능하다.

<br>

우선 AT&T의 얼굴 데이터셋을 받아준다. 
40명 각자 10개의 그레이스케일 이미지( 가로 92px * 세로 112px )를 포함한다.

따라서 총 400개의 이미지이다.

![](https://i.imgur.com/e8YBsSl.png)

이 400개의 이미지 중 하나를 입력 받아 40명중 누구의 얼굴인지 출력하는 시스템을 만든다.

2단계로 진행되는데

1. 주성분 분석으로 이미지 피처 생성
2. 1에서 생성한 피처와 SVM을 이용하여 모델 학습

위 와 같은 과정을 거친다.

<br>

예제에서는 4가지 함수를 사용한다.

- read_data : 이미지 데이터를 읽어 numpy 배열형으로 변환한 뒤 반환
- create_train_test_data : 데이터를 학습, 평가 데이터로 나눈다
- extract_features : 학습 데이터에서 PCA 피처를 뽑는다
- train_test_classifier : 모델 학습 및 평가

<br>

---
### read_data

```
def read_data(fin):
    target_li = []
    data_li = []
    for line in open(fin):
        image_path, face_id = line.strip().split(';')
        image_data = cv2.imread(image_path, cv2.COLOR_BGR2GRAY)
        data_li.append(image_data) # 이미지 데이터 
        target_li.append(int(face_id)) # 이미지 얼굴 번호
    return(np.array(data_li), np.array(target_li))
```

 각 이미지의 경로와 해당되는 얼굴 번호를 적어놓은 csv 파일을 불러와
 numpy 배열형으로 변환한 뒤 반환 하는 함수
 
 ---
 ### create_train_test_data
 
 ```
 def create_train_test_data(image_data, label_li):
    
    # 이미지데이터수, 이미지세로픽셀크기, 이미지가로픽셀크기 
    n_samples, image_h, image_w = image_data.shape
    
    # 가로 픽셀 강도벡터와 세로 픽셀 강도벡터를 이어서 하나의 벡터로 만든다.
    # 길이는 image_h x image_w가 되고 피처 벡터가 된다.
    X = image_data.reshape(n_samples, -1)
    n_features = X.shape[1] # 피처 크기
    y = label_li # 학습 레이블
    
    n_classes = len(set(y)) # 클래스(인물) 수
    print("Total dataset size:") 
    print("n_samples: %d" % n_samples) 
    print("n_features: %d" % n_features) 
    print("n_classes: %d" % n_classes)
    
    # 사이킷런의 함수 train_test_split을 이용하여 학습셋과 평가셋을 나눈다.
    X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.25,
                                                                random_state=42)
    return(X_train, X_test, y_train, y_test)
 ```
 
width * height 의 크기로 강도 벡터를 하나로 만들고
학습 레이블과 함께 사이킷런의 함수를 이용하여 데이터 셋을 나눈다.

---
### extract_features

```
def extract_features_2(X_train, X_test, n_components, image_h, image_w): 
    print("Extracting the top %d eigenfaces from %d faces"% (n_components, X_train.shape[0])) 
    
    #PCA 클래스 선언
    #n_components : 사용할 성분의 개수 int형은 그 수만큼, 
    #    0~1 사이의 float는 분산 퍼센티지에 따른 설명가능한 수
    #svd_solver : 주성분 분석에 사용하는 특잇값 분해를 어떻게 풀지 정한다.
    #    일반적으로 auto를 사용하면 된다.
    #whiten : bool 형, 기본값은 False.
    #    True면 모든 성분을 척도화하여 분해한다.
    #    모델이 학습 시 샘플의 전차원 방향을 고려해 정확도를 높인다.
    #    하지만 각 성분의 상대 편차값이 사라진다.
    #copy
    pca = PCA(n_components=n_components,
              svd_solver='auto', 
              whiten=True).fit(X_train) 
    
    eigenfaces = pca.components_.reshape((n_components, image_h, image_w))
    
    X_train_pca = pca.transform(X_train)
    X_test_pca = pca.transform(X_test) 
    return(X_train_pca, X_test_pca, eigenfaces)
```

다음과 같은 애트리뷰트를 가지며 모델 학습 후 아래 값들을 사용할 수 있다.

- components_ : 각 피처 공간에서의 주성분. 주성분 수 * 피처 수 의 크기를 가진다
- explained_variance_ : 각 주성분이 설명할 수 있는 데이터 분산의 크기
- explained_variance_ratio_ : 전체 분산 중에서 해당하는 주성분이 설명하는 분산의 백분율
- mean_ : 학습 데이터에서 샘플의 피처값 평균
- n_components_ : int형의 주성분 수 추정값
- noise_variance_ : float형의 추정된 노이즈 공분산 값

extract_features의 경우 components_를 이후 고유 얼굴을 시각화 하기 위해 사용한다.

---
### train_test_classifier

```
def train_test_classifer(X_train_pca, X_test_pca, y_train, y_test): 
    print("Fitting the classifier to the training set")
    
    param_grid = {'C': [1e3, 5e3, 1e4, 5e4, 1e5],'gamma': [0.0001, 0.0005, 0.001, 0.005, 0.01, 0.1]}
    clf = GridSearchCV(SVC(kernel='rbf', class_weight='balanced'), param_grid) 
    clf = clf.fit(X_train_pca, y_train)
    
    print("Best estimator found by grid search:")
    print(clf.best_estimator_)
    
    print("Predicting people's names on the test set") 
    y_pred = clf.predict(X_test_pca) 
    print(classification_report(y_test, y_pred))
```
이제 추출한 피처를 SVM의 입력으로 사용하여 학습시킨다.
사이킷 런은 SVC라는 클래스로 구현하고 있다.

위 코드에서
```
SVC(kernel='rbf', class_weight='balanced')
```
- kernel : string 형으로 기본값은 'rbf' 커널이다. 
모델에서 사용할 함수를 정하며, 
'linear, poly, rbf, sigmoid, precomputed'를 지원하지만 'linear', 'rbf'를 쓰는게 무난하다.
- class_weight : 딕셔너리형 인자 혹은 'balanced'를 받는다.
클래스 가중치를 넘기기 위한 딕셔너리형, 키는 클래스 인덱스, 값은 가중치가 된다.

머신러닝에 사용될 지도학습 모델을 설정하는 것이다.

<br>

하지만 SVM은 파라미터에 따라 성능이 많이 달라지는데, 
복잡하게 할 필요 없이 GridSearchCV 클래스를 이용해서 직접 코드를 쓰지않고
두 개 파라미터의 조합을 만들어 모델의 성능을 평가할 수 있다.

파라미터 조합을 이용하여 학습된 모델의 성능을 교차검증한다.

```
GridSearchCV(SVC(...), param_grid)
```

사이킷런 모델이 앞에 들어가있고 뒤에는 딕셔너리형이 있다.

SVM의 파라미터 중 C는 에러에 대한 파라미터로 예외 데이터를 가정하게 되며,
gamma는 float 형으로 커널의 계수를 뜻한다.

코드의 param_grid 초기화 부분을 보면 C와 gamma에 대한
딕셔너리형을 넣어준다음 교차검증 하는 것을 알 수 있다.
