brunch

You can make anything
by writing

C.S.Lewis

by 라인하트 Jan 06. 2021

머신러닝 옥타브 실습 (3-2) : 손글씨 숫자 인식

   온라인 강의 플랫폼 코세라의 창립자인 앤드류 응 (Andrew Ng) 교수는 인공지능 업계의 거장입니다. 그가 스탠퍼드 대학에서 머신 러닝 입문자에게 한 강의를 그대로 코세라 온라인 강의 (Coursera.org)에서 무료로 배울 수 있습니다. 이 강의는 머신러닝 입문자들의 필수코스입니다. 인공지능과 머신러닝을 혼자 공부하면서 자연스럽게 만나게 되는 강의입니다. 


Programing Exercise 3 : Multi-class Classification and Neural Networks 

프로그래밍 실습 3 : 멀티클래스 분류와 신경망  


1. Multi-class Classification


1.4 One-vs-all Classification (멀티 클래스 분류)


   In this part of the exercise, you will implement one-vs-all classification by training multiple regularized logistic regression classifiers, one for each of the K classes in our dataset (Figure 1). In the handwritten digits dataset, K = 10, but your code should work for any value of K.

   You should now complete the code in oneVsAll.m to train one classifier for each class. In particular, your code should return all the classifier parameters in a matrix Θ ∈ RK×(N+1) , where each row of Θ corresponds to the learned logistic regression parameters for one class. You can do this with a “for”-loop from 1 to K, training each classifier independently.

   Note that the y argument to this function is a vector of labels from 1 to 10, where we have mapped the digit “0” to the label 10 (to avoid confusions with indexing).

   When training the classifier for class k ∈ {1,...,K}, you will want a m- dimensional vector of labels y, where yj ∈ 0,1 indicates whether the j-th training instance belongs to class k (yj = 1), or if it belongs to a different class (yj = 0). You may find logical arrays helpful for this task.


   이번 실습은 데이터 셋의 k 클래스 각각에 대해 하나씩 정규화된 로지스틱 회귀 분류기를 훈련시켜 one-vs-all 분류를 구현합니다. 손글씨 데이터에서 클래스 k= 10 개이므로 모든 클래스에 동작할 수 있도록 코드를 작성합니다.  

   oneVsAll.m 파일이 코드를 완성하여 각 클래스에 대해 하나의 분류기를 훈련합니다. 코드는 파라미터 행렬 Θ는 R^(k X k+1) 차원의 모든 분류기의 파라미터를 반환합니다. 여기서, 파라미터 행렬 Θ의 각 행은 한 클래스를 학습한 로지스틱 회귀 파라미터에 해당합니다. 1에서 k까지 For 루프를 사용하여 각 분류기를 독립적으로 훈련합니다. 

   y는 1에서 10까지의 레이블 벡터이고, 인덱싱과 혼동을 피하기 위해 숫자 0은 10으로 매핑합니다. 클래스 k 분류기를 훈련할 때 레이블 y의 m 차원 벡터가 필요합니다. yj가 0 또는 1의 값일 때 j번째 학습 예제가 클래스 k에 속하는 지를 나타냅니다  k 클래스에 속하면 yj = 1이고 다른 클래스에 속하면 yj = 0입니다.  이 작업에 유용한 논리 배열을 찾습니다. 


Octave/MATLAB Tip: Logical arrays in Octave/MATLAB are arrays which contain binary (0 or 1) elements. In Octave/MATLAB, evaluating the expression a == b for a vector a (of size m×1) and scalar b will return a vector of the same size as a with ones at positions where the elements of a are equal to b and zeroes where they are different. To see how this works for yourself, try the following code in Octave/MATLAB:

   a = 1:10; % Create a and b
   b = 3;
   a == b    % You should try different values of b here 


   옥타브 프로그램 팁 :

        옥타브 프로그램의 논리 배열은 0 또는 1의 이진 요소를 포함합니다. 옥타브에서 m X 1차원의 벡터 a와 스칼라 b를  a==b라고 평가하면 a의 각 성분 별로 b와 같은 값이면 1, 다른 값이면 0을 반환합니다. 따라서, 결과 벡터는 mX1 차원입니다.  옥타브에서 다음 코드를 통해 확인하세요


       a = 1:10; 

       b = 3;

       a == b   


   Furthermore, you will be using fmincg for this exercise (instead of fminunc). fmincg works similarly to fminunc, but is more more efficient for dealing with a large number of parameters.

   After you have correctly completed the code for oneVsAll.m, the script ex3.m will continue to use your oneVsAll function to train a multi-class clas- sifier.


   또한, 이번 실습에서 fminunc 함수 대신 fmincg 함수를 사용합니다. fmincg는 fminunc와 유사하지만 많은 수의 파라미터를 처리할 때 효율적입니다. 

   oneVsAll.m 에 대한 코드를 작성한 후에 ex3.m 스크립트를 수행합니다. 



