brunch

You can make anything
by writing

C.S.Lewis

by 라인하트 Jan 30. 2021

머신러닝 옥타브 실습(6-2): 서포트 벡터 머신 구현

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


Programming Exercise 6: 

Support Vector Machine (서포트 벡터 머신) 


1. Support Vector Machine (서포트 벡터 머신)  


1.2 SVM with Gaussian Kernels (가우시안 커널과 SVM)


1.2.1 Gaussian Kernel (가우시안 커널)


   To find non-linear decision boundaries with the SVM, we need to first im- plement a Gaussian kernel. You can think of the Gaussian kernel as a similarity function that measures the “distance” between a pair of examples, (x(i),x(j)). The Gaussian kernel is also parameterized by a bandwidth parameter, σ, which determines how fast the similarity metric decreases (to 0) as the examples are further apart.

   You should now complete the code in gaussianKernel.m to compute the Gaussian kernel between two examples, (x^(i),x^(j)). The Gaussian kernel function is defined as:



   Once you’ve completed the function gaussianKernel.m, the script ex6.m will test your kernel function on two provided examples and you should expect to see a value of 0.324652.

   You should now submit your solutions.


   SVM으로 비선형 결정 경계를 찾으려면 먼저 가우스 커널을 구현합니다. 가우시안 커널은 한 쌍의 예 (x^(i), x^(j)) 사이의 거리를 측정하는 유사도 함수입니다. 가우시안 커널은 유사성 지표가 얼마나 빨리 0으로 감소되는 지를 결정하는 대역폭 파라미터 σ가 있습니다.

   두 예제 (x^(i), x^(j)) 사이의 가우스 커널을 계산하는 공식은 gaussianKernel.m 파일에 작성합니다. 가우스 커널 함수는 다음과 같이 정의합니다.

   gaussianKernel.m 파일을 완료하고 ex6.m 파일에서 두 가지 예제에 대한 커널 함수를 테스트합니다. 결과값은 0.324652입니다. 이제 솔루션을 제출합니다. 



< Part 3: 가우시안 커널 구현>


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


clear ;             % 옥타브 프로그램에 모든 변수를 제거

close all;         % 터미널 이외의 창을 닫음

clc                   % 터미널을 깨끗이 정리 


load ('ex6data1.mat');    % 옥타브 프로그램으로 데이터 업로드 

[m,n] = size(X);                % 학습 예제의 수와 피처의 수를 정의 


(2) 가우시안 커널 파일에 제공할 변수 정의


x1 = [1 2 1];

x2 = [0 4 -1]; 

sigma = 2;


(3) gaussianKernel.m 파일 분석



function sim = gaussianKernel(x1, x2, sigma)

%RBFKERNEL x1과 x2 사이의 가우시안 커널을 반환  

%   


%  행 벡터 x1과 x2를 열 벡터로 전환

x1 = x1(:); x2 = x2(:);


% 반환할 변수를 선언 

sim = 0;


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

% Instructions: 

%               이 함수는 x1과 x2 사이의 유사도를 반환 

%               대역폭 시그마와 함께 가우시안 커널을 계산

%




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

end


(4) 가우시안 커널 함수 작성


   가우시안 커널 함수는 다음과 같습니다. 

   두 예제 x^(1)과 x^(2)는 3 X1 차원 벡터이고, 다음과 같습니다. 


 3 X 1 벡터로 된 두 예제 사이의 거리의 제곱을 대역폭 파라미터 계산하는  2σ^2으로 나누어줍니다.

 

우선, 두 벡터 사이의 거리를 계산하여 변수 distance에 넣습니다.

 

distance = x1 - x2       


>> distance = x1 - x2

distance =

   1

  -2

   2


   3 X 1차원 벡터 distance를 제곱하는 방법 중 많이 사용하는 방법은 distance를 전치하여 곱하는 방법입니다.  벡터 곱셈은 각 성분을 제곱한 후 합산한 결과를 보여줍니다.  


>> distance'*distance

ans =  9


   또 다른 방법은 벡터의 성분을 직접 제곱한 후에 합산하는 것입니다. 


>> distance .^2

ans =

   1

   4

   4

>> sum(distance .^2)

