Прелюдия
Утро туманное, ты забыл свои очки внутри, но нет времени вернуться за ними. Вы спускаетесь туда, где у вас заблокирован байк, не замечая, что какой-то хулиган тайком заменил его тигром. После быстрой поездки в больницу вы решаете никогда больше не путать байк с тигром. К счастью для вас, с небольшим тензорным потоком и небольшим PIL вы можете научить свой компьютер отличать байки от тигров (или львов, акул, действительно всего, что эти хулиганы могут попытаться ускользнуть).
Для этого мы воспользуемся нейронными сетями. Мы будем извлекать данные из изображений Google, в частности изображений велосипедов и тигров, обрабатывать их с помощью PIL и использовать их для обучения нейронной сети с тензорным потоком.
Фон
Нейронная сеть, как можно понять из названия, - это метод, позволяющий заставить компьютеры учиться на данных, созданный на основе того, как, по нашему мнению, мозг может учиться на данных. Классический вариант использования нейронной сети - научить компьютер распознавать нарисованные от руки цифры. Хотя это может показаться нам ослепляюще очевидным, с самого начала совсем не ясно, как мы можем научить компьютер распознавать какой-то паттерн как 3, а какой-то другой как 4. Для правильного объяснения математической интуиции я бы порекомендовал 3Blue1Browns отличный цикл из 4 частей по этой теме (https://www.youtube.com/playlist?list=PLZHQObOWTQDNU6R1_67000Dx_ZCJB-3p). Как и большинство методов машинного обучения, нейронные сети используют множество обучающих данных, чтобы попытаться научиться. В классическом примере мы можем скормить компьютеру много-много нарисованных от руки цифр. В нашем случае нам нужно найти много фотографий мотоциклов и тигров и дать нашей модели, чтобы она творила чудеса.
Парсинг веб-страниц
Перво-наперво нам нужны некоторые данные. Мы будем использовать программу для автоматического извлечения и загрузки около 170 изображений велосипедов и тигров из изображений Google. Код для этого извлечения любезно предоставлен
from selenium import webdriver import requests import os import io import hashlib from bs4 import BeautifulSoup from PIL import Image import time ###Image scraping with python code goes here#### search_and_download(search_term="tiger",driver_path=DRIVER_PATH,number_images=170) #gets 170 images of tigers from google images, saves to folder called tiger, in folder called image, in our working directory search_and_download(search_term="bike",driver_path=DRIVER_PATH,number_images=170) #Same for bikes
Чтобы получить наши данные, мы вводим поисковый запрос и количество изображений в функцию загрузки. После его запуска у нас должна быть папка с именем tiger, содержащая 170 файлов tiger jpg, и папка с именем bike, содержащая 170 файлов bike jpgs, со случайным именем и форматированием. Обратите внимание: сколько значений мы можем очистить с помощью этого алгоритма, прежде чем он застрянет, зависит от поискового запроса. Для этих терминов мы смогли извлечь около 170. Игра с аргументом «сон между взаимодействиями» в коде ссылки может помочь увеличить это число. Google не любит, когда мы извлекаем jpeg-файлы слишком быстро, поэтому намеренное увеличение времени обработки может помочь нам получить больше данных, не создавая никаких предупреждений. Теперь, когда у нас есть необработанные данные изображения, наш следующий шаг - выполнить некоторую обработку этих изображений с помощью модуля PIL, чтобы привести их в пригодную для использования форму.
Обработка изображений PIL
Наша цель здесь - преобразовать наши изображения в пригодные для использования пакеты данных. Для каждого изображения наша модель нейронной сети требует в качестве входных данных массив функций и массив меток. Массив функций - это, по сути, массив чисел, соответствующих каждому пикселю наших данных. Для данных цвета это означает 3 числа, соответствующие значению RGB для каждого пикселя. Для оттенков серого, как мы будем использовать, требуется только 1 число, яркость. Массив меток - это всего лишь одномерный массив с каждой меткой изображения, числовой уровень, который соответствует этой категории изображений. В нашем случае изображения тигров получают уровень 1, а изображения велосипедов - уровень 0. Прежде чем мы сможем получить эти массивы, мы должны преобразовать все наши изображения в согласованную обрабатываемую форму. Начнем с того, что сделаем их все черно-белыми. Чтобы преобразовать изображение jpg в оттенки серого, мы запускаем:
import PIL img= Image.open(jpg) img = img.convert('L')
Затем мы преобразуем все изображения в некоторую общую форму и разрешение, поскольку модель ожидает, что от каждого изображения будет поступать один и тот же объем данных, то есть одно и то же количество пикселей. Здесь мы также можем настроить на основе большого количества данных, которые может обработать наш компьютер. Изображение размером 1000x1000 содержит 1 миллион пикселей, то есть 1 миллион фрагментов данных, которые наша модель должна преобразовать и обработать на 1 изображение. Мы настраиваем разрешение в соответствии с нашими вычислительными возможностями, игра с числами, как правило, здесь самый простой подход. Мы можем использовать функции «кадрирования», «размера» и «изменения размера» PIL, как показано ниже. Во-первых, чтобы обрезать изображение до квадрата.
size = img.size dim = min(size) img = img.crop((0, 0, dim, dim))
Это возвращает квадратное обрезанное изображение размера dimxdim, где dim - это минимальное значение высоты и ширины изображения. Теперь настройте разрешение
img=img.resize((130, 130))
Это возвращает наше изображение в разрешении 130x130.
Наконец, чтобы превратить наш img в массив, мы просто запускаем:
img=np.asarray(img)
Возвращение массива 130 на 130 значений оттенков серого, соответствующих каждому пикселю. Мы объединяем все это вместе в одну функцию image_processor:
def image_processor(jpg): img= Image.open(jpg) img = img.convert('L'). #converts to grayscale size = img.size dim = min(size) img = img.crop((0, 0, dim, dim)) #crops to square img=img.resize((130, 130)) #changes res to 130x130 name=str(jpg) img.save(name) #saves image to folder return(img)
Эта функция возвращает полностью обработанное изображение и сохраняет это изображение в папке.
Преобразование данных
Теперь, чтобы превратить наши изображения в наши массивы изображений и массивы меток. Следующие фрагменты кода обрабатывают каждое изображение с помощью image_processor, конвертируют эти обработанные изображения в image_arrays и label_arrays и разбивают данные на наборы для обучения и тестирования.
from os import walk import shutil # first we need file path to tiger photos, bike photos, and images #the folder containing both tiger_path='<insert _tiger file path>' bicycle_path='<insert bike file path>' image_path='<insert image file path>' tiger_img=(_, _, filenames) = list(next(walk(tiger_path))) tiger_img=tiger_img[2] bicycle_img=(_, _, filenames) = list(next(walk(bicycle_path))) bicycle_img=bicycle_img[2] #gets list of all files names in tiger and bike folder respectively dir_path = image_path + '/processed_images' if os.path.exists(dir_path): shutil.rmtree(dir_path) os.chdir(image_path) os.makedirs("processed_images") #first deletes "processed_images" folder if present, then adds new #folder of that name os.chdir(tiger_path) A=0 for jpg in tiger_img: A=A+1 label=str(A)+"_image_tiger" print(label) img=image_processor(jpg) jpg_name = ("%s" % (label)) + ".jpg" file_path = os.path.join( dir_path,jpg_name) print(file_path) img.save( file_path ) #sets tiger photos folder as directory, processes all photos inside #and saves them to processed images folder under name #str(A)+"_image_tiger" where A is just index of photo os.chdir(bicycle_path) B=0 for jpg in bicycle_img: B = B + 1 label = str(B)+"_image_bicycle" img=image_processor(jpg) jpg_name = ("%s" % (label)) + ".jpg" file_path = os.path.join( dir_path,jpg_name) img.save( file_path ) #Same for bikes os.chdir(dir_path) train_images=[] train_labels=[] test_images=[] test_labels=[] Bt=int(B*.7) At=int(A*.7) # sets processed images folder as new directory, sets limiting index #for training and testing set as .7*total index for bike and tiger #respectively for i in range(1,Bt): bike_img=str(i)+"_image_bicycle" +'.jpg' bike_img = Image.open(bike_img) bike_array=np.asarray(bike_img) train_labels.append(0) train_images.append(bike_array) #converts processed training bike images to array, appends array #to train_images, appends 0 to train labels since bikes have label 0 for i in range(1, At): tiger_img = str(i)+"_image_tiger" + '.jpg' tiger_img = Image.open(tiger_img) tiger_array = np.asarray(tiger_img) train_labels.append(1) train_images.append(tiger_array) #converts processed training tiger images to array, appends array #to train_images, appends 1 to train labels since tigers have label #0 for i in range(Bt,B): bike_img=str(i)+"_image_bicycle" +'.jpg' bike_img = Image.open(bike_img) bike_array=np.asarray(bike_img) test_labels.append(0) test_images.append(bike_array) for i in range(At, A): tiger_img = str(i)+"_image_tiger" + '.jpg' tiger_img = Image.open(tiger_img) tiger_array = np.asarray(tiger_img) test_labels.append(1) test_images.append(tiger_array) # same for testing train_images=np.asarray(train_images) train_labels=np.asarray(train_labels) test_images=np.asarray(test_images) test_labels=np.asarray(test_labels) #creates array of the array lists train_images = train_images / 255.0 test_images = test_images / 255.0 #scales value down to [0,1], to produce our final data
Теперь данные полностью обработаны и готовы к использованию для обучения нейронной сети.
Настройка базовой нейронной сети
Код ниже вводит наши обработанные данные в нейронную сеть. Мы вводим соответствующую форму данных, желаемое количество классов и желаемые гиперпараметры. Здесь гиперпараметры: relu, softmax, adam, sparse_categorical_crossentropy, epoch. Эпоха - это просто количество раз, когда модель видит наши данные. Сложность моделей такова, что они могут видеть данные в разное время и узнавать разные вещи из этих данных (в отличие от регрессии, которая дает одинаковый результат при постоянной загрузке одних и тех же данных). Мы хотим настроить эпоху так, чтобы модель училась всему, что можно, на основе данных, но не настолько, чтобы ее переобучать. Мы увидим это через секунду.
from tensorflow import keras model = keras.Sequential([ keras.layers.Flatten(input_shape=(130, 130)), # input layer (1) #flatten changes shape from 130x130 tensor to 16900 vector keras.layers.Dense(2500, activation='relu'), # hidden layer (2) #Dense means all connected, 2500 is number of neurons, should be #fraction size of input layer here ~1/8 keras.layers.Dense(2, activation='softmax') # output layer (3) #should have as many neurons as classes to predict ]) model.compile(optimizer='adam', loss='sparse_categorical_crossentropy', metrics=['accuracy']) #Compiles model using given hyperparameters model.fit(train_images, train_labels, epochs=5) #trains model on our data
Эта модель выводит:
Epoch 1/5 8/8 [==============================] — 1s 129ms/step — loss: 0.6762 — accuracy: 0.4828 Epoch 2/5 8/8 [==============================] — 1s 133ms/step — loss: 0.6070 — accuracy: 0.6681 Epoch 3/5 8/8 [==============================] — 1s 131ms/step — loss: 0.5521 — accuracy: 0.7500 Epoch 4/5 8/8 [==============================] — 1s 132ms/step — loss: 0.5142 — accuracy: 0.7543 Epoch 5/5 8/8 [==============================] — 1s 131ms/step — loss: 0.4701 — accuracy: 0.8190 4/4 [==============================] — 0s 12ms/step — loss: 0.5468 — accuracy: 0.6765 test_acc= 0.6764705777168274
Как мы видим, точность улучшается с каждой добавляемой эпохой, но мы знаем, что точность должна достигать пика в какую-то эпоху. Мы можем перебирать эпохи, чтобы увидеть, где мы достигаем наилучшей точности тестирования, выполняя следующие действия:
import matplotlib.pyplot as plt accuracy_test=[] accuracy_train=[] for i in range(25): model.compile(optimizer='adam', loss='sparse_categorical_crossentropy', metrics=['accuracy']) model.fit(train_images, train_labels, epochs=i) test_loss, test_acc = model.evaluate(test_images, test_labels, . verbose=1) accuracy_test.append(test_acc) train_loss,train_acc=model.evaluate(train_images, train_labels,verbose=1) accuracy_train.append(train_acc) #runs model with 1 to 25 epochs, gets test and train accuracy for each and appends to 2 lists import matplotlib.pyplot as plt plt.plot(accuracy_test) plt.plot( accuracy_train) plt.title('accuracy v epoch') plt.ylabel('accuracy') plt.xlabel('epoch') plt.legend(['Validation','Train'], loc='upper left') plt.show() #plots training and testing accuracy per epoch
Выполнение кода возвращает следующий график:
Точность на тестовом наборе достигает пика около 0,85 в течение 12 периодов, затем падает до 16, после чего все становится статичным, поскольку модель полностью запомнила данные.
Можем ли мы сделать лучше? Наивный подход может заключаться только в увеличении разрешения. Более высокая детализация = больше информации = лучший прогноз, верно? К сожалению, это не так просто.
Больше пикселей не всегда означает большую точность, но всегда приводит к слишком большему времени вычислений. Более разумным способом может быть использование более продвинутой модели, такой как сверточная нейронная сеть, или добавление слоев, экспериментирование с другими гиперпараметрами (настройка гиперпараметров).
Источники: