Введение:

На создание этой статьи меня вдохновил курс Эндрю Нг на Coursera. Проходя курс, я иногда запутывался. Эндрю, без сомнения, невероятный учитель, но сама математика не очень интуитивна, и временами становится трудно запомнить все термины и то, как они связаны друг с другом, особенно когда у вас нет времени смотреть большие куски. видеолекций за один раз. К счастью, в конце 2-й недели курса было задано руководство. Это задание помогло мне прояснить многие сомнения. Я человек, который лучше учится, работая, и я пишу эту статью для таких людей, как я, которым нужно практическое применение теории, чтобы лучше понять концепции. Это также хороший проект для начинающих, чтобы получить хорошее представление о логистической регрессии. Я настоятельно рекомендую изучить какую-то теорию логистической регрессии, прежде чем переходить к этой статье.

В этой статье вы узнаете, как построить модель логистической регрессии для классификации изображений как «кошка» или «не кошка», используя только базовые библиотеки Python. Мы НЕ будем использовать scikit Learn. Я не буду объяснять, как установить и настроить блокнот Jupyter, который я буду использовать для запуска кода.

Вы можете найти набор данных на Kaggle в разделе Cat images Dataset | Kaggle, обязательно загрузите данные обучения и тестирования и сохраните их в той же папке, что и блокнот Jupyter.

Давайте начнем!

Сначала импортируйте библиотеки

import numpy as np
import matplotlib.pyplot as plt
import h5py

%matplotlib inline

Numpy позволяет быстро и эффективно выполнять сложные вычисления, такие как умножение векторов. Matplotlib давайте построим графики и посмотрим изображения. h5py требуется для загрузки данных изображения.

Загрузка данных

f = h5py.File('train_catvnoncat.h5', 'r')
print(f.keys())
//<KeysViewHDF5 ['list_classes', 'train_set_x', 'train_set_y']>
g = h5py.File('test_catvnoncat.h5','r')
print(g.keys())
//<KeysViewHDF5 ['list_classes', 'test_set_x', 'test_set_y']>
def load_dataset():
    f = h5py.File('train_catvnoncat.h5', 'r')
    train_set_x = np.array(f['train_set_x'])
    train_set_y = np.array(f['train_set_y']).reshape(1,f['train_set_y'].shape[0])
    list_classes = np.array([x.decode('UTF-8') for x in f['list_classes']])
    g = h5py.File('test_catvnoncat.h5','r')
    test_set_x = np.array(g['test_set_x'])
    test_set_y = np.array(g['test_set_y']).reshape(1,g['test_set_y'].shape[0])
    return list_classes, train_set_x, train_set_y, test_set_x, test_set_y
list_classes, train_set_x, train_set_y, test_set_x, test_set_y = load_dataset()

Обзор данных

Набор данных — это просто набор изображений. Давайте взглянем

print(list_classes)
['non-cat' 'cat']
plt.imshow(train_set_x[25])
print("the label for the image is "+list_classes[np.squeeze(train_set_y[:,25])])
//the label for the image is cat

Теперь давайте посмотрим на форму необработанных данных.

num_px = train_set_x.shape[1]
print("train:",train_set_x.shape)
print("test:",test_set_x.shape)
//train: (209, 64, 64, 3)
//test: (50, 64, 64, 3)

Как видите, тренировочные данные имеют размеры (209,64,64,3), а тестовые данные имеют размеры (50, 64, 64, 3).

В обучающих данных 209 — это количество изображений, 64 — это высота, а вторые 64 — ширина. Каждый из самых внутренних списков содержит значения RGB соответствующего пикселя.

print(train_set_x[0])
//array([[[17, 31, 56],
//        [22, 33, 59],
//        [25, 35, 62],
//        ...,
//        [ 1, 28, 57],
//        [ 1, 26, 56],
//     [ 1, 22, 51]],

//       [[25, 36, 62],
//        [28, 38, 64],
//        [30, 40, 67],
//        ...
//        ...
//        ...
//        [ 0,  0,  0],
//        [ 0,  0,  0],
//        [ 0,  0,  0]]], dtype=uint8)

Нам нужно преобразовать эти данные во что-то подходящее для логистической регрессии.

Разработка функций

