brunch

You can make anything
by writing

C.S.Lewis

by 라인하트 Feb 06. 2021

머신러닝 옥타브 실습(6-5): 스팸 이메일 분류기

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


Programming Exercise 6: 

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


2. Spam Classification (스팸 분류기)  


2.2 Extracting Features from Emails (이메일에서 피처 추출하기)


   You will now implement the feature extraction that converts each email into a vector in Rn. For this exercise, you will be using n = # words in vocabulary list. Specifically, the feature xi ∈ {0, 1} for an email corresponds to whether the i-th word in the dictionary occurs in the email. That is, xi = 1 if the i-th word is in the email and xi = 0 if the i-th word is not present in the email.

   Thus, for a typical email, this feature would look like:


   각 이메일을 R^(n) 차원의 벡터로 변환하는 피처 추출을 구현합니다. 이번 실습에서 단어 목록에서 n = # 단어를 사용합니다. 예를 들면, 이메일 피처 x(i)는 0 또는 1의 값을 가지고, 목록의 i 번째 단어가 이메일에 있는지 여부를 나타냅니다. 즉, i 번째 단어가 이메일에 있으면 xi = 1이고, i 번째 단어가 이메일에 없으면 xi = 0입니다. 

   따라서, 일반적인 이메일의 피처는 다음과 같습니다. 


   You should now complete the code in emailFeatures.m to generate a feature vector for an email, given the word_indices. Once you have implemented emailFeatures.m, the next part of ex6 spam.m will run your code on the email sample. You should see that the feature vec- tor had length 1899 and 45 non-zero entries.


    word_indices를 emailFeatures.m 파일에 입력하여 피처 벡터를 생성하는 코드를 완성합니다.  ex6_spam.m 파일은 emailFeatures.m 파일 호출하고 이메일 샘플에서 코드를 실행합니다. 피처 벡터의 길이는 1899이고 0이 아닌 45개의 항목이 있습니다. 



<Part 2 : 피처 추출 > 


(1) 데이터 업로드


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

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

clc                   % 터미널을 깨끗이 정리 


(2) emailFeatures.m 파일 분석



function x = emailFeatures(word_indices)

%EMAILFEATURES  word_indices 벡터로부터 피처 벡터를 생성

%   x = EMAILFEATURES(word_indices) word_indices 벡터로부터 피처 벡터를 생성 


% 단어 목록의 수 

n = 1899;


% 반환할 변수를 초기화 

x = zeros(n, 1);


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

% Instructions:  이메일에서 추출한 word_indices를 바탕으로 피처 벡터를 반환 

%               각 이메일을 전 처리하고 단어 목록(1899개)의 인덱스를 추출

%               word_indices는 이메일 하나에 있는 단어 리스트

%               예를 들어, 다음과 같은 이메일 텍스트가 있다면

%                "The quick brown fox jumped over the lazy dog"

%  

%                  word_indices 벡터는 다음과 같이 표현               

%                   60  100   33   44   10     53  60  58   5

%

%                  즉, 다음과 같이 매핑 

%                   the   -- 60

%                   quick -- 100

%                   ...

%

%              이메일에 특정 단어들을 추출하여 word_indices 벡터를 생성

%               단어 목록의 i 번째 단어와 일치하면  x(i) = 1. 

%              예를 들면, 'the'는 단어 목록의 60번째 단어이므로   x(60) = 1.

%              이메일 당 피처 벡터는 다음과 같이 표현

%               x = [ 0 0 0 0 1 0 0 0 ... 0 0 0 0 1 ... 0 0 0 1 0 ..];





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


end




(3) 피처 벡터 구현


   지난 실습에서 이메일을 전처리하고 이메일의 내용을 구성하는 단어들을 추출하였습니다. 단어 목록에 같은 단어가 있을 경우 단어의 위치인 인덱스 값을 word_indices에 저장하였습니다. 


>> word_indices

word_indices =


     86

    916

    794

   1077

    883

    370

   1699

    790

   1822

....

   1477

     71

    530

   1699

    531


   따라서, 각 이메일의 피처 벡터는 1899 X1 차원이고 초기값은 0입니다.   word_indices의 값과 같은 위치의 피처 벡터 x(i)의 값을 1로 대체합니다. 예를 들면, word_indices(1) = 86이므로 x(86)=1로 전환합니다. 이렇게 모든 값에 대해 반복합니다.


for i = 1: length(word_indices)

    x(word_indices(i)) = 1;

end


   제대로 되었는 지를 확인하기 위해 다음과 같이 실행합니다.


c = 0;

for i = 1: n

    if x(i) == 1

        c = c+1;

    end

end


>> c = 0;

>> for i = 1: n

    if x(i) == 1

        c = c+1;

    end

end


>> c

c =  45


피처 벡터 x의 개수는 45개입니다. 문제의 정답과 일치합니다.


(4) emailFeatures.m 파일에 정답 입력


