Здравей, Internet Traveler, и добре дошъл в първата от трилогията статии! Ще се повозим в света на машинното обучение, демонстрирайки как може да изглежда работата по определен тип задачи. Ще следваме пълния набор от стъпки, от намирането на необходимите данни до съхраняването на нашия оценен модел на машинно обучение за по-късно. Ще се заровим в някои интересни аспекти и ще научим за възможни проблеми. Това няма да бъде подробен урок или някакъв вид мини-курс за машинно обучение: той просто ще служи като изследване на случай на употреба. След като прочетете поредицата, ще придобиете интуиция за използваните методи, основния работен процес и някои възможни клопки при работа по проект от този тип.

За да дефинираме нашия примерен проблем с машинното обучение, имаме нужда от данни, и то много. Публичната наличност на набори от данни с прилично качество се подобри неимоверно през последното десетилетие, което означава, че всеки има достъп до всякакви интересни набори от данни.

Нека да разгледаме един такъв набор от данни, „Uber TLC FOIL Response“. По същество това е изхвърляне на пътуванията с Uber в Ню Йорк от по-голямата част от 2014 и 2015 г. Изхвърлянето също така съдържа подобни данни за други доставчици на таксита и превози, макар и в много по-малък мащаб – по-малко от 200 000 за най-големия набор от данни на доставчици, срещу милиони на записвания за Uber. И накрая, някои спомагателни данни са включени, като обобщени статистики и различни търсения на код.

Какво ще правим с такъв набор от данни? Е, възможностите са много. Въпреки това, тъй като това е поредица от блогове със сравнително ниска лента за разбиране на влизане, трябва да се придържаме към прости проблеми с играчките. Имайки това предвид, ние дефинираме проблема, както следва:

Въз основа на набора от данни ще се опитаме да изградим предиктор, който оценява броя на пътуванията — „интензивността“ на използването на Uber — за дадено място и време в рамките на Ню Йорк.

По същество имаме избор между наборите от данни за 2014 г. и 2015 г. Както обяснява README, разликата е следната:

  • този от април-септември 2014 г. съдържа точна информация за геолокация за позицията на пикапа,
  • този за януари-юни 2015 г. съдържа само кодове на местоположение, които могат да бъдат съпоставени с квартали (градски квартали) и подзони.

Комплектът 2014 предлага по-голям потенциал за изследване и анализ. Но има проблем - трансформирането на необработените данни за геолокация в интерпретируема информация може да бъде с достатъчен обхват, за да формира основа за цяла отделна поредица от блогове. Ще трябва да получим точна геокарта на Ню Йорк, да определим детайлността на нашите интересни области, да дефинираме всякакви специални интересни точки, да начертаем координатите на тези области и да интерпретираме съществуващите данни по отношение на тази карта, търсейки всякакви допълнителни взаимоотношения.

Тогава ще продължим с набора от данни за 2015 г. Преди да навлезем в самия анализ, нека първо да поговорим за нашите инструменти.

Основен учебник за панди

Ще използваме главно pandas, вероятно доминиращата библиотека на Python за анализ на данни и общи спорове.

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

ще изглежда така:

Имайте предвид, че стойностите на колоните са представени като pandas.Series обект. Като цяло pandas предоставя доста стандартен OOP API - както DataFrames, така и Series имат множество инструменти за достъп и методи за работа с данните. По-конкретно, има три метода DataFrame (или DF), които вероятно ще използвате много често.

Първият е .head(), който просто връща началните редове на DataFrame:

Тук имаме само 3 реда, така че целият DF се отпечатва, но на практика ще използвате много по-големи набори от данни. И това е, за което .head() е добър - да получите първи, незабавен "вкус" на данните.

Вторият метод е .info() :

Това предоставя сравнително подробен преглед на DF, включително неговия размер в паметта. Обръщаме специално внимание на колоната Dtype – всеки тип колона има различни оператори, които могат да се използват с нея. Виждаме също, че конструкторът DataFrame е достатъчно умен, за да идентифицира първата колона като изцяло цяло число. За останалите избра object, което също е по подразбиране за колони от „смесен тип“.

