brunch

You can make anything
by writing

C.S.Lewis

by 라인하트 Jan 05. 2021

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

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


Programing Exercise 3 : Multi-class Classification and Neural Networks 

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


1. Multi-class Classification


   For this exercise, you will use logistic regression and neural networks to recognize handwritten digits (from 0 to 9). Automated handwritten digit recognition is widely used today - from recognizing zip codes (postal codes) on mail envelopes to recognizing amounts written on bank checks. This exercise will show you how the methods you’ve learned can be used for this classification task.

   In the first part of the exercise, you will extend your previous implemen- tion of logistic regression and apply it to one-vs-all classification.


   이번 실습은 로지스틱 회귀와 신경망을 사용하여 0에서 9까지의 손글씨 숫자를 인식하는 알고리즘을 만드는 것입니다. 우편 봉투의 우편 번호에서 은행 수표에 기록된 금액 인식에 이르기까지 손글씨 숫자를 인식하는 알고리즘은 널리 사용됩니다. 이번 실습은 알고리즘을 분류 작업에 사용할 수 있는 방법을 안내할 것입니다.  

   지난 실습에서 사용한 로지스틱 회귀 구현을 확장하여 one-vs-all  분류에 적용합니다.  



1.1 Dataset (데이터 셋) 

             

   You are given a data set in ex3data1.mat that contains 5000 training examples of handwritten digits. The .mat format means that that the data has been saved in a native Octave/MATLAB matrix format, instead of a text (ASCII) format like a csv-file. These matrices can be read directly into your program by using the load command. After loading, matrices of the correct dimensions and values will appear in your program’s memory. The matrix will already be named, so you do not need to assign names to them.


   ex3data1.mat 파일은 5,000개의 손글씨 숫자 학습 데이터 셋입니다. mat 포맷의 파일은 데이터가 텍스트(ASCII)가 아닌 옥타브/메트랩 행렬 형식으로 저장되었음을 의미합니다. 액셀의 csv 파일 형식과 같습니다. 옥타브 프로그램에서 load 명령어를 사용하여 mat 파일을 로드하고 읽을 수 있습니다. 옥타브 프로그램 메모리에서 숫자의 행렬로 보입니다. 행렬은 이미 변수 X, y가 지정되어 있으므로 따로 이름을 지정하지 않고 load 명령어만을 사용합니다.  


% 파일에서 저장된 행렬을 옥타브 프로그램에 로드  
load('ex3data1.mat');     
% 옥타브 프로그램에 행렬 X와 y는 이미 있음  


   There are 5000 training examples in ex3data1.mat, where each training example is a 20 pixel by 20 pixel grayscale image of the digit. Each pixel is represented by a floating point number indicating the grayscale intensity at that location. The 20 by 20 grid of pixels is “unrolled” into a 400-dimensional vector. Each of these training examples becomes a single row in our data matrix X. This gives us a 5000 by 400 matrix X where every row is a training example for a handwritten digit image.



   The second part of the training set is a 5000-dimensional vector y that contains labels for the training set. To make things more compatible with Octave/MATLAB indexing, where there is no zero index, we have mapped the digit zero to the value ten. Therefore, a “0” digit is labeled as “10”, while the digits “1” to “9” are labeled as “1” to “9” in their natural order.


   ex3data1.mat 파일에 포함된 5,000 개의 학습 예제는 각각 20 X 20 픽셀의 흑백 이미지를 나타냅니다. 각 픽셀은 해당 위치의 밝기 또는 선명도를 나타내는 부동 소수점 숫자입니다. 20 X 20 픽셀 격자는 400차원 벡터로 풀어져 있습니다. 각 학습 예제는 데이터 행렬 X에서 하나의 행입니다. 그러면 행이 손글씨 숫자 한 개에 대한 학습 예제이므로 행렬 X는 5,000 X 400차원입니다. 


   또한, 학습 셋은 5,000차원 벡터 y를 포함합니다. 옥타브 프로그램은 0-인덱스가 없습니다. 따라서, 혼동되지 않도록 숫자 0을 값 10에 매핑하였습니다. 손글씨 숫자 0은 '10'으로 인식하고, 1에서 9까지의 손글씨는 그대로 1에서 9로 인식합니다. 



