Това е петата статия от тази поредица, в която се опитвам да прекодирам упражненията в (стария) курс за машинно обучение от Андрю Нг (където упражненията по програмиране се правят с помощта на Octave). Намерението ми да пиша тези статии е да помогна на обучаемите в този курс да използват Python като алтернатива, докато изпълняват упражненията. Моля, не се колебайте да разгледате и предишните части от тази поредица:
Част1: Модел на линейна регресия с една характеристика
Част2: Линейна регресия с множество функции
Част3: (Нерегулирана ) Модел на логистична регресия
Част 4: Регулярна логистична регресия

За това упражнение ни е даден набор от данни, съдържащ 5000 примера за обучение на ръкописни цифри, като всеки пример за обучение е изображение на цифрата с размери 20 на 20 пиксела в сива скала. Всеки пиксел е представен от число с плаваща запетая, което показва интензитета на сивата скала на това място. И така, нашият набор от данни за обучение е матрица 5000 на 400. Втората част от набора за обучение е 5000-измерен вектор y, който съдържа етикети за набора за обучение. Има общо 10 класа („1“, „2“, „3“, …., „10“). Моля, обърнете внимание, че цифрата „0“ е означена като „10“, докато цифрите от „1“ до „9“ са означени като „1“ до „9“ в техния естествен ред.

1. Запознаване с данните

Нека първо да разгледаме дадения набор от данни.

# load the data
import scipy.io
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
data = scipy.io.loadmat('ex3data1.mat')
data

За да визуализирам данните, ще създам функция, наречена displayData(), която ще приеме два параметъра: набора от данни за обучение и броя на подграфите (на една ос). P.S. Засега можете да пренебрегнете първата част от оператора „if“, която трябва да покаже прогнозирания резултат в последната част на статията.

def displayData(X, subplot):
    
    width = int(round(math.sqrt(X.shape[1])))
    m, n = X.shape
    height = int(n/width)
    
    # create subplots
    fig, axarr = plt.subplots (subplot,subplot, figsize=(6,6),
                              gridspec_kw = {'wspace':0, 'hspace':0})
    
    # this part is for visualizing the training and prediciton in the last part.
    if subplot == 1:
        pixels = X
        pixels = pixels.reshape(width, height)
        axarr.imshow(pixels.T, cmap = 'gray_r')
        axarr.set_xticks([]) # remove the ticks
        axarr.set_yticks([])
    
    # this part is for showing random digits from X
    else:
        for i in range(subplot):
            for j in range(subplot):
                random_index = np.random.choice(len(X))
                pixels = X[random_index]   
                pixels = pixels.reshape(width, height)
                axarr[i,j].imshow(pixels.T, cmap ='gray_r')
                axarr[i,j].set_xticks([]) # remove the ticks
                axarr[i,j].set_yticks([])
    plt.show()
X = data['X']
y = data['y']
displayData(X, 10)

Това ще избере произволно 100 реда от X и ще ги покаже във фигура със 100 подграфика, както е показано по-долу:

2. Функция на разходите и градиент (с регулиране)

Наборът от данни съдържа повече от два етикета и следователно ще използваме множество логистични регресионни модели едно срещу всички, за да изградим многокласов класификатор. Тъй като има 10 класа (етикети от „1“ до „10“), ние ще обучим 10 отделни класификатора за логистична регресия. Преди да внедрим многокласовия класификатор, нека първо дефинираме необходимите функции: сигмоидната функция, функцията на разходите и градиентната функция.

Функция на регуляризираните разходи

Тук, както за функцията на разходите, така и за функцията на градиента, не забравяйте да добавите колона от 1s към X за члена на отклонението. Докато намирате градиента на разходите, моля, обърнете внимание, че ние не трябва да регулираме стойността тета за члена на отклонението. Моля, вижте Част 4 за математическите уравнения за двете функции.

# this function will compute cost of using theta as the parameter for regularized logistic regression
def sigmoid(z):
    g = 1/(1 + np.exp(-z))
    return g
def J(theta, x, y, lambdaa):
    J = 0
    m = len(y)
    theta = theta.reshape((theta.shape[0],1))
    
    x = np.concatenate((np.ones((len(x),1)), x), axis = 1)  # add ones to the X: (5000x401) matrix
    
    y_prime = np.transpose(y)
    x_prime = np.transpose(x)
    h_theta = sigmoid(np.dot(x,theta))
    
    term_1 = (np.dot(y_prime, np.log(h_theta)))
    term_2 = (np.dot(np.transpose(1-y), np.log(1-h_theta)))
    term_3 = np.array(0.5*lambdaa*np.sum(np.power(theta[1::],2)))
    J     = (-term_1 -term_2 + term_3)/m
    J = np.sum(J)
    return J

Градиент на разходите (регуляризирани)

def Gradient(theta, x, y, lambdaa):
    m = len(y)
    theta = theta.reshape((theta.shape[0],1))
    
    x = np.concatenate((np.ones((len(x),1)), x), axis = 1)  # add ones to the X: (5000x401) matrix
    
    j = np.ones((x.shape[1],1))
    j[0] = 0 #we will not regularize for j=0
    grad = (np.dot(np.transpose(x), sigmoid(np.dot(x,theta))-y ) +  lambdaa* np.multiply(j,theta))/m
    return grad

3. Класификация „един срещу всички“.

Ще приложим класификация „един срещу всички“ чрез обучение на 10 класификатора на регуляризирана логистична регресия, по един за всеки от K класовете (в нашия набор от данни K = 10).
Всички параметри на класификатора ще бъдат върнати в матрица, както е показано по-долу където всеки ред съответства на научените параметри на логистична регресия за един клас.