function x = emailFeatures(word_indices)

%EMAILFEATURES  word_indices 벡터로부터 피처 벡터를 생성

%   x = EMAILFEATURES(word_indices) word_indices 벡터로부터 피처 벡터를 생성 


% 단어 목록의 수 

n = 1899;


% 반환할 변수를 초기화 

x = zeros(n, 1);


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

% Instructions:  이메일에서 추출한 word_indices를 바탕으로 피처 벡터를 반환 

%               각 이메일을 전처리하고 단어 목록(1899개)의 인덱스를 추출

%               word_indices는 이메일 하나에 있는 단어 리스트

%               예를 들어, 다음과 같은 이메일 텍스트가 있다면

%                "The quick brown fox jumped over the lazy dog"

%  

%                  word_indices 벡터는 다음과 같이 표현               

%                   60  100   33   44   10     53  60  58   5

%

%                  즉, 다음과 같이 매핑 

%                   the   -- 60

%                   quick -- 100

%                   ...

%

%              이메일에 특정 단어들을 추출하여 word_indices 벡터를 생성

%               단어 목록의 i 번째 단어와 일치하면  x(i) = 1. 

%              예를 들면, 'the'는 단어 목록의 60번째 단어이므로   x(60) = 1.

%              이메일 당 피처 벡터는 다음과 같이 표현

%               x = [ 0 0 0 0 1 0 0 0 ... 0 0 0 0 1 ... 0 0 0 1 0 ..];


for i = 1: length(word_indices)

    x(word_indices(i)) = 1;

end



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


end


< 결과 확인>




2.3 Training SVM for Spam Classification 

       (스팸 분류를 위한 SVM 학습)


   After you have completed the feature extraction functions, the next step of ex6 spam.m will load a preprocessed training dataset that will be used to train a SVM classifier. spamTrain.mat contains 4000 training examples of spam and non-spam email, while spamTest.mat contains 1000 test examples. Each original email was processed using the processEmail and emailFeatures functions and converted into a vector x(i) ∈ R1899.

   After loading the dataset, ex6 spam.m will proceed to train a SVM to classify between spam (y = 1) and non-spam (y = 0) emails. Once the training completes, you should see that the classifier gets a training accuracy of about 99.8% and a test accuracy of about 98.5%.


   지금까지 피처 추출 함수를 완성하였습니다. 다음 단계는 SVM 분류기를 학습하기 위해 사용할  전처리된 학습 데이터 셋 spamTrain.mat을 로드합니다.  spamTrain.mat 파일은 4,000개의 스팸 및 비스팸 이메일이 있고, spamTest.mat 파일은 1,000 테스트 셋이 있습니다. 각 원본 이메일은 processEmail.m 및 eamilFeature.m 함수로 전치리하여 벡터 x(i)는 R^(1899) 차원입니다.

   데이터 셋을 로드한 후 ex6_spam.m 파일은 스팸(y=1) 및 비스팸(y=0)을 분류하고 SVM을 훈련합니다. 훈련이 완료되면 분류기는 99.8%의 학습 정확도와 98.5%의 테스트 정확도를 얻습니다. 


<Part 3 : 스팸 분류를 위한 선형 SVM 학습하기> 


(1) 데이터 업로드


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

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

clc                   % 터미널을 깨끗이 정리 


load('spamTrain.mat');

[m n] = size(X);


   옥타브 프로그램에서 로드된 데이터를 확인합니다. 


>> whos

Variables in the current scope:


   Attr Name        Size                     Bytes  Class

   ==== ====        ====                     =====  =====

        X                  4000x1899             60768000  double

        m                 1x1                            8  double

        n                  1x1                            8  double

        y                  4000x1                      32000  double


Total is 7600002 elements using 60800016 bytes


(2) SVM 학습하기


   4,000개의 학습 예제를 선형 커널로 학습합니다. 


C = 0.1;

model = svmTrain(X, y, C, @linearKernel);


   학습 결과는 다음과 같습니다. 


>> size(model.w)

ans =

   1899      1

   

(3) 예측 정확도 측정


   학습 셋의 정확도를 예측합니다. 

 

p = svmPredict(model, X)


   4,000개의 학습 예제에 대한 예측 결과입니다.


>> size(p)

ans =

   4000      1


   실제 y 값과 비교하여 평균을 냅니다. 


mean(double(p==y))*100


>> mean(double(p==y))*100

ans =  99.825


<Part 4 : 스팸 분류 테스트>


(1) 데이터 로드


    테스트 셋 데이터를 옥타브 프로그램에 로드합니다. 

 

load('spamTest.mat');


>> load('spamTest.mat');

>> whos

Variables in the current scope:


   Attr Name        Size                     Bytes  Class

   ==== ====        ====                     =====  =====

        C           1x1                          8  double

        X        4000x1899                60768000  double

        Xtest    1000x1899                15192000  double

        ans         1x1                          8  double

        m           1x1                          8  double

        model       1x1                   12075144  struct

        n           1x1                          8  double

        p        4000x1                      32000  double

        y        4000x1                      32000  double

        ytest    1000x1                       8000  double