1.2 Visualizing the data


   You will begin by visualizing a subset of the training set. In Part 1 of ex3.m, the code randomly selects selects 100 rows from X and passes those rows to the displayData function. This function maps each row to a 20 pixel by 20 pixel grayscale image and displays the images together. We have provided the displayData function, and you are encouraged to examine the code to see how it works. After you run this step, you should see an image like Figure 1.



   학습 셋의 학습 예제를 시각화합니다. ex3.m의 part 1은 행렬 X에서 100개 행을 무작위로 선택하고 displayData.m 함수로 전달합니다. displayData.m 파일은 전달받은 데이터를 20 X 20 픽셀 흑백 이미지에 매핑하고 이미지를 표시합니다. displayData.m 파일을 보고 코드가 동작하는 방식을 살펴보는 것이 좋습니다. 이 단계를 실행하면 그림 1과 같은 이미지가 표시됩니다.


<해설>


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


clear; close all; clc         

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

[m, n] = size(X);           


(2) randperm()의 이해


   5,000개의 데이터 중에서 무작위로 100개의 데이터를 선택하기 위해 randperm() 함수를 사용합니다. randperm() 함수는 무작위로 행 벡터를 반환합니다. 

   

>> randperm(10)            % 1에서 10까지의 숫자를 랜덤 하게 추출하여 행 벡터로 반환

ans =

    6    9    3    4    5    2    1    7   10    8


>> randperm(10,5)        % 1에서 10까지의 숫자를 랜덤 하게 5개만 추출

ans =

   10    8    4    3    1


(3) 무작위로 100개의 데이터를 선택


rand_index = randperm(m,100);        % 1부터 5,000까지 중에서 100개의 숫자를 선택

sel = X (rand_index,:);                         % 행렬 X에서  rand_index 행으로 sel 행렬을 생성


>> size(sel)

ans =

   100   400


sel 행렬은 100 X 400차원입니다. 


(4) imagesc () 함수의 이해


    imagesc() 함수는 IMG 행렬을 컬러 이미지로 표시합니다. 행렬은 전체 colormap에 대한 정보를 모두 포함해야 합니다. 따라서, 1 X400 차원의 IMG 행렬을 20 X20 차원으로 재정렬합니다. 100번째 학습 예제를 20 X 20차원으로 변경하기 위해 for 루프를 사용합니다. 


for i = 0:19

    for j = 1:20

       X100 (i+1,j)= X(100,i*20+j);

    end

end


X(100) 데이터를 그립니다. 


colormap(gray);

imagesc(X100);




imagesc(X100,[-1,1]);  % 선명도를 조절 clims = [-1,1]로 표현



행렬 X의 1205번째 행의 다른 손글씨 숫자를 그립니다.  


for i = 0:19

    for j = 1:20

       X1205 (i+1,j)= X(1205,i*20+j);

    end

end


colormap(gray);

imagesc(X1205, [-1,1]);



(5) reshape() 함수의 이해 


    reshape() 함수는 행렬의 차원을 변경합니다. reshape() 명령은 복잡한 구성된 For 루프를 사용한 것처럼 행렬의 모양을 바꾸어줍니다. 5 줄의 코드를 한 줄로 만듭니다. 


>> reshape(1:15, 5, 3)     % 1부터 15까지의 행 벡터를 5X3 행렬로 변환

ans =

    1    6   11

    2    7   12

    3    8   13

    4    9   14

    5   10   15


>> reshape (1:20, 5, 4)  %1부터 20까지의 행 벡터를 5X4 행렬로 변환

ans =

    1    6   11   16

    2    7   12   17

    3    8   13   18

    4    9   14   19

    5   10   15   20


   4000번째 학습 예제를 20X20차원으로 변경합니다. 


X4000 = reshape(X(4000,:), 20, 20) ;  % 400차원의 행 벡터를 20 X 20차원의 행렬로 변환

 

colormap(gray);