За нашия случай функцията по-долу ще върне (10 401) матрица, където всеки ред представлява тета стойности за всеки етикет (напр. ред с индекс0 представлява тета стойности за етикет „1“, индекс1 за етикет „2“,…, индекс9 за етикет „10“).

import scipy.optimize as opt
from scipy.optimize import minimize
def oneVsall(X,y,labels,lambdaa):
    m = X.shape[0]  # no. of training examples: 5000
    n = X.shape[1]  # no. of features: 400
    all_theta = np.zeros((labels,n+1)) 
    
    for i in range(1, labels+1):
        y_ = (y==i).astype(int)
        theta_start = np.zeros((n+1,1)) 
        
        theta_ = opt.fmin_tnc(func=J, x0=theta_start, fprime = Gradient, args = (X,y_,lambdaa)) #-> result
        
        all_theta[i-1,:] = theta_[0]
    return all_theta
labels = 10 
lambdaa = 0.1
result = oneVsall(X,y,labels,lambdaa)

4. Прогноза

Вече можем да използваме нашия класификатор едно срещу всички, за да предвидим цифрата, съдържаща се в дадено изображение. За всеки вход ще изчислим „вероятността“ той да принадлежи към всеки клас, като използваме обучените класификатори. Функцията за прогнозиране едно срещу всички ще избере класа, за който съответният класификатор на логистична регресия извежда най-високата вероятност и ще върне етикета на класа (1,2,…,10) като прогноза.

# the labels are in the range (1 to K) where K = 10.
def predict(all_theta, x):
    labels = all_theta.shape[0] 
    
    h_theta = sigmoid(np.dot(x,np.transpose(all_theta)))  
    hmax    = np.amax(h_theta, axis=1)   
    prediction = np.argmax(h_theta, axis =1 )+1  
    prediction = prediction.reshape((prediction.shape[0],1)) # just reshaping into similar shape as y
    
    return prediction
X_ = np.concatenate((np.ones((len(X),1)), X), axis = 1)  # add ones to the X: (5000x401) matrix
pred = predict(result, X_)
print("Training set accuracy : {}%".format(np.mean((pred == y).astype(float))*100))

Полученият „pred“ е (5000,1) матрица, съдържаща всички прогнозирани резултати за 5000 примера за обучение. Получената точност на обучителния набор е 96,46%.

5. Многокласова логистична регресия срещу невронна мрежа

Внедрихме многокласова логистична регресия за разпознаване на ръкописни цифри. Въпреки това, логистичната регресия не може да формира по-сложни хипотези, тъй като е само линеен класификатор (можем да добавим повече характеристики като полиномни характеристики към логистичната регресия, но това може да бъде много скъпо за обучение). И така, сега ще внедрим невронна мрежа за разпознаване на ръкописни цифри, използвайки същия тренировъчен набор преди. Невронната мрежа ще може да представя сложни модели, които формират нелинейни хипотези.

За тази част от упражнението ни се дават теглата (вече обучени) и просто трябва да внедрим алгоритъма за предаване напред за прогнозиране.

Нашата невронна мрежа има 3 слоя - входящ слой, скрит слой и изходен слой. Имаме 400 единици в нашия входен слой (с изключение на допълнителната единица за отклонение, която винаги извежда +1). Имаме набор от обучени параметри за ϴ(1) и ϴ(2) (които се съхраняват в ex3weights.mat). Параметрите имат размери, които са оразмерени за невронна мрежа с 25 единици във втория слой и 10 единици в изходния слой (съответстващи на 10 класа).

weights = scipy.io.loadmat('ex3weights.mat')
Theta1 = weights['Theta1']
Theta2 = weights['Theta2']

Тук Theta1 е (25,401) матрица, а Theta2 е (10,26) матрица.

5.1 Предаващо разпространение и прогнозиране

# This function will predict the label of an input given a trained neural network
def predict2(theta1, theta2, x):
    x = np.concatenate((np.ones((len(x),1)), x), axis = 1)  # add ones to the X: (5000x401) matrix
    
    m = x.shape[0]
    labels = theta2.shape[0] 
    
    a1 = x  # we have already added bias colunm to x 
    
    z2 = np.dot(a1, np.transpose(theta1))
    a2 = sigmoid(z2)
    a2 = np.concatenate((np.ones((m,1)), a2), axis=1)    
    z3 = np.dot(a2, np.transpose(theta2)) 
    a3 = sigmoid(z3)
    
    hmax    = np.amax(a3, axis=1)   
    prediction = np.argmax(a3, axis =1 ) + 1 
    prediction = prediction.reshape((prediction.shape[0],1)) # just reshaping into similar shape as y
    
    return prediction
pred = predict2(Theta1, Theta2, X)
print("Training set accuracy : {}%".format(np.mean((pred == y).astype(float))*100))

Точността на набора за обучение, получена от тази невронна мрежа, е 97,52% (което е малко по-високо от предишните ни класификатори едно срещу всички).

Можем да използваме кода по-долу, за да покажем изображението на набора за обучение (един по един) заедно с неговия прогнозиран етикет.

# to display images from the training set one at a time
i = np.random.randint(5000)
train_eg = X[i,:].reshape((1,X[i,:].shape[0]))
pred = predict2(Theta1, Theta2, train_eg)
print("Neural Network Prediction : {} (digit {})\n".format(pred, pred%10))
displayData(train_eg,1)

Това е всичко за тази част. В предстоящата част 6 ще обучим нашите собствени параметри, използвайки функцията на разходите и градиентите, получени от обратното разпространение на невронната мрежа (използвайки същия набор от данни).

Продължавай да учиш. Насладете се на пътуването!