Продолжая серию, мы перейдем к машинам опорных векторов для задания программирования 6. Если вы заметили, у меня не было рецензии на задание 5, так как большинство задач просто требуют построения и интерпретации кривых обучения. Однако вы все еще можете найти код в моем GitHub по адресу https://github.com/Benlau93/Machine-Learning-by-Andrew-Ng-in-Python/tree/master/Bias_Vs_Variance.
Это задание состоит из двух частей. Во-первых, мы реализуем опорные векторные машины (SVM) для нескольких наборов 2D-данных, чтобы иметь представление об алгоритмах и принципах их работы. Затем мы будем использовать SVM в наборах данных электронных писем, чтобы попытаться классифицировать спам-письма.
Чтобы загрузить набор данных, loadmat из scipy.io используется для открытия файлов mat.
import numpy as np import pandas as pd import matplotlib.pyplot as plt from scipy.io import loadmat mat = loadmat("ex6data1.mat") X = mat["X"] y = mat["y"]
Построение набора данных
m,n = X.shape[0],X.shape[1] pos,neg= (y==1).reshape(m,1), (y==0).reshape(m,1) plt.scatter(X[pos[:,0],0],X[pos[:,0],1],c="r",marker="+",s=50) plt.scatter(X[neg[:,0],0],X[neg[:,0],1],c="y",marker="o",s=50)
Мы начинаем с простого набора данных, который имеет четкую линейную границу между обучающими примерами.
Как рекомендовано в лекции, мы стараемся не кодировать SVM с нуля, а вместо этого использовать для этого назначения высокооптимизированную библиотеку, такую как sklearn. Официальную документацию можно найти здесь.
from sklearn.svm import SVC classifier = SVC(kernel="linear") classifier.fit(X,np.ravel(y))
Поскольку это задача линейной классификации, мы не будем использовать какое-либо ядро для этой задачи. Это эквивалентно использованию линейного ядра в SVC (обратите внимание, что настройка ядра по умолчанию для SVC - «rbf», что означает радиальная базисная функция). Здесь функция ravel()
возвращает массив размером (m,), который требуется для SVC.
plt.figure(figsize=(8,6)) plt.scatter(X[pos[:,0],0],X[pos[:,0],1],c="r",marker="+",s=50) plt.scatter(X[neg[:,0],0],X[neg[:,0],1],c="y",marker="o",s=50) # plotting the decision boundary X_1,X_2 = np.meshgrid(np.linspace(X[:,0].min(),X[:,1].max(),num=100),np.linspace(X[:,1].min(),X[:,1].max(),num=100)) plt.contour(X_1,X_2,classifier.predict(np.array([X_1.ravel(),X_2.ravel()]).T).reshape(X_1.shape),1,colors="b") plt.xlim(0,4.5) plt.ylim(1.5,5)
При настройке по умолчанию C = 1.0 (помните, что C = 1 / λ) это граница решения, которую мы получили.
# Test C = 100 classifier2 = SVC(C=100,kernel="linear") classifier2.fit(X,np.ravel(y))
plt.figure(figsize=(8,6)) plt.scatter(X[pos[:,0],0],X[pos[:,0],1],c="r",marker="+",s=50) plt.scatter(X[neg[:,0],0],X[neg[:,0],1],c="y",marker="o",s=50) # plotting the decision boundary X_3,X_4 = np.meshgrid(np.linspace(X[:,0].min(),X[:,1].max(),num=100),np.linspace(X[:,1].min(),X[:,1].max(),num=100)) plt.contour(X_3,X_4,classifier2.predict(np.array([X_3.ravel(),X_4.ravel()]).T).reshape(X_3.shape),1,colors="b") plt.xlim(0,4.5) plt.ylim(1.5,5)
Изменение C = 100 дало границу решения, которая выходит за рамки обучающих примеров.
Далее мы рассмотрим набор данных, который нельзя разделить линейно. Здесь в игру вступают ядра, которые предоставляют нам функции нелинейного классификатора. Для тех, кто испытывает трудности с пониманием концепции ядер, эта статья, которую я нашел, дала довольно хорошую интуицию и некоторые математические объяснения о ядрах. Для этой части задания от нас требовалось завершить функцию gaussianKernel
, чтобы помочь в реализации SVM с гауссовскими ядрами. Я пропущу этот шаг, поскольку SVC содержит собственную реализацию гауссовского ядра в виде радиальной базисной функции (rbf). Вот страница Википедии с уравнением для rbf, как видите, оно идентично функции ядра Гаусса из курса.
Загрузка и построение примера набора данных 2
mat2 = loadmat("ex6data2.mat") X2 = mat2["X"] y2 = mat2["y"] m2,n2 = X2.shape[0],X2.shape[1] pos2,neg2= (y2==1).reshape(m2,1), (y2==0).reshape(m2,1) plt.figure(figsize=(8,6)) plt.scatter(X2[pos2[:,0],0],X2[pos2[:,0],1],c="r",marker="+") plt.scatter(X2[neg2[:,0],0],X2[neg2[:,0],1],c="y",marker="o") plt.xlim(0,1) plt.ylim(0.4,1)
Для реализации SVM с гауссовскими ядрами
classifier3 = SVC(kernel="rbf",gamma=30) classifier3.fit(X2,y2.ravel())
Что касается параметров SVM с ядром rbf, он использует гамму вместо сигмы. Документацию по параметрам можно найти здесь. Я обнаружил, что гамма похожа на 1 / σ, но не совсем. Я надеюсь, что какой-нибудь эксперт в предметной области сможет дать мне представление об интерпретации этого гамма-члена. Что касается этого набора данных, я обнаружил, что значение гаммы 30 наиболее похоже на оптимизированные параметры в задании (в курсе сигма была 0,1).
plt.figure(figsize=(8,6)) plt.scatter(X2[pos2[:,0],0],X2[pos2[:,0],1],c="r",marker="+") plt.scatter(X2[neg2[:,0],0],X2[neg2[:,0],1],c="y",marker="o") # plotting the decision boundary X_5,X_6 = np.meshgrid(np.linspace(X2[:,0].min(),X2[:,1].max(),num=100),np.linspace(X2[:,1].min(),X2[:,1].max(),num=100)) plt.contour(X_5,X_6,classifier3.predict(np.array([X_5.ravel(),X_6.ravel()]).T).reshape(X_5.shape),1,colors="b") plt.xlim(0,1) plt.ylim(0.4,1)
Что касается последнего набора данных в этой части, мы выполняем простую настройку гиперпараметров, чтобы определить лучшие значения C и гамма для использования.
Загрузка и построение набора данных примеров 3
mat3 = loadmat("ex6data3.mat") X3 = mat3["X"] y3 = mat3["y"] Xval = mat3["Xval"] yval = mat3["yval"] m3,n3 = X3.shape[0],X3.shape[1] pos3,neg3= (y3==1).reshape(m3,1), (y3==0).reshape(m3,1) plt.figure(figsize=(8,6)) plt.scatter(X3[pos3[:,0],0],X3[pos3[:,0],1],c="r",marker="+",s=50) plt.scatter(X3[neg3[:,0],0],X3[neg3[:,0],1],c="y",marker="o",s=50)
def dataset3Params(X, y, Xval, yval,vals): """ Returns your choice of C and sigma. You should complete this function to return the optimal C and sigma based on a cross-validation set. """ acc = 0 best_c=0 best_gamma=0 for i in vals: C= i for j in vals: gamma = 1/j classifier = SVC(C=C,gamma=gamma) classifier.fit(X,y) prediction = classifier.predict(Xval) score = classifier.score(Xval,yval) if score>acc: acc =score best_c =C best_gamma=gamma return best_c, best_gamma
dataset3Params
выполняет итерацию по списку vals
, заданному в функции, и устанавливает C как vals, а гамму как 1 / vals. Модель SVC строится с использованием каждой комбинации параметров, и вычисляется точность набора для проверки. На основе точности выбирается лучшая модель и возвращаются значения для соответствующих C и гаммы.
vals = [0.01, 0.03, 0.1, 0.3, 1, 3, 10, 30] C, gamma = dataset3Params(X3, y3.ravel(), Xval, yval.ravel(),vals) classifier4 = SVC(C=C,gamma=gamma) classifier4.fit(X3,y3.ravel())
plt.figure(figsize=(8,6)) plt.scatter(X3[pos3[:,0],0],X3[pos3[:,0],1],c="r",marker="+",s=50) plt.scatter(X3[neg3[:,0],0],X3[neg3[:,0],1],c="y",marker="o",s=50) # plotting the decision boundary X_7,X_8 = np.meshgrid(np.linspace(X3[:,0].min(),X3[:,1].max(),num=100),np.linspace(X3[:,1].min(),X3[:,1].max(),num=100)) plt.contour(X_7,X_8,classifier4.predict(np.array([X_7.ravel(),X_8.ravel()]).T).reshape(X_7.shape),1,colors="b") plt.xlim(-0.6,0.3) plt.ylim(-0.7,0.5)
Оптимальные значения - 0,3 для C и 100 для гаммы, это приводит к такой же границе решения, как и при задании.
Переходим к классификации спама. Эта проблема уникальна, поскольку она больше ориентирована на предварительную обработку данных, чем на фактический процесс моделирования. Электронные письма должны обрабатываться таким образом, чтобы их можно было использовать в качестве входных данных для модели. Один из способов сделать это - получить индексы всех слов в электронном письме на основе списка обычно используемой лексики.
Загрузка данных
import re from nltk.stem import PorterStemmer file_contents = open("emailSample1.txt","r").read() vocabList = open("vocab.txt","r").read()
Был дан список словаря и его соответствующие индексы, я сохранил список как словарь с словарями в качестве ключей и индексами в качестве значений. Возможно, вы могли бы сделать это по-другому, но я хочу упростить доступ к словарям (например, with if keys in dict
)
vocabList=vocabList.split("\n")[:-1] vocabList_d={} for ea in vocabList: value,key = ea.split("\t")[:] vocabList_d[key] = value
Что касается предварительной обработки писем, в задании для нас было намечено несколько шагов.
def processEmail(email_contents,vocabList_d): """ Preprocesses the body of an email and returns a list of indices of the words contained in the email. """ # Lower case email_contents = email_contents.lower() # Handle numbers email_contents = re.sub("[0-9]+","number",email_contents) # Handle URLS email_contents = re.sub("[http|https]://[^\s]*","httpaddr",email_contents) # Handle Email Addresses email_contents = re.sub("[^\s]+@[^\s]+","emailaddr",email_contents) # Handle $ sign email_contents = re.sub("[$]+","dollar",email_contents) # Strip all special characters specialChar = ["<","[","^",">","+","?","!","'",".",",",":"] for char in specialChar: email_contents = email_contents.replace(str(char),"") email_contents = email_contents.replace("\n"," ") # Stem the word ps = PorterStemmer() email_contents = [ps.stem(token) for token in email_contents.split(" ")] email_contents= " ".join(email_contents) # Process the email and return word_indices word_indices=[] for char in email_contents.split(): if len(char) >1 and char in vocabList_d: word_indices.append(int(vocabList_d[char])) return word_indices word_indices= processEmail(file_contents,vocabList_d)
Использование регулярных выражений здесь очень удобно, этот учебник от гуру Python может помочь вам начать работу с re. Другая полезная библиотека - nlkt, где функция PorterStemmer()
помогает с выделением слов. Еще один хороший учебник, на этот раз от pythonprogramming.net.
После получения индексов слов нам нужно преобразовать индексы в вектор признаков.
def emailFeatures(word_indices, vocabList_d): """ Takes in a word_indices vector and produces a feature vector from the word indices. """ n = len(vocabList_d) features = np.zeros((n,1)) for i in word_indices: features[i] =1 return features features = emailFeatures(word_indices,vocabList_d) print("Length of feature vector: ",len(features)) print("Number of non-zero entries: ",np.sum(features))
Оператор печати напечатает: Length of feature vector: 1899
и Number of non-zero entries: 43.0
. Это немного отличается от присваивания, поскольку you’re
был записан как «вы» и «re» в присвоении, в то время как мой код идентифицировал его как «ваш», что привело к меньшему количеству ненулевых записей.
Обучить SVM будет так же просто, как передать функции в качестве входных данных. Однако это только один обучающий пример, и нам потребуется больше обучающих данных для обучения классификатора.
spam_mat = loadmat("spamTrain.mat") X_train =spam_mat["X"] y_train = spam_mat["y"]
В spamTrain.mat
приведены обучающие примеры для обучения нашего классификатора, а в spamTest.mat
- тестовые примеры для определения обобщаемости нашей модели.
C =0.1 spam_svc = SVC(C=0.1,kernel ="linear") spam_svc.fit(X_train,y_train.ravel()) print("Training Accuracy:",(spam_svc.score(X_train,y_train.ravel()))*100,"%")
Оператор печати напечатает: Training Accuracy: 99.825 %
spam_mat_test = loadmat("spamTest.mat") X_test = spam_mat_test["Xtest"] y_test =spam_mat_test["ytest"] spam_svc.predict(X_test) print("Test Accuracy:",(spam_svc.score(X_test,y_test.ravel()))*100,"%")
Оператор печати напечатает: Test Accuracy: 98.9 %
Чтобы лучше понять нашу модель, мы могли бы посмотреть на вес каждого слова и выяснить, какие слова наиболее предсказуемы для спама.
weights = spam_svc.coef_[0] weights_col = np.hstack((np.arange(1,1900).reshape(1899,1),weights.reshape(1899,1))) df = pd.DataFrame(weights_col) df.sort_values(by=[1],ascending = False,inplace=True) predictors = [] idx=[] for i in df[0][:15]: for keys, values in vocabList_d.items(): if str(int(i)) == values: predictors.append(keys) idx.append(int(values)) print("Top predictors of spam:") for _ in range(15): print(predictors[_],"\t\t",round(df[1][idx[_]-1],6))
Вот и все, что касается опорных векторных машин! Записная книжка jupyter будет загружена на мой GitHub по адресу (https://github.com/Benlau93/Machine-Learning-by-Andrew-Ng-in-Python).
Для другой реализации Python в этой серии:
- "Линейная регрессия"
- "Логистическая регрессия"
- Регулярная логистическая регрессия
- "Нейронные сети"
- Обучение без учителя
- Обнаружение аномалий
Спасибо за чтение.