imagesc(X4000, [-1,1]);


(5) subplot() 함수의 이해


   subplot() 함수는 하나의 그래프에 여러 개의 그림창을 만듭니다. 하나의 그림창에 여러개의 그래프를 격자 무늬로 그릴 수 있습니다. 격자 구조는 5X5 또는 10 X 10 을 선택합니다. 


>> figure;                  % 그림창 열기

>> subplot(5,5,1);   % 5 X 5 격자 구조를 만들고 첫번째 격자에 그림을 그립니다. 


>> subplot(5,5,5)      % 다시 명령어를 추가 입력하면 5번째 작은 창을 에서 그림

(6) 10 X 10 격자 구조에 숫자를 넣기 


   행렬 sel에 있는 100개의 숫자를 표시하기 위해 10 X 10 격자 구조를 만듭니다.


figure;

colormap(gray);


subplot (10,10,1);

hold on;


for i = 1:size(sel,1)

   s1 = reshape(sel(i,:), 20, 20);

   subplot (10,10,i);

   imagesc(s1, [-1,1]);

    axis off

 end


   축의 숫자들이 눈에 거슬립니다. 축의 값을 제거 하기 위해  axis off 명령어를 이용합니다.


figure;

colormap(gray);


subplot (10,10,1);

hold on;


for i = 1:size(sel,1)

   s1 = reshape(sel(i,:), 20, 20);

   subplot (10,10,i);

   imagesc(s1, [-1,1]);

    axis off

 end



(7) displayData.m 파일로 데이터 그리기


   다음의 명령어는 400 X 400차원의 sel 행렬을 생성하고, 10 X 10 배열의 격자 구조에 있는 손글씨를 확인할 수 있습니다.  


clear; close all; clc         

 load ('ex3data1.mat');         

[m, n] = size(X);           


rand_index = randperm(m,100);  

sel = X (rand_index,:);  


displayData (sel)


   다음의 명령어는 400 X 400차원의 sel 행렬을 생성하고, 20 X 20 배열의 격자 구조에 있는 손글씨를 확인할 수 있습니다.  


clear; close all; clc         

 load ('ex3data1.mat');         

[m, n] = size(X);           


rand_index = randperm(m,400);  

sel = X (rand_index,:);  


displayData (sel)


   다음의 명령어는 200 X 400차원의 sel 행렬을 생성하고, 14 X 15 배열의 격자 구조에 있는 손글씨를 확인할 수 있습니다.  마지막 열에 빈 공간은 패딩 (padding)으로 대체하였습니다.


clear; close all; clc         

 load ('ex3data1.mat');         

[m, n] = size(X);           


rand_index = randperm(m,200);  

sel = X (rand_index,:);  


displayData (sel)


   따라서, DisplayData.m 파일은 20 X 20 픽셀의 숫자 이미지를 입력받아 정방형 모양의 격자구조로 표시합니다. 입력되는 이미지의 개수에 따라 정방형 격자 구조를 만들고 숫자 이미지를 표시합니다.



(6) displayData.m 파일 분석



function [h, display_array] = displayData(X, example_width)

%DISPLAYDATA  격자 모양의 2D 데이터 표시 

%   [h, display_array] = DISPLAYDATA(X, example_width) 

%   격자구조로 행렬 X에 저장된 2D 데이터 표시  

%   필요할 경우 h, display_array 변수 반환 


% example_width 변수가 존재한다면 다음을 실행 

if ~exist('example_width', 'var') || isempty(example_width) 

example_width = round(sqrt(size(X, 2)));

end


% 흑백 이미지 

colormap(gray);


% 행과 열을 계산. 

[m n] = size(X);

example_height = (n / example_width);   


% 표시할 아이템의 수를 계산 

display_rows = floor(sqrt(m));

display_cols = ceil(m / display_rows);


% 이미지와 이미지 사이의 간격을 위한 패딩 

pad = 1;


% 빈 공간 설정 

display_array = - ones(pad + display_rows * (example_height + pad), ...

                       pad + display_cols * (example_width + pad));


% 각 예제를 디스플레이 공간에 복사 