(2) 예측 정확도 측정


   이미 계산한 모델로 학습 셋의 정확도를 예측합니다. 

 

p = svmPredict(model, Xtest);


   1,000개의 학습 예제에 대한 예측 결과입니다.


>> size(p)

ans =

   1000      1


   실제 y 값과 비교하여 평균을 냅니다. 


mean(double(p==ytest))*100


>> mean(double(p==ytest))*100

ans =  98.800




2.4 Top Predictors for Spam

        (스팸 이메일이 많이 포함하는 Top Predictor 찾기) 



   To better understand how the spam classifier works, we can inspect the parameters to see which words the classifier thinks are the most predictive of spam. The next step of ex6 spam.m finds the parameters with the largest positive values in the classifier and displays the corresponding words (Figure 12). Thus, if an email contains words such as “guarantee”, “remove”, “dollar”, and “price” (the top predictors shown in Figure 12), it is likely to be classified as spam.


   스팸 분류기의 동작 방식을 이해하기 위해 파라미터를 검사하여 분류자가 스팸을 가장 잘 예측한다고 생각하는 단어를 확인할 수 있습니다. ex6_spam.m의 다음 단계는 분류자에서 가장 큰 양수 값을 가진 파라미터를 찾아 해당 단어를 그림 12와 같이 표시합니다.  전자메일에 “guarantee”, “remove”, “dollar”, and “price” 와 같은 단어가 포함된 이메일을 스팸으로 분류될 가능성이 높습니다. 



<Part 5 : 스팸 분류 테스트>


   선형 SVM은 스팸 이메일 분류를 결정하기 위해 1899개의 단어에 가중치를 사용합니다. 분류 기안에 가장 높은 가중치를 가진 단어가 Top Predictors입니다.  


(1) 가중치의 크기별로 정렬


  데이터를 정렬할 때 sort() 함수를 사용합니다. Sort() 함수는 기본적으로 열 단위로 행렬 A를 성분의 크기 순서로 나열합니다. 


>> A

A =

   1   5

   3   8

  -1   1


>> sort(A)  % 오름 차순으로 정렬

ans =

  -1   1

   1   5

   3   8 


>> sort(A,2)   % 행 단위로 정렬

ans =

   1   5

   3   8

  -1   1


>> sort (A, 'descend')         % 열 단위 내림차순 정렬

ans =

   3   8

   1   5

  -1   1


>> sort (A, 2, 'descend')    % 행단 위 내림차순 정렬

ans =

   5   1

   8   3

   1  -1


   sort() 함수는 정렬을 할 때 기존 행렬의 인덱스를 그대로 반환합니다. 


>> A

A =

   1   5

   3   8

  -1   1


>> [B I ] = sort(A)       

B =

  -1   1

   1   5

   3   8


I =

   3   3

   1   1

   2   2


   여기서 행렬 B는 행렬 A의 성분을 열 단위 오름차순으로 정렬한 것입니다. 행렬 I는 행렬 B의 성분이 원래 행렬 A의 인덱스를 나타냅니다. 예를 들어 B(1) = -1입니다. B(1)의 값의 A의 원래 위치를 알기 위해서는 B(1)의 값의 인덱스 I(1) = 3입니다. A(3) = -1입니다. 즉, B(1) = A(I(1))


>> B(1)

ans = -1


>> A(I(1))

ans = -1

   

   sort() 함수는 숫자뿐만 아니라 문자도 정렬합니다. 


   sort() 함수로 가중치 벡터 model.w를 재 정렬합니다. 


[weight index] = sort(model.w, 'descend');

 

   

(2) 단어 목록 불러오기


   vocabList = getVocabList();


>> vocabList = getVocabList();

>> vocabList

vocabList =

{

  [1,1] = aa

  [2,1] = ab

  [3,1] = abil

  [4,1] = abl

  [5,1] = about

  [6,1] = abov

...


(3) 상위 15개의 단어 목록 표시


for i = 1:15

    fprintf('%-15s(%f)\n',vocabList{index(i)}, weight(i));

end


>> for i = 1:15

    fprintf('%-15s(%f)\n',vocabList{index(i)}, weight(i));

end


our            (0.499759)

click          (0.469201)

remov          (0.417782)

guarante       (0.381420)

visit          (0.369886)

basenumb       (0.349298)

dollar         (0.330523)

will           (0.268946)

pleas          (0.267688)

price          (0.265088)

lo             (0.263212)

most           (0.258071)

nbsp           (0.253714)

ga             (0.247109)

al             (0.241299)




매거진의 이전글 머신러닝 옥타브 실습(6-4): 스팸 이메일 분류기
브런치는 최신 브라우저에 최적화 되어있습니다. IE chrome safari