Разработка функций — это процесс выбора, обработки и преобразования необработанных данных в функции, которые можно использовать в обучении с учителем.

В нашем примере для удобства мы теперь должны преобразовать изображения формы (num_px, num_px, 3) в массив numpy формы (num_px * num_px * 3, 1). После этого наш обучающий (и тестовый) набор данных представляет собой массив numpy, где каждый столбец представляет собой сглаженное изображение. Должны быть столбцы m_train (соответственно m_test).

Уловка, когда вы хотите сгладить матрицу X формы (a,b,c,d) до матрицы X_flatten формы (b*c*d, a), заключается в использовании:

X_flatten = X.reshape(X.shape[0], -1).T

X.T - транспонирование X

train_set_x_flatten = train_set_x.reshape(train_set_x.shape[0], -1).T
test_set_x_flatten = test_set_x.reshape(test_set_x.shape[0], -1).T
print("train_flatten:",train_set_x_flatten.shape)
print("test_flatten:",test_set_x_flatten.shape)
//train_flatten: (12288, 209)
//test_flatten: (12288, 50)

Теперь стандартизируем данные. Стандартизация данных — это важнейший процесс приведения данных к единому формату.

train_set_x=train_set_x_flatten/255 
test_set_x = test_set_x_flatten/255
print(train_set_x.shape,
test_set_x.shape)
//(12288, 209) (12288, 50)

Построение компонентов модели

Логистическая регрессия, по сути, представляет собой всего лишь однослойную нейронную сеть. Однако нейронные сети — это тема для другого дня.

На высоком уровне это основные шаги, которые нам нужно выполнить.

  • Инициализировать параметры модели
  • Узнать параметры модели, минимизировав стоимость
  • Используйте изученные параметры, чтобы делать прогнозы (на тестовом наборе)
  • Проанализируйте результаты и сделайте вывод

Математическая форма алгоритма, описывающего логистическую регрессию

И функция стоимости

Сначала мы определяем сигмовидную функцию в коде. С помощью функции активации Sigmoid мы можем уменьшить потери во время обучения, потому что это устраняет проблему градиента в модели машинного обучения во время обучения.

def sigmoid(z):
    s = 1/(1+np.exp(-z))
    return s

print ("sigmoid(0) = " + str(sigmoid(0)))
sigmoid(0) = 0.5
x = np.linspace(-7,7,100)
plt.plot(x,sigmoid(x))
plt.xlabel("x")
plt.title("Sigmoid function")
//Text(0.5, 1.0, 'Sigmoid function')

Теперь давайте поработаем над параметрами, т.е. w,b. Вы должны инициализировать w как вектор нулей. Для этого мы просто используем np.zeros()

def param_init(dim):
    w = np.zeros(shape=(dim,1))
    b = 0
    return w,b

Теперь нам нужно реализовать прямое и обратное распространение. Именно это позволяет модели «обучаться». По сути, он постоянно меняет веса для достижения необходимой точности/целей.

Сначала мы находим A, который является вектором активаций, то есть выходом слоя.

Тогда стоимость

def propagate(w,b,X,Y):
    m=X.shape[1]
    
    #Forward propogation
    A = sigmoid(np.dot(w.T,X)+b) # Here we compute the activations
    cost = (- 1 / m) * np.sum(Y * np.log(A) + (1 - Y) * (np.log(1 - A)))  # and here we compute cost             
    #Back propogation
    dw = (1 / m) * np.dot(X, (A - Y).T)
    db = (1 / m) * np.sum(A - Y)
    
    grads = {"dw":dw,"db":db}
    cost = np.squeeze(cost)
    
    return grads, cost

Теперь давайте создадим алгоритм градиентного спуска, это позволит нам обновить наши параметры.

Цель состоит в том, чтобы минимизировать функцию стоимости и найти соответствующие w и b. В общем случае для параметра 𝜃 правило обновления имеет вид 𝜃=𝜃−𝛼 𝑑𝜃, где 𝛼 — скорость обучения.

