# 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개의 이미지이다.

이 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에 대한
딕셔너리형을 넣어준다음 교차검증 하는 것을 알 수 있다.