이 과제 Multiclass SVM을 직접 구현하시는 것입니다. 기본적으로 사이킷 런에 있는 SVM은 멀티클래스 SVM을 지원합니다. 그러나 과제에서는 그것을 쓰면 안됩니다! 아이리스 데이터는 총 세 개의 클래스가 있으므로 이 클래스를 one-hot 인코딩 한 뒤, 각각 binary SVM을 트레이닝하고 이 결과를 조합하여 multiclass SVM을 구현하는 것입니다.
위에서 말했듯 기본적으로 one vs one, one vs rest 방법이 있으며 어떤 것을 구현하든 자유입니다. 만약 투표결과 동점이 나온경우(예를 들어 각각의 SVM의 결과가 A vs B C의 경우 A로 판별 , B vs A C의 결과 B로 판별, C vs A B의 경우 C로 판별한 경우 투표를 통해 class를 결정할 수 없음) decision_function을 활용하시거나, 가장 개수가 많은 클래스를 사용하시거나 랜덤으로 하나를 뽑거나 하는 방법 등을 이용해 동점자인 경우를 판별해주시면 됩니다. 공식 문서를 보면 사이킷런이 어떤 방법으로 구현했는지가 글로 나와 있으므로 참조하셔도 무관합니다.
과제코드에는 제가 iris 데이터를 로드하고 iris 데이터를 one hot hot인코딩 한 부분까지 구현해 놓았습니다. 또한 decision function을 호출해서 사용하는 예시도 하나 넣어 놓았으니 참고하시면 됩니다. 개인적으로 one vs rest가 더 구현하기 쉬울것으로 생각되며, 생각보다 코드가 길지 않고 어렵지 않습니다.
우수과제 선정이유
클래스로 구현해실제 함수로도 쓸 수 있게끔 깔끔하게 구현하고, one vs one 알고리즘까지 구현해 우수과제로 선정되었습니다.
Import packages
In [1]:
# dataimport numpy as npimport pandas as pd# visualizationimport matplotlib.pyplot as pltimport seaborn as sns# modelfrom sklearn.svm import SVCfrom sklearn.model_selection import train_test_splitfrom sklearn.pipeline import Pipelinefrom sklearn.preprocessing import StandardScalerfrom sklearn.metrics import*import warningswarnings.filterwarnings(action='ignore')
classOneVsRestSVM:def__init__(self,n_classes=3): self.n_classes = n_classes self.clfs = [] self.y_pred = []# y를 onehot encoding하는 과정defone_vs_rest_labels(self,y_train): y_train = pd.get_dummies(y_train)return y_train# encoding된 y를 가져와서 class 개수 만큼의 classifier에 각각 돌리는 과정deffit(self,X_train,y_train,C=5,gamma=5):# y encoding y_encoded = self.one_vs_rest_labels(y_train)for i inrange(self.n_classes): clf =SVC(kernel='rbf', C=C, gamma=gamma) clf.fit(X_train, y_encoded.iloc[:,i]) self.clfs.append(clf)# 각각의 classifier에서 나온 결과를 바탕으로 투표를 진행하는 과정defpredict(self,X_test): vote = np.zeros((len(X_test), 3), dtype=int) size = X_test.shape[0]for i inrange(size):# 해당 class에 속하는 샘플을 +1 만큼 투표를, 나머지 샘플에 -1 만큼 투표를 진행한다.if self.clfs[0].predict(X_test)[i] ==1: vote[i][0] +=1 vote[i][1] -=1 vote[i][2] -=1elif self.clfs[1].predict(X_test)[i] ==1: vote[i][0] -=1 vote[i][1] +=1 vote[i][2] -=1elif self.clfs[2].predict(X_test)[i] ==1: vote[i][0] -=1 vote[i][1] -=1 vote[i][2] +=1# 투표한 값 중 가장 큰 값의 인덱스를 test label에 넣는다 self.y_pred.append(np.argmax(vote[i]))# 경우의 수# 1. 한 분류기의 투표 결과가 양수이고 나머지는 음수인 경우# 2. 세 분류기의 투표 결과가 모두 0으로 같은 경우# 3. 두 분류기의 투표 결과가 양수로 같은 경우# 2번째, 동점일 경우 decision_function의 값이 가장 큰 경우를 test label에 넣는다if (np.sign(self.clfs[0].decision_function(X_test)[i])== np.sign(self.clfs[1].decision_function(X_test)[i])) \and (np.sign(self.clfs[1].decision_function(X_test)[i])== np.sign(self.clfs[2].decision_function(X_test)[i])): self.y_pred[i]= np.argmax([self.clfs[0].decision_function(X_test)[i], self.clfs[1].decision_function(X_test)[i], self.clfs[2].decision_function(X_test)[i]])# 3번째, 두 분류기의 투표 결과가 양수로 같을 경우 decision_function의 값이 가장 큰 경우를 test label에 넣는다elif (vote[i][0] == vote[i][1]) and vote[i][0] >0and vote[i][1] >0: self.y_pred[i]= np.argmax([self.clfs[0].decision_function(X_test)[i], self.clfs[1].decision_function(X_test)[i]])elif (vote[i][0] == vote[i][2]) and vote[i][0] >0and vote[i][2] >0: self.y_pred[i]= np.argmax([self.clfs[0].decision_function(X_test)[i], self.clfs[2].decision_function(X_test)[i]])elif (vote[i][1] == vote[i][2]) and vote[i][1] >0and vote[i][2] >0: self.y_pred[i]= np.argmax([self.clfs[1].decision_function(X_test)[i], self.clfs[2].decision_function(X_test)[i]])# test를 진행하기 위해 0,1,2로 되어있던 데이터를 다시 문자 label로 변환 self.y_pred = pd.DataFrame(self.y_pred).replace({0:'setosa', 1:'versicolor', 2:'virginica'})return self.y_pred# accuracy 확인defevaluate(self,y_test):print('Accuacy : {: .5f}'.format(accuracy_score(y_test, self.y_pred)))
classOneVsOneSVM:def__init__(self,n_classes=3): self.n_classes = n_classes self.clfs = [] self.y_pred = []# c1 class와 c2 class의 label 만드는 과정defone_vs_one_labels(self,c1,c2,y_train): size = y_train.shape[0] y = np.zeros(size)# one vs one을 학습시키기 위해 c1 class인지 c2 class인지를 구분해야 하므로# class가 c1인 경우 1, c2인 경우 -1을 넣은 새로운 label을 생성한다.for i inrange(size):if y_train[i]== c1: y[i]=1else: y[i]=-1return y# one vs one label을 적용해 두 class의 데이터만 가져오는 과정defone_vs_one_data(self,c1,c2,X_train,y_train): y_train = pd.DataFrame(y_train).replace({'setosa':0, 'versicolor':1, 'virginica':2}).values.flatten()# 해당 class의 index를 가져온다. index_c1 = (y_train == c1) index_c2 = (y_train == c2)# c1 class인지 c2 class인지를 비교해야 하므로# 해당 두 class에 속하는 데이터만 가져온다. y_train_c = np.concatenate((y_train[index_c1], y_train[index_c2])) y_train_c = self.one_vs_one_labels(c1, c2, y_train_c) X_train_c = np.vstack((X_train[index_c1], X_train[index_c2]))return y_train_c, X_train_c# class들의 조합 개수만큼의 classifier를 만들고 fitting 시키는 과정deffit(self,X_train,y_train,C=5,gamma=5):# class가 m개 라면 m * (m-1) / 2 개의 classifer가 필요하다.for c1 inrange(self.n_classes):for c2 inrange(c1+1, self.n_classes): data_c = self.one_vs_one_data(c1, c2, X_train, y_train) y_c = data_c[0].reshape(-1,1) X_c = data_c[1] clf =SVC(kernel='rbf', C=C, gamma=gamma) clf.fit(X_c, y_c) self.clfs.append([clf, c1, c2])# 각각의 classifier에서 나온 결과를 바탕으로 투표를 진행하는 과정defpredict(self,X_test): vote = np.zeros((len(X_test), 3), dtype=int) size = X_test.shape[0]for i inrange(size): x = X_test[i,:].reshape(-1, 4)for j inrange(len(self.clfs)): clf, c1, c2 = self.clfs[j] pred = clf.predict(x)# x를 class c1으로 분류하면 class c1에 +1점# c2로 분류하면 class c2에 +1점을 준다.if pred ==1: vote[i][c1] +=1else: vote[i][c2] +=1# 투표한 값 중 가장 큰 값의 인덱스를 test label에 넣는다 self.y_pred.append(np.argmax(vote[i]))# 경우의 수# 1. 한 분류기의 투표 결과가 제일 높은 경우# 2. 세 분류기의 투표 결과가 모두 같은 경우# 3. 두 분류기의 투표 결과가 같고 나머지 한 분류기는 다른 경우# 2번째, 모두 동점일 경우 decision_function의 값이 가장 큰 경우를 test label에 넣는다if (vote[i][0] == vote[i][1]) and (vote[i][1] == vote[i][2]): self.y_pred[i]= np.argmax([self.clfs[0].decision_function(X_test)[i], self.clfs[1].decision_function(X_test)[i], self.clfs[2].decision_function(X_test)[i]])# 3번째, 두 분류기의 투표 결과가 양수로 같은 경우 decision_function이 값이 큰 경우를 test label에 넣는다elif (vote[i][0] == vote[i][1]) and vote[i][0] >0and vote[i][1] >0: self.y_pred[i]= np.argmax([self.clfs[0].decision_function(X_test)[i], self.clfs[1].decision_function(X_test)[i]])elif (vote[i][0] == vote[i][2]) and vote[i][0] >0and vote[i][2] >0: self.y_pred[i]= np.argmax([self.clfs[0].decision_function(X_test)[i], self.clfs[2].decision_function(X_test)[i]])elif (vote[i][1] == vote[i][2]) and vote[i][1] >0and vote[i][2] >0: self.y_pred[i]= np.argmax([self.clfs[1].decision_function(X_test)[i], self.clfs[2].decision_function(X_test)[i]])# test를 진행하기 위해 0,1,2로 되어있던 데이터를 다시 문자 label로 변환 self.y_pred = pd.DataFrame(self.y_pred).replace({0:'setosa', 1:'versicolor', 2:'virginica'})return self.y_pred# accuracy 확인defevaluate(self,y_test):print('Accuacy : {: .5f}'.format(accuracy_score(y_test, self.y_pred)))
# 그냥 원래 라이브러리가 제공하는 멀티클래스 SVM과 여러분이 구현한 multiclass SVM결과를 한 번 비교해주세요X_train_2, X_test_2, y_train_2, y_test_2 =train_test_split(X, y, test_size=0.2, random_state=48)scaler =StandardScaler()#scalingX_train_2 = scaler.fit_transform(X_train_2)X_test_2 = scaler.transform(X_test_2)svm_4 =SVC(kernel ='rbf', C =5, gamma =5)svm_4.fit(X_train_2, y_train_2)y_pred = svm_4.predict(X_test_2)accuracy_score(y_test_2,y_pred)