<해설>


(1) 데이터 업로드 및 기본 변수 설정 


clear; close all; clc         

load ('ex3data1.mat');  % 5000X 400의 손글씨 숫자 흑백 이미지 행렬을 업로드       

[m, n] = size(X);              % 행렬 X가 5000X 400차원일 때 m = 5000, n= 400    


(2) 필요한 변수 선언


input_layer_size  = 400;      % 20x20 픽셀 이미지 크기 지정

num_labels = 10;                   %  k = 10, 10개의 라벨 (숫자 0은 10으로 매핑

lambda = 0.1;                           %  정규화 파라미터 값을 0.1로 지정

              

(3) oneVsAll.m 파일 분석


function [all_theta] = oneVsAll(X, y, num_labels, lambda)

% ONEVSALL  다수의 로지스틱 회귀 분류기를 훈련

%   [all_theta] = ONEVSALL(X, y, num_labels, lambda) 

%       num_labels 만큼의 로지스틱 회귀 분류기를 훈련

%       모든 분류기의 정보를 가진 변수 all_theta 행렬 반환

%       레이블 i를 위한 분류기는 i 번째 행(row)

 

% 변수 초기화. [m, n] = size(X)으로 한 줄 코드로 정리 가능

m = size(X, 1);

n = size(X, 2);


% 반환할 변수 all_theta 초기화 

% 0에서 9까지의 레이블을 행에 배치, 학습 예제당 피처의 수 400과 인터셉트 항 1을 추가  

all_theta = zeros(num_labels, n + 1);


% 행렬 X에 인터셉트 항 x0를 추가 

X = [ones(m, 1) X];


% ====================== YOUR CODE HERE ======================

% Instructions: 

%               num_labels의 수만큼 정규화된 로지스틱 회귀 분류기를 구현

% Hint: 

%              theta(:)는 열 벡터를 반환 

% Hint: 

%       클래스 별로 학습 예제에 대한 1 또는 0의 값을 얻기 위해 y == c 논리 연산 사용

%

% Note: 

%       비용 함수를 최적화하기 위해 fmincg 함수를 사용할 것 (

%       fminunc와 유사하지만 파라미터가 많을 때 효과적

%       각각 클래스에 대한 For 루프를 활용할 때 효율적 

%                for c = 1:num_labels


% fmincg 활용 방법:

%

%     

%     initial_theta = zeros(n + 1, 1);   % 초기화 

%     

%     options = optimset('GradObj', 'on', 'MaxIter', 50);  % fminunc 함수의 옵션 설정

%     % fmincg는 최적의 파라미터 theta를 반환 

%     [theta] = ...

%         fmincg (@(t)(lrCostFunction(t, X, (y == c), lambda)), ...

%                 initial_theta, options);

%





% =========================================================================

end


(4) 추가적인 변수를 선언


all_theta = zeros(num_labels, n + 1);

X = [ones(m, 1) X];



(5) 행렬의 논리 연산의 이해 


   논리 연산에서 '==' 비교하는 값이 같으면 참 (1), 다르면 거짓(0)을 반환합니다. 스칼라 값만을 비교하는 것이 아니라 벡터 == 스칼라, 행렬과 스칼라, 같은 차원의 행렬끼리도 비교할 수 있습니다. 

  

>> a = 1:10         % 1X10 행 벡터 a를 생성

a =

    1    2    3    4    5    6    7    8    9   10


>> a == 3            % 행 벡터 a의 성분이 3이면 1, 다른 값은 0으로 결과 값을 반환

ans =

   0   0   1   0   0   0   0   0   0   0


>> reshape (1:9, 3, 3).  %. 5X3차원의 행렬을  생성 

ans =

   1   4   7

   2   5   8

   3   6   9


>> reshape (1:15, 5, 3) == 8.  % 행렬 성분이 8이면 1, 다른 값은 0으로 결과 값을 반환

ans =

   0   0   0

   0   0   1

   0   0   0


>> reshape (1:15,5,3) == reshape(1:15, 5,3)     % 같은 위치의 행렬 성분끼리 비교  

ans =

   1   1   1

   1   1   1

   1   1   1


(6) 레이블을 k 클래스 별로 다시 계산 


  현재 y는 5000x1 열 벡터이고, 레이블은 [1,2,3,4,5,6,7,8,9,10]로 이루어진 10개의 k값을 가집니다.  '==' 논리 연상을 활용하여 클래스 k가 1 일 때 1은 1이고, 1을 제외한 나머지 k값들은 0으로 정렬합니다.  재 정렬합니다. 


y_k = [ zeros(m, num_labels)];  % 5000 X 10차원의 행렬을 초기화


for i = 1:num_labels

    y_k(:,i) = y == i;

end


(7) fmincg 함수를 적용하여 비용을 계산


   oneVsAll.m 파일에 있는 fmincg 함수의 사용 사례를 복사합니다.


 initial_theta = zeros(n + 1, 1); 

 options = optimset('GradObj', 'on', 'MaxIter', 50);  

[theta] =  fmincg (@(t)(lrCostFunction(t, X, (y == c), lambda)),initial_theta, options);


   모든 클래스에 대해 theta를 구해야 하므로 For 루프를 이용합니다. 


initial_theta = zeros(n + 1, 1); 

options = optimset('GradObj', 'on', 'MaxIter', 50);  


for i = 1:num_labels

    all_theta(i,:) =  fmincg (@(t)(lrCostFunction(t, X, y_k(:,i), lambda)),initial_theta, options);

end



<결과 확인>


옥타브 프로그램에서 다음 명령어를 차례대로 입력합니다.


clear; close all; clc         

load ('ex3data1.mat');    

[m, n] = size(X);           


input_layer_size  = 400;      

num_labels = 10;                   

lambda = 0.1;                         


all_theta = zeros(num_labels, n + 1);

X = [ones(m, 1) X];


initial_theta = zeros(n + 1, 1); 

y_k = [ zeros(m,num_labels)];

options = optimset('GradObj', 'on', 'MaxIter', 50);  


for i = 1:num_labels

      y_k(:,i) = y == i;

    all_theta(i,:) =  fmincg (@(t)(lrCostFunction(t, X, y_k(:,i), lambda)),initial_theta, options);

end


fmincg 함수가 제공하는 출력 값을 확인합니다. 


Iteration    50 | Cost: 1.355714e-02

Iteration    50 | Cost: 5.725264e-02

Iteration    50 | Cost: 6.287535e-02

Iteration    50 | Cost: 3.600294e-02

Iteration    50 | Cost: 6.176009e-02

Iteration    50 | Cost: 2.210423e-02

Iteration    50 | Cost: 3.621458e-02

Iteration    50 | Cost: 8.454014e-02

Iteration    50 | Cost: 7.929352e-02

Iteration    50 | Cost: 9.582262e-03


물론, ex3를 실행해도 같은 결과를 출력합니다. 


<정답>


   oneVsAll.m 파일에 다음의 값을 입력합니다. 


function [all_theta] = oneVsAll(X, y, num_labels, lambda)

% ONEVSALL  다수의 로지스틱 회귀 분류기를 훈련

%   [all_theta] = ONEVSALL(X, y, num_labels, lambda) 

%       num_labels 만큼의 로지스틱 회귀 분류기를 훈련

%       모든 분류기의 정보를 가진 변수 all_theta 행렬 반환

%       레이블 i를 위한 분류기는 i 번째 행(row)

 

% 변수 초기화. [m, n] = size(X)으로 한 줄 코드로 정리 가능

m = size(X, 1);

n = size(X, 2);


% 반환할 변수 all_theta 초기화 

% 0에서 9까지의 레이블을 행에 배치, 학습 예제당 피처의 수 400과 인터셉트 항 1을 추가  

all_theta = zeros(num_labels, n + 1);


% 행렬 X에 인터셉트 항 x0를 추가 

X = [ones(m, 1) X];


% ====================== YOUR CODE HERE ======================

% Instructions: 

%               num_labels의 수만큼 정규화된 로지스틱 회귀 분류기를 구현

% Hint: 

%              theta(:)는 열 벡터를 반환 

% Hint: 

%       클래스 별로 학습 예제에 대한 1 또는 0의 값을 얻기 위해 y == c 논리 연산 사용

%

% Note: 

%       비용 함수를 최적화하기 위해 fmincg 함수를 사용할 것 (

%       fminunc와 유사하지만 파라미터가 많을 때 효과적

%       각각 클래스에 대한 For 루프를 활용할 때 효율적 

%                for c = 1:num_labels


% fmincg 활용 방법:

%

%     

%     initial_theta = zeros(n + 1, 1);   % 초기화 

%     

%     options = optimset('GradObj', 'on', 'MaxIter', 50);  % fminunc 함수의 옵션 설정

%     % fmincg는 최적의 파라미터 theta를 반환 

%     [theta] = ...

%         fmincg (@(t)(lrCostFunction(t, X, (y == c), lambda)), ...

%                 initial_theta, options);

%


initial_theta = zeros(n + 1, 1); 

y_k = [ zeros(m,num_labels)];

options = optimset('GradObj', 'on', 'MaxIter', 50);  


for i = 1:num_labels

      y_k(:,i) = y == i;

    all_theta(i,:) =  fmincg (@(t)(lrCostFunction(t, X, y_k(:,i), lambda)),initial_theta, options);

end


% =========================================================================

end





  all_theta는 10X401차원 행렬입니다. 각 theta는 손글씨 숫자를 인식할 수 있는 최적의 파라미터 theta 값을 획득하기 위해 로지스틱 회귀 분류기 10개를 학습시켰습니다. 



1.4.1 One-vs-all Prediction (예측)


   After training your one-vs-all classifier, you can now use it to predict the digit contained in a given image. For each input, you should compute the “probability” that it belongs to each class using the trained logistic regression classifiers. Your one-vs-all prediction function will pick the class for which the corresponding logistic regression classifier outputs the highest probability and return the class label (1, 2,..., or K) as the prediction for the input example.

   You should now complete the code in predictOneVsAll.m to use the one-vs-all classifier to make predictions.

   Once you are done, ex3.m will call your predictOneVsAll function using the learned value of Θ. You should see that the training set accuracy is about 94.9% (i.e., it classifies 94.9% of the examples in the training set correctly).


   One-vs-all 분류기를 훈련한 후 새로운 이미지에 포함된 숫자를 예측합니다. 입력된 이미지에 대해 학습한 로지스틱 회귀 분류기는 각 클래스에 속하는 확률을 계산합니다. One-vs-all 예측 함수는 로지스틱 회귀 분류기가 가장 높은 확률을 출력하는 클래스를 선택하고 입력 예제에 대한 예측의 결과로 클래스 레이블을 반환합니다. 

   PredictOneVsAll.m 파일의 코드를 완성하여 예측을 합니다. ex3.m 파일은 predictOneVsAll.m 파일을 호출하여 계산한 학습 셋 정확도는 94.9%입니다. 



<해설>


(1) 변수 all_theta의 값을 그대로 활용  


(2) predictOneVsAll.m 파일 분석


function p = predictOneVsAll(all_theta, X)

%PREDICT 학습한 one-vs-all 분류기를 이용하여 새로운 예제에 대한 예측 실행 

%

%  p = PREDICTONEVSALL(all_theta, X) will return a vector of predictions

%   p : 행렬 X에 대한 각 예제에 대한 예측을 반환 

%   all_theta : i 번째 클래스에 대한 학습된 로지스틱 회귀 파라미터 벡터 theta 

%   p는 모든 클래스에 대한 확률을 표시 

%       (p = [1; 3; 1; 2] 4 개의 예제에 대한 예측 값 )  


m = size(X, 1);

num_labels = size(all_theta, 1);


% p 값을 반환. p는 5000 X 1차원 벡터 

p = zeros(size(X, 1), 1);


% X에 인터셉트 항을 추가 

X = [ones(m, 1) X];


% ====================== YOUR CODE HERE ======================

% Instructions: 학습된 로지스틱 회귀 파라미터로 예측하는 코드 완성

%                p는 1부터 num_labels까지의 클래스를 예측

%

% Hint: 

%       max 함수를 사용하는 벡터화 구현

           max(A, [],2)는 각 열의 최대값을 반환 

%       




% =========================================================================


end



(3) 변수 초기화


m = size(X, 1);

num_labels = size(all_theta, 1);

p = zeros(size(X, 1), 1);    % p 값을 반환. p는 5000 X 1차원 벡터 

X = [ones(m, 1) X];            % X에 인터셉트 항을 추가 



(4) max() 함수의 이해


   max() 함수는 최대값을 반환합니다. 만일 입력값이 행렬일 경우 열 별로 최대값을 반환합니다. 


>> A = magic(3)

A =

   8   1   6

   3   5   7

   4   9   2


>> max (A)

ans =

   8   9   7


>> max(A, pi)                   % 행렬 A의 성분을 pi와 비교하여 큰 값으로 대체

ans =

   8.0000   3.1416   6.0000

   3.1416   5.0000   7.0000

   4.0000   9.0000   3.1416


>> [a, b] = max(A(1,:)).  % 두 개의 변수를 반환할 때, a는 최대값을, b는 위치를 반환

a =  8

b =  1



(5) 예측 p를 계산 


   hθ(x) = g(z) 이므로 다음과 같이 가설을 계산합니다. 행렬 X는 5000X401 행렬이고, all_theta는 10X401 행렬입니다. 행렬 곱셈을 위해 all_theta를 전치합니다. 가설 h는 5000X10 행렬이 만들어집니다. 


h = sigmoid(X*all_theta');


  각 예제마다 10개의 클래스에 대한 확률이 행마다 적혀 있습니다. 이 중에 가장 높은 확률을 가진 클래스가 정답입니다. 


for i = 1 : m

   p(i) = max(h(i,:));

end


>> p                % 각 학습 예제의 최대 확률을 반환

p =

   0.9994932

   0.9999372

   0.9998028

   0.9999437

   ...


   가장 높은 확률이 아닌 가장 높은 확률이 있는 위치를 반환받아야 합니다.


for i = 1 : m

   [a, p(i)] = max(h(i,:));

end


>> p

p =


   10

   10

   10

   10

   ...


   그러나 언제나 백터화 구현을 선호합니다. 문제도 벡터화 구현을 강조합니다. 


[max_p p] = max(Predict, [],2); 



(6) 정확도 계산


   가설을 통해 예측 치를 계산하고 p에 결과 값을 입력합니다.  


p = zeros(size(X, 1), 1);    


h = sigmoid(X*all_theta');

for i = 1 : m

   [a, p(i)] = max(h(i,:));

end


   이제 데이터 행렬 X에 대한 레이블 y와 가설 h를 비교하여 정확도를 계산합니다. 지난 실습에서 정확도를 예측하기 위해 다음과 같이 코드를 작성했습니다.


pos = find (p == y);

size(pos,1) /m *100


>> pos = find (p == y);

>> size(pos,1) /m *100

ans =  94.900


   ex3.m 파일에 mean 함수를 이용한 방법이 있습니다. 너무나 간단한 방법입니다. mean (p ==y)는 p와 y의 값은 1이고 다른 값이면 0입니다. 따라서, 1과 0으로 된 값을 모두 합산하여 데이터 개수로 나누는 mean () 함수는 자동으로 확률을 구합니다. 여기에 백분율을 계산하기 위해 100을 곱해 줍니다.  같은 데이터의 퍼센트를 구합니다. 직접 복잡하게 백분율을 구할 필요 없이 간단하게 구할 수 있습니다. 


>> mean (p == y)

ans =  0.94900


>> mean (p == y) * 100

ans =  94.900



<정답>


function p = predictOneVsAll(all_theta, X)

%PREDICT 학습한 one-vs-all 분류기를 이용하여 새로운 예제에 대한 예측 실행 

%

%  p = PREDICTONEVSALL(all_theta, X) will return a vector of predictions

%   p : 행렬 X에 대한 각 예제에 대한 예측을 반환 

%   all_theta : i 번째 클래스에 대한 학습된 로지스틱 회귀 파라미터 벡터 theta 

%   p 는 모든 클래스에 대한 확률을 표시 

%       (p = [1; 3; 1; 2] 4 개의 예제에 대한 예측 값 )  


m = size(X, 1);

num_labels = size(all_theta, 1);


% p 값을 반환. p는 5000 X 1차원 벡터 

p = zeros(size(X, 1), 1);


% X에 인터셉트 항을 추가 

X = [ones(m, 1) X];


% ====================== YOUR CODE HERE ======================

% Instructions: 학습된 로지스틱 회귀 파라미터로 예측하는 코드 완성

%                p는 1부터 num_labels까지의 클래스를 예측

%

% Hint: 

%       max 함수를 사용하는 벡터화 구현

           max(A, [],2)는 각 열의 최대값을 반환 

%       


Predict = sigmoid(X*all_theta');


[max_p p] = max(Predict, [],2); 



% =========================================================================


end


ex3를 실행하면 아래와 같은 결과를 얻을 수 있다.


>>ex3

Training Set Accuracy: 94.900000


매거진의 이전글 머신러닝 옥타브 실습 (3-1) : 손글씨 숫자 인식
작품 선택
키워드 선택 0 / 3 0
댓글여부
afliean
브런치는 최신 브라우저에 최적화 되어있습니다. IE chrome safari