ans =  9


   여기서는 훨씬 깔끔해 보이는 벡터 곱셈을 사용합니다. 가우시안 커널을 계산하는 코드는 다음과 같습니다.


   sim = exp( - distance'*distance / (2*sigma^2))


실제 대입하면 다음과 같은 결과를 얻습니다.


>> sim = exp(- distance'*distance / (2*sigma^2))

sim =  0.32465



<Part 3 : 정답 확인>


gaussianKernel.m 파일을 다음과 같이 업데이트합니다. 


function sim = gaussianKernel(x1, x2, sigma)

%RBFKERNEL x1과 x2 사이의 가우시안 커널을 반환  

%   


%  행 벡터 x1과 x2를 열 벡터로 전환

x1 = x1(:); x2 = x2(:);


% 반환할 변수를 선언 

sim = 0;


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

% Instructions: 

%               이 함수는 x1과 x2 사이의 유사도를 반환 

%               대역폭 시그마와 함께 가우시안 커널을 계산

%


distance = x1 - x2;

sim = exp( - distance'*distance / (2*sigma^2));


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

end


ex6.m 파일의 내용대로 실행합니다.


>> x1 = [1 2 1]; x2 = [0 4 -1]; sigma = 2;

>> sim = gaussianKernel(x1, x2, sigma);

>> fprintf(['Gaussian Kernel between x1 = [1; 2; 1], x2 = [0; 4; -1], sigma = %f :''\n\t%f\n(for sigma = 2, this value should be about 0.324652)\n'], sigma, sim);


Gaussian Kernel between x1 = [1; 2; 1], x2 = [0; 4; -1], sigma = 2.000000 :

        0.324652

(for sigma = 2, this value should be about 0.324652)





1.2.2 Example Dataset 2 (두 번째 데이터 셋)

   The next part in ex6.m will load and plot dataset 2 (Figure 4). From the figure, you can obserse that there is no linear decision boundary that separates the positive and negative examples for this dataset. However, by using the Gaussian kernel with the SVM, you will be able to learn a non-linear decision boundary that can perform reasonably well for the dataset.

   If you have correctly implemented the Gaussian kernel function, ex6.m will proceed to train the SVM with the Gaussian kernel on this dataset.


   ex6.m 파일의 다음 실습은 데이터셋 2를 옥타브 프로그램에 로딩하고 그림 4와 같이 도식화합니다.  데이터셋의 파지티브 예제와 네거티브 예제에 대한 결정 경계는 아직 없습니다. SVM과 가우시안 커널을 활용하면 데이터셋 2에 합리적인 비선형 결정 경계를 그릴 수 있습니다. 

   ex6.m 파일은 바로 전 실습에서 구현한 가우시안 커널 함수를  이용하여 SVM 훈련을 진행합니다. 




   Figure 5 shows the decision boundary found by the SVM with a Gaussian kernel. The decision boundary is able to separate most of the positive and negative examples correctly and follows the contours of the dataset well.


   그림 5는 가우시안 커널을 사용하는 SVM이 학습한 결정 경계를 표시합니다. 결정 경계는 파지티브 예제와 네거티브 에제를 올바르게 분리할 수 있고, 데이터 셋의 윤곽을 잘 따릅니다. 


< Part 4 : 데이터셋 2 시각화 (해설)> 


(1) 데이터 업로드 


clear ;             % 옥타브 프로그램에 모든 변수를 제거

close all;         % 터미널 이외의 창을 닫음

clc                   % 터미널을 깨끗이 정리 


load ('ex6data2.mat');    % 옥타브 프로그램으로 데이터 업로드 


(2) 데이터 그리기


clear; close all; clc;

load('ex6data2.mat');


plotData(X,y);



< Part 5 : RBF 커널로 SVM 학습하기 (해설)> 


(1) 데이터 업로드 


clear; close all; clc;

load('ex6data2.mat');


(2) 필수 파라미터 설정


C = 1;

sigma = 0.1;


(3) SVM 학습


   SVM이 학습하는 코드는 다음과 같습니다. 지난 실습에서 SVM 학습을 다루었습니다.  


model= svmTrain(X, y, C, @(x1, x2) gaussianKernel(x1, x2, sigma)); 


>> C = 1;

>> model= svmTrain(X, y, C, @(x1, x2) gaussianKernel);


Training ........................................................................................................................................................................................................................................................................................................................................................................................................... Done!


>> model.w

ans =

  -0.54391

   1.56961


>> model.b

ans =  0.40667


   SVM 학습의 결과로 얻어진 가중치 model.w 와 인터셉트항 b가 구해졌습니다. 



(4) visualizeBoundary.m 파일 분석(1)



function visualizeBoundary(X, y, model, varargin)

%VISUALIZEBOUNDARY SVM이 학습한 비선형 결정 경계를 도식화 

%   VISUALIZEBOUNDARYLINEAR(X, y, model) 

%   데이터와 비선형 결정 경계를 같이 도식화 



% 학습 데이터를 도식화 

plotData(X, y)


% 격자 구조에 따라 예측을 분류 

x1plot = linspace(min(X(:,1)), max(X(:,1)), 100)';

x2plot = linspace(min(X(:,2)), max(X(:,2)), 100)';


[X1, X2] = meshgrid(x1plot, x2plot);

vals = zeros(size(X1));

for i = 1:size(X1, 2)

   this_X = [X1(:, i), X2(:, i)];

   vals(:, i) = svmPredict(model, this_X);

end


% Plot the SVM boundary

hold on

contour(X1, X2, vals, [0.5 0.5], 'b');

hold off;


end



   여기서 수평축과 수직축을 100개의 구간으로 나누기 위해 linspace() 함수를 사용합니다. linspace() 함수는 최소값과 최대값을 사이를 원하는 크기로 나눕니다. 아래 코드는 좌표를 최소값과 최대값 사이를 100개로 나눕니다.  


x1plot = linspace(min(X(:,1)), max(X(:,1)),100);

x2plot = linspace(min(X(:,2)), max(X(:,2)),100);


  linspace()함수는 행 벡터로 결과를 반환하므로 열 벡터로 전환하기 위해 전치합니다. 


x1plot = linspace(min(X(:,1)), max(X(:,1)),100)';

x2plot = linspace(min(X(:,2)), max(X(:,2)),100)';


   수평축과 수직축이 100개의 구간으로 나뉘었고, 전체 2차원 평면으로 확장해야 합니다. 즉, x1plot은 100 X 1 차원 행렬이고, x2plot은 100 X 1차원 행렬입니다. 두 값을 결합하여 2차원 평면의 좌표로 인식하기 위해 100 X 100차원으로 만들어야 합니다. meshgrid() 함수는 2차원 또는 3차원 격자 구조를 만듭니다.


>> x1 = [ 1 2 3]

x1 =

   1   2   3


>> x2 = [1 2 3 4 5]

x2 =

   1   2   3   4   5


>> [X1,Y1] = meshgrid(x1,x2)

X1 =

   1   2   3

   1   2   3

   1   2   3

   1   2   3

   1   2   3


Y1 =

   1   1   1

   2   2   2

   3   3   3

   4   4   4

   5   5   5


   meshgrid() 함수를 이용하여 2차원 평면의 격자구조를 표현합니다. X1과 X2는 각각 100 X 100차원의 행렬입니다. 


[X1, X2] = meshgrid(x1plot, x2plot);


   각 좌표마다 예측 값을 저장하는 변수 vals는 X1의 크기로 초기화합니다. 


vals = zeros(size(X1));


   100 X 100차원 행렬을 100 X1 차원 벡터로 분할하고, for 루프를 활용하여 100번을 반복합니다. this_X는 100 X2 차원 벡터로 수평축 (X1)의 값이 고정되었을 때 수직축(X2)의 값을 최소값에서 최대값까지 변화를 주는 것입니다. 그리고 svmPredict 파일을 호출하여 model과 svmPredict를 처리합니다. svmPredict.m 파일은 SVM이 학습한 결과인 model을 가지고 각 좌표에 따른 분류 값을 반환합니다.  


for i = 1:size(X1, 2)

   this_X = [X1(:, i), X2(:, i)];

   vals(:, i) = svmPredict(model, this_X);

end


(5) svmPredict.m 파일 분석



function pred = svmPredict(model, X)

%SVMPREDICT svmTrain.m 파일로 학습한 SVM 모델로 예측에 대한 벡터를 반환

%   pred = SVMPREDICT(model, X)

%       X는 mXn 차원 행렬이고, 각 예제는 행으로 표시

%       pred는 0 또는 1의 값을 예측하는 m X 1 벡터 

%


% 열벡터인지 확인, 각 예제에 대한 예측 

if (size(X, 2) == 1).  % 각 예제가 행 벡터라면 열 벡터로 전환 

    X = X';

end


% 데이터 셋 변수 정의  

m = size(X, 1);               % m은 총예제의 수  

p = zeros(m, 1);            % p는 각 예제에 대한 예측 값을 저장

pred = zeros(m, 1);      % pred는 각 예제에 대한 예측 값을 저장


if strcmp(func2str(model.kernelFunction), 'linearKernel')

    % 선형 커널(linear kernel)이 반환하는 가중치 w와 바이어스 값을 반환 값을 가설

    p = X * model.w + model.b;


elseif strfind(func2str(model.kernelFunction), 'gaussianKernel')

    % 벡터화된 RBF 커널인 가우시안 커널 

    % 매 예제에 대한 커널을 계산하는 것과 같음 

    X1 = sum(X.^2, 2);

    X2 = sum(model.X.^2, 2)';

    K = bsxfun(@plus, X1, bsxfun(@plus, X2, - 2 * X * model.X'));

    K = model.kernelFunction(1, 0) .^ K;

    K = bsxfun(@times, model.y', K);

    K = bsxfun(@times, model.alphas', K);

    p = sum(K, 2);

else

    % 비선형 커널 

    for i = 1:m

        prediction = 0;

        for j = 1:size(model.X, 1)

            prediction = prediction + ...

                model.alphas(j) * model.y(j) * ...

                model.kernelFunction(X(i,:)', model.X(j,:)');

        end

        p(i) = prediction + model.b;

    end

end


% Convert predictions into 0 / 1

pred(p >= 0) =  1;

pred(p <  0) =  0;


end


   svmPredict.m 파일은 모델을 계산하기 위해 사용한 커널이 무엇인지에 따라 가설 hθ(x)를 계산하는 방법을 달리합니다. svmTrain.m 파일에서 사용한 model 변수를 확인하는 방법은 model의 하위 변수인 model.kernelFunction 에서 확인합니다.   

   

   지난 실습에서 두 벡터의 내적을 계산하는 선형 커널을 다루었습니다. 따라서, 선형 커널을 가리키는 linearKernel 이름이 같은 지를 확인하기 위해 strcmp() 함수를 사용합니다. 


>> S1

S1 = Yes

>> S2

S2 = Yes

>> S3

S3 = Yes No

>> strcmp(S1, S2).  % 두 문자열이 같으면 1을 반환

ans =  1

>> strcmp(S1, S3).  % 두 문자열이 다르면 0을 반환

ans = 0


   따라서, 다음과 같이 조건문을 만들 수 있습니다. 


>> model.kernelFunction

@linearKernel


>> func2str(model.kernelFunction)        % 커널 함수명을 문자열로 변환

linearKernel


>> strcmp(func2str(model.kernelFunction), 'linearKernel') 

ans = 1


   이번 실습에서 가우시안 커널을 사용했습니다. 가우시안 커널의 함수명을 여러 문자열로 나타냅니다. 따라서, strcmp() 함수 대신에 strfind() 함수를 사용합니다.  strfind() 함수는 첫 번째 문자열에 두 번째 문자열을 포함한 인덱스를 반환합니다. 


>> S1

S1 = Yes

>> S2

S2 = Yes

>> S3

S3 = Yes No

>> strfind(S3, S1)    % Yes 문자열이 시작되는 위치의 인덱스를 반환

ans =  1


>> S4 = 'Yes, I am going to do that'

S4 = Yes, I am going to do that

>> strfind(S4, 'do')          % 'do'가 시작되는 위치의 인덱스를 반환

ans =  20


   따라서, 다음과 같은 조건문을 만듭니다. 


>> model.kernelFunction

ans =

@(x1, x2) gaussianKernel (x1, x2, sigma)


>> func2str(model.kernelFunction)        % 커널 함수명을 문자열로 변환

ans = @(x1, x2) gaussianKernel (x1, x2, sigma)


>> strfind(func2str(model.kernelFunction), 'gaussianKernel')

ans = 11



  선형 커널의 예측값은 다음과 같이 계산합니다. 여기서 행렬 X는 100칸으로 이루어진 2차원 격자구조 X1과 X2의 값 중에 각 첫 번째 열입니다. 행렬 X는 100X2차원 행렬로 첫 번째 열은 값이 고정되어 있고, 두 번째 열의 값만 달라집니다. 


[X1, X2] = meshgrid(x1plot, x2plot);

vals = zeros(size(X1));


for i = 1:size(X1, 2)

       this_X = [X1(:, i), X2(:, i)];

end


>> size(this_X)

ans =

   100     2


>> this_X

this_X =

   0.99885   0.40263

   0.99885   0.40855

   0.99885   0.41447

   0.99885   0.42039

   ...

   0.99885   0.45590

   0.99885   0.46182

  0.99885   0.98268

   0.99885   0.98860


  선형 커널로 학습한 SVM의 가중치 model.w 와 바이어스 b를 알고 있습니다. 행렬 X는 100 X 2차원 행렬이고, model.w는 2X1차원 벡터입니다. 두 값의 계산 결과는 실수이고 바이어스항 b도 실수입니다.  


    p = X * model.w + model.b;

 

  p의 값을 확인하였고, p의 값을 기준으로 0 또는 1의 값을 결정하고, 값을 반환합니다. 


pred(p >= 0) =  1;

pred(p <  0) =  0;


   RBF 커널과 비선형 커널의 분석은 생략합니다. 격자구조마다 주어진 커널로 계산하는 방식은 동일합니다.



(6) visualizeBoundary.m 파일 분석(2)


   그림을 100 X 100 칸으로 나누었습니다. SVM이 학습한 모델을 기준으로 각 칸마다 값을 분류하였습니다. 변수 vals은 100 X 100 행렬로 모든 좌표에 해당하는 결과 값을 가지고 있습니다. 


clear; close all; clc;

load('ex6data2.mat');


C = 1; sigma = 0.1;

model= svmTrain(X, y, C, @(x1, x2) gaussianKernel(x1, x2, sigma)); 


x1plot = linspace(min(X(:,1)), max(X(:,1)), 100)';

x2plot = linspace(min(X(:,2)), max(X(:,2)), 100)';

[X1, X2] = meshgrid(x1plot, x2plot);

vals = zeros(size(X1));

for i = 1:size(X1, 2)

   this_X = [X1(:, i), X2(:, i)];

   vals(:, i) = svmPredict(model, this_X);

end



   이제 그림을 그리기 위한 준비가 완료되었습니다. 매우 복잡한 비선형 결정 경계를 그리는 함수는 contour()입니다. contour() 함수는 선형 회귀에서 등고선 그래프를 그릴 때 사용했었습니다. 


>> X1

X1 =

 Columns 1 through 6:

   0.044931   0.054566   0.064202   0.073837   0.083473   0.093109

   0.044931   0.054566   0.064202   0.073837   0.083473   0.093109

   0.044931   0.054566   0.064202   0.073837   0.083473   0.093109

   0.044931   0.054566   0.064202   0.073837   0.083473   0.093109


>> contour(X1)  % 행의 값은 고정, 열이 바뀔 때마다 값이 변함 


>> X2

X2 =

 Columns 1 through 6:

   0.40263   0.40263   0.40263   0.40263   0.40263   0.40263

   0.40855   0.40855   0.40855   0.40855   0.40855   0.40855

   0.41447   0.41447   0.41447   0.41447   0.41447   0.41447

   0.42039   0.42039   0.42039   0.42039   0.42039   0.42039


>>  contour (X2). % 행의 값은 고정, 열이 바뀔 때마다 값이 변함




>> contour(X1, X2, vals)  % X1, X2, model로 예측한 값을 표시


>> contour(X1, X2, vals, [0.5 0.5], 'b');



   여기서 [0.5 0.5]는 등고선의 높이를 나타내고, 'b'는 파란색을 나타냅니다. 


plotData.m 파일을 이용하여 데이터를 함께 그립니다.  


매거진의 이전글 머신러닝 옥타브 실습(6-1): 서포트 벡터 머신 구현
작품 선택
키워드 선택 0 / 3 0
댓글여부
afliean
브런치는 최신 브라우저에 최적화 되어있습니다. IE chrome safari