curr_ex = 1;

for j = 1:display_rows

for i = 1:display_cols

if curr_ex > m, 

break; 

end

% 패치를 복사하고 패치의 최대값을 계산

max_val = max(abs(X(curr_ex, :)));

display_array(pad + (j - 1) * (example_height + pad) + (1:example_height), ...

              pad + (i - 1) * (example_width + pad) + (1:example_width)) = ...

reshape(X(curr_ex, :), example_height, example_width) / max_val;

curr_ex = curr_ex + 1;

end

if curr_ex > m, 

break; 

end

end


% Display Image

h = imagesc(display_array, [-1 1]);


% 축을 표시하지 않음 

axis image off


drawnow;


end


   격자 구조를 만들기 위해 다수의 수학 공식이 사용되었습니다. 나중에 자세히 정리하겠습니다. 필자도 수학 공식으로 다시 정리하여 어떤 의미인지를 분석하려면 시간이 많이 걸리기 때문입니다. 그리고, 이렇게 데이터를 표시할 일도 많지 않을 것 같습니다. ^^



1.3 Vectorizing Logistic Regression


   You will be using multiple one-vs-all logistic regression models to build a multi-class classifier. Since there are 10 classes, you will need to train 10 separate logistic regression classifiers. To make this training efficient, it is important to ensure that your code is well vectorized. In this section, you will implement a vectorized version of logistic regression that does not employ any for loops. You can use your code in the last exercise as a starting point for this exercise.


   멀티 클래스 분류기를 구현하기 위해 one-vs-all 로지스틱 회귀 모델을 사용합니다. 10개의 클래스가 있으므로 10개의 개별 로지스틱 회귀 분류기가 각각 학습해야 합니다. 효율적으로 학습하려면 벡터화 구현이 잘 되었는지 확인하는 것이 중요합니다. 여기 For 루프를 사용하지 않는 벡터화 구현으로 로지스틱 회귀를 구현합니다. 지난 실습에서 사용했던 코드를 그대로 사용합니다. 



1.3.1 Vectorizing the cost function (비용 함수 벡터화 구현)


   We will begin by writing a vectorized version of the cost function. Recall that in (unregularized) logistic regression, the cost function is


   To compute each element in the summation, we have to compute hθ(x(i)) for every example 

i sigmoid function. It turns out that we can compute this quickly for all our examples by using matrix multiplication. Let us define X and θ as


   벡터화된 비용 함수를 작성합니다. 정규화되지 않은 비용 함수는 다음과 같습니다. 비용 함수의 시그마 합산 부분을 계산하기 위해서는 시그모이드 함수를 이용하여 모든 학습 예제를 계산해야 합니다. 가설 hθ(x) = θ^Tx입니다.  



  Then, by computing the matrix product Xθ, we have


   행렬 X와 파라미터 θ를 다음과 같이 정의하고 행렬 곱셈을 계산합니다. 


   In the last equality, we used the fact that aT b = bT a if a and b are vectors. This allows us to compute the products θT x(i) for all our examples i in one line of code.

   Your job is to write the unregularized cost function in the file lrCostFunction.m Your implementation should use the strategy we presented above to calculate θTx(i). You should also use a vectorized approach for the rest of the cost function. A fully vectorized version of lrCostFunction.m should not contain any loops.


   θ^Tx = Xθ가 같다는 것을 이미 경험적으로 알고 있습니다. a와 b가 벡터 일 때 a^Tb = b^a입니다. 이 한 줄의 코드로 θ^Tx를 계산할 수 있습니다. 

   IrCostFunction.m 파일에 비 정규화된 비용 함수를 작성합니다. 나머지 비용 함수들도 모두 벡터화된 접근 방식을 사용합니다. IrCostFuction.m 에서 루프를 사용하지 말아야 합니다.


(힌트:

     함수를 작성할 때 성분 별 곱셈 연산 (.*)를 사용합니다.)