def optimize(w, b, X, Y, num_iterations, learning_rate, print_cost = False):
    costs = []
    
    for i in range(num_iterations):
        
        
        # Cost and gradient calculation 
        grads, cost = propagate(w, b, X, Y)   
        dw = grads["dw"]
        db = grads["db"]
        
        w = w - learning_rate * dw  
        b = b - learning_rate * db
        
        # Record the costs
        if i % 100 == 0:
            costs.append(cost)
        
        # Print the cost every 100 training examples
        if print_cost and i % 100 == 0:
            print ("Cost after iteration %i: %f" % (i, cost))
    
    params = {"w": w,
              "b": b}
    
    grads = {"dw": dw,
             "db": db}
    
    return params, grads, costs

Окончательный результат определяется

w и b — окончательные оптимизированные параметры.

Теперь давайте создадим последний компонент

def predict(w,b,X):
    m = X.shape[1]
    Y_prediction = np.zeros((1, m))
    w = w.reshape(X.shape[0], 1)
    
    
    #Computing the activtion vector
    A = sigmoid(np.dot(w.T, X) + b)
    
    #converting activations to predictions
    for i in range(A.shape[1]):
        Y_prediction[0, i] = 1 if A[0, i] > 0.5 else 0
    
    return Y_prediction

после этого мы можем объединить все и построить модель.

Построить модель

def logistic_regression(X_train, Y_train, X_test, Y_test, num_iterations=2000, learning_rate=0.5, print_cost=False):
    #1 initialize parameters
    w,b = param_init(X_train.shape[0])
    
    #2 Update parameters with gradient descent
    parameters, grads, costs = optimize(w, b, X_train, Y_train, num_iterations, learning_rate, print_cost)
    
    w = parameters["w"]
    b = parameters["b"]
    
    Y_prediction_test = predict(w, b, X_test)
    Y_prediction_train = predict(w, b, X_train)
    
    print("train accuracy: {} %".format(100 - np.mean(np.abs(Y_prediction_train - Y_train)) * 100))
    print("test accuracy: {} %".format(100 - np.mean(np.abs(Y_prediction_test - Y_test)) * 100))
    
    d = {"costs": costs,
         "Y_prediction_test": Y_prediction_test, 
         "Y_prediction_train" : Y_prediction_train, 
         "w" : w, 
         "b" : b,
         "learning_rate" : learning_rate,
         "num_iterations": num_iterations}
    
    return d
d = logistic_regression(train_set_x, train_set_y, test_set_x, test_set_y, num_iterations = 2000, learning_rate = 0.005, print_cost = True)
//Cost after iteration 0: 0.693147
//Cost after iteration 100: 0.584508
//Cost after iteration 200: 0.466949
//Cost after iteration 300: 0.376007
//Cost after iteration 400: 0.331463
//Cost after iteration 500: 0.303273
//Cost after iteration 600: 0.279880
//Cost after iteration 700: 0.260042
//Cost after iteration 800: 0.242941
//Cost after iteration 900: 0.228004
//Cost after iteration 1000: 0.214820
//Cost after iteration 1100: 0.203078
//Cost after iteration 1200: 0.192544
//Cost after iteration 1300: 0.183033
//Cost after iteration 1400: 0.174399
//Cost after iteration 1500: 0.166521
//Cost after iteration 1600: 0.159305
//Cost after iteration 1700: 0.152667
//Cost after iteration 1800: 0.146542
//Cost after iteration 1900: 0.140872
//train accuracy: 99.04306220095694 %
//test accuracy: 70.0 %

Большой! мы получили точность теста 70%, что довольно хорошо для простой модели логистической регрессии.

Теперь давайте сделаем так, чтобы вы могли использовать свои собственные изображения.

import scipy.misc
from PIL import Image
import imageio
from ipyfilechooser import FileChooser
fc = FileChooser()
display(fc)

Запустите следующую часть кода после выбора файла



my_image = fc.selected

fname = "images/" + my_image
image = np.array(imageio.imread(my_image))
my_image = np.array(Image.fromarray(image).resize((num_px,num_px))).reshape((1, num_px * num_px * 3)).T
my_predicted_image = predict(d["w"], d["b"], my_image)

plt.imshow(image)
print("y = " + str(np.squeeze(my_predicted_image)) + ", your algorithm predicts a "+ list_classes[int(np.squeeze(my_predicted_image)),])
//y = 0.0, your algorithm predicts a non-cat

Поздравляем! Вы успешно внедрили алгоритм логистической регрессии, надеюсь, это помогло укрепить ваше понимание алгоритма логистической регрессии, ура!