И накрая, има .describe():

който изобразява някои обобщени статистически данни за всяка колона (по подразбиране само числови, следователно нашата употреба на include=’all’). Обърнете внимание, че числените и номиналните имат различни набори от информация за тях. Като цяло, това е още един инструмент за „дегустация“ на данните – например, можете бързо да определите дали има някакви значителни „извънредности“ или да видите колко уникални стойности има в номиналните колони.

Важно е обаче да запомните, че този API не е вашият стандартен API за колекции на Python. Например индексирането работи по следния начин:

или по колони и редове:

За щастие можете да използвате срезове, списъци с индекси/етикети или други методи за интелигентен избор на подмножества от данни.

В действителност ние можем да използваме инструмент за достъп до индекс на DataFrame:

Това ни позволява да получим всички точки от данни за дадена колона по индекс. Нещо повече, той връща обект от гореспоменатия тип Series:

които ще използваме широко в нашия анализ.

И това е, защото разбирането на списъци, друга основна програма на Python, също се гледа с неодобрение в света на пандите. Вместо това влизат в действие гореспоменатите специфични за Dtype оператори:

В последния пример използваме str, StringMethods инструмента за достъп, изрично, за да използваме pandas различни низови методи, налични на Series.

Причината зад цялата песен и танц с персонализиран API е проста: във фонов режим pandas разчита на много ефективен собствен код за бързо изпълнение на операции върху големи набори от данни. Тъй като ще провеждаме pandas обаждания на данните от Uber FOIL за 2015 г., ще видим, че по-голямата част от стъпките отнемат не повече от части от секундата — за повече от дузина милиона записа. Замяната на този код със „стандартен Python“ ще го направи значително по-бавен. Недостатъкът е, разбира се, че трябва да научите изцяло нов API, включително необичайни модели за достъп като .str StringMethods по-горе.

Към кода

Нека изтеглим набора от данни, разархивираме го в папка, наречена data, стартираме Jupyter Notebook (с инсталирани pandas и scikit-learn) и заредим набора от данни .

Имайте предвид, че процесът на зареждане може да отнеме известно време, поне няколко секунди - главно в зависимост от типа дисково устройство, което използвате, и скоростта на вашия процесор.

Резултатът е почти същият като очаквания:

Уау, 14 милиона записа! Интересното е, че за много проблеми, с които се занимават специалисти по данни или инженери по машинно обучение, това е не само управляемо, но, в зависимост от тяхната специализация, вероятно и малко. Все пак целият DataFrame отнема по-малко от половин гигабайт RAM, което означава около 32 байта на ред - доста ефективно.

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

Dispatching_base_num и Affiliated_base_num са и двата кодове на фирмени бази, лицензирани от Комисията за таксита и лимузини в Ню Йорк. Например, B02617 е кодът за базата 'Weiter', която, както вероятно очаквате, е една от базите на Uber (очевидно наречена най-вече след немски локационни реклами по някаква причина).

Pickup_date съдържа добре форматирани (надяваме се, за всички редове) ISO-8601 дата и час. Това е добре - ще бъде лесно да се анализира в нещо различно от object Dtype,, както ще видим.

locationID е идентификационният номер на конкретна зона или интересна точка. Друго наблюдение тук е, че колоната е дефинирана като int64, което означава, че поне всички кодове са дадени в цифрова форма – и не, да речем, съдържат някакво замърсяване на данните под формата на буквени знаци.

На този фронт сега искаме да преобразуваме абстрактните locationIDs в по-смислена информация. За целта използваме предоставената справочна таблица:

Таблицата, както очакваме, съдържа картографиране от locationID в „основния“ набор от данни в двата района и техните подразделения и/или POI.

Обединяваме двата DataFrame с една уловка — първо трябва да преименуваме колоната за справка, тъй като тя е изписана с главна буква в зоната на такси DataFrame (за разлика от „основната“):

Вече сме готови да преобразуваме тези данни в информация. Точно това ще направим в следващата част от поредицата.

Забележка: нито авторът на тази публикация, нито SoftwareMill са свързани с Uber по никакъв начин.