1.3.1 Vectorizing the gradient (기울기 벡터화 구현)


   Recall that the gradient of the (unregularized) logistic regression cost is a vector where the jth element is defined as


   정규화되지 않은 로지스틱 회귀의 비용 함수의 기울기는 j 번째 성분을 다음과 같이 정의합니다. 


   To vectorize this operation over the dataset, we start by writing out all the partial derivatives explicitly for all θj,


   모든 성분 θj에 대해 정규화되지 않은 로지스틱 회귀의 비용 함수의 기울기를 구하는 벡터화 구현은 다음과 같이 정의합니다. 



   Note that x(i) is a vector, while (hθ(x(i))−y(i)) is a scalar (single number). To understand the last step of the derivation, let βi = (hθ(x(i)) − y(i)) and observe that:


  x^(i)는 벡터이지만 hθ(x^(i))−y^(i)는 스칼라 값입니다.   βi의 값을 계산합니다. 



   The expression above allows us to compute all the partial derivatives without any loops. If you are comfortable with linear algebra, we encourage you to work through the matrix multiplications above to convince yourself that the vectorized version does the same computations. You should now implement Equation 1 to compute the correct vectorized gradient. Once you are done, complete the function lrCostFunction.m by implementing the gradient.


  위의 공식에서 모든 편미분 항 (편도 함수)를 계산합니다. 선형 대수에 익숙한 분들은 벡터화된 행렬 곱셈이 동일한 계산을 수행한다는 것을 알 것입니다. 방정식을 구현하여 벡터화된 기울기를 계산합니다. 완료하기 위해 IrCostFunction.m 파일을 작성합니다.



<해설>


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


clear; close all; clc         

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

[m, n] = size(X);           


(2) sigmoid.m 파일 분석


function g = sigmoid(z)

%SIGMOID 시그모이드 함수를 계산 

%   g = SIGMOID(z) z를 입력받아 g를 반환


g = 1.0 ./ (1.0 + exp(-z));


end


   이미 지난 실습에서 직접 동일한 코드를 개발하였으므로 분석만 합니다.  로지스틱 회귀의 가설은 다음과 같습니다. 

   따라서, 시그모이드 함수를 계산하는 공식은 다음과 같습니다.


g = 1.0 ./ (1.0 + exp(-z));



(3) IrCostFunction.m 파일 분석 



function [J, grad] = lrCostFunction(theta, X, y, lambda)

%LRCOSTFUNCTION 정규화된 로지스틱 회귀의 비용과 기울기를 계산 

%   J = LRCOSTFUNCTION(theta, X, y, lambda) 

%   


% 변수 초기화

m = length(y); % 학습 예제의 총 수 


% 반환할 비용과 기울기를 초기화  

J = 0;

grad = zeros(size(theta));


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

% Instructions: Compute the cost of a particular choice of theta.

%               You should set J to the cost.

%               Compute the partial derivatives and set grad to the partial

%               derivatives of the cost w.r.t. each parameter in theta

%

% 힌트: The computation of the cost function and gradients can be

%       시그모이드 함수를 별도로 계산하면 효과적으로 비용과 기울기를 계산할 수 있음

%

%           sigmoid(X * theta)

%

%       행렬의 각 행은 로지스틱 회귀 가설 함수를 활용하여 예제에 대한 예측치를 얻음 

%       예측치를 비용 함수와 기울기를 벡터화할 때 활용할 수 있음 

%

% 힌트: 

%       정규화된 비용 함수의 기울기를 계산하는 방법은 매우 다양함  

%       다음과 같은 방법도 있음

%           grad = (로지스틱 회귀의 비정규화된 기울기)

%           temp = theta; 

%           temp(1) = 0;   % theta 0는 정규화지 않음   

%           grad = grad + YOUR_CODE_HERE (using the temp variable)

%


z = X*theta;

J = -1/m*(y'*log(sigmoid(z)) + (1-y)'*log(1-sigmoid(z))) + lambda/(2*m)*theta(2:end)'*theta(2:end);


grad = 1/m *X'*(sigmoid(z) - y) + lambda/m * theta;

grad(1) = grad(1) - lambda/m*theta(1);


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


grad = grad(:);


end



  이미 지난 실습에서 직접 동일한 코드를 개발하였으므로 분석만 합니다.  로지스틱 회귀의 가설, 비용 함수와 정규화된 비용 함수 공식은 다음과 같습니다. 


   따라서, 지난 실습에서 정규화된 비용 함수를 계산하는 공식은 다음과 같습니다. 처음부터 벡터화된 구현으로 구현하였습니다. 실제로 For 루프를 이용하는 것보다 벡터화 구현이 훨씬 더 간단합니다. 


z = X*theta;

J = -1/m*(y'*log(sigmoid(z)) + (1-y)'*log(1-sigmoid(z))) + lambda/(2*m)*theta(2:end)'*theta(2:end);


grad = 1/m *X'*(sigmoid(z) - y) + lambda/m * theta;

grad(1) = grad(1) - lambda/m*theta(1);



(3) 테스트 변수 입력


   ex3.m 파일에서 주어진 값으로 IrCostFunction.m 파일이 제대로 동작하는 지를 확인합니다. 


theta_t = [-2; -1; 1; 2];            % theta_t는 4X1차원 벡터


% 5X1 열 벡터와 5X3 행렬을 조합하여 5 X 4 행렬 X_t를 생성 

X_t = [ones(5,1) reshape(1:15,5,3)/10];     


y_t = ([1;0;1;0;1] >= 0.5);   % y_t는 4X1차원 벡터

lambda_t = 3;

[J grad] = lrCostFunction(theta_t, X_t, y_t, lambda_t);



 여기서,  5X4 행렬인 변수 X_t 가 어떤 모양인지를 정리합니다.   


>> reshape(1:15, 5, 3)                  % 5X3 행렬 생성

ans =

    1    6   11

    2    7   12

    3    8   13

    4    9   14

    5   10   15


>> reshape(1:15,5,3)/10         % 5X3 행렬의 각 성분을 10으로 나눔

ans =

   0.10000   0.60000   1.10000

   0.20000   0.70000   1.20000

   0.30000   0.80000   1.30000

   0.40000   0.90000   1.40000

   0.50000   1.00000   1.50000


>> X_t = [ones(5,1) reshape(1:15,5,3)/10]    % 행렬 조합

X_t =

   1.00000   0.10000   0.60000   1.10000

   1.00000   0.20000   0.70000   1.20000

   1.00000   0.30000   0.80000   1.30000

   1.00000   0.40000   0.90000   1.40000

   1.00000   0.50000   1.00000   1.50000



(4) 테스트 실행


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


theta_t = [-2; -1; 1; 2];          

X_t = [ones(5,1) reshape(1:15,5,3)/10];     

y_t = ([1;0;1;0;1] >= 0.5);   

lambda_t = 3;


[J grad] = lrCostFunction(theta_t, X_t, y_t, lambda_t);


결과가 제대로 나왔는 지를 다음과 같이 입력합니다. 


fprintf('\nCost: %f\n', J);                          % Cost 문자열과 변수 J의 값 출력

fprintf('Expected cost: 2.534819\n');        


Cost: 2.534819

Expected cost: 2.534819


fprintf('Gradients:\n');

fprintf(' %f \n', grad);                           % 변수 grad값 출력

fprintf('Expected gradients:\n');

fprintf(' 0.146561\n -0.548558\n 0.724722\n 1.398003\n');


Gradients:

 0.146561

 -0.548558

 0.724722

 1.398003


Expected gradients:

 0.146561

 -0.548558

 0.724722

 1.398003



<정답 >


>> ex3

Loading and Visualizing Data ...

Program paused. Press enter to continue.


Testing lrCostFunction() with regularization

Cost: 2.534819

Expected cost: 2.534819

Gradients:

 0.146561

 -0.548558

 0.724722

 1.398003

Expected gradients:

 0.146561

 -0.548558

 0.724722

 1.398003



매거진의 이전글 머신러닝 옥타브 실습 (2-4) : 로지스틱 회귀
작품 선택
키워드 선택 0 / 3 0
댓글여부
afliean
브런치는 최신 브라우저에 최적화 되어있습니다. IE chrome safari