Коли даних дійсно багато: Vowpal Wabbit

Привіт, хабр!



В попередніх двох постах (раз, два) ми расмотрели основні алгоритми і техніки, що застосовуються учасниками змагань Kaggle. Сьогодні хотілося б піти далі і поговорити про те, з якими труднощами зустрічаються дослідники при розробці алгоритмів у разі, коли даних дуже багато і навчатися припадає на вибірках, які не поміщаються в пам'ять. Відразу варто відзначити, що це відбувається досить часто, навіть на самому Kaggle (в даній задачі навчальна вибірка має об'єм в кілька гігабайт і новачкові може бути просто не зрозуміло, що з цим робити). Нижче ми розглянемо алгоритми машинного навчання та інструменти, справляються з цією проблемою.

Багато хто, хто знайомий з машинним навчанням знають, що досить часто хороша якість можна отримати завдяки простим лінійним моделям, за умови, що були відібрані і добре згенеровані ознаки (що ми обговорювали раніше). Дані моделі радують також своєю простотою і часто навіть наочністю (наприклад, SVM, який максимізує ширину розділяє смуги). Проте, є ще одне дуже важливе достоїнство лінійних методів — при навчанні можна досягти того, що настройка параметрів алгоритму (тобто етап оновлення ваг) буде проводиться кожного разу при додаванні нового об'єкта. Дані методи машинного навчання у літературі часто також називають Online Machine Learning або активне навчання).

Якщо не вдаватися в деталі, то в двох словах виглядає це приблизно так: для того, щоб підібрати параметри конкретного лінійного методу (наприклад, ваги в логістичній регресії), ініціалізується деяке початкове значення цих параметрів, після чого отримуючи на вхід черговий об'єкт з навчальної вибірки, ваги оновлюються. Так ось лінійні методи допускають саме такий вид навчання. При цьому, зрозуміло, що зберігати всі об'єкти одночасно в пам'яті тепер просто не потрібно.

На сьогоднішній день однією з найбільш відомих реалізацій таких методів є пакет Vowpal Wabbit, який можна коротко описати кількома пунктами:

  • В ньому можна навчати тільки лінійні моделі. При цьому, збільшувати якість самих методів, як ми вже знаємо можна за рахунок додавання нових ознак, підгонкою функції втрат, а також завдяки використанню низкоранговых розкладень (про це докладніше поговоримо в наступних статтях)
  • Навчальна вибірка обробляється з допомогою стахостического оптимізатора, завдяки чому можна навчатися на вибірках, які не поміщаються в пам'ять
  • Можна обробляти велику кількість ознак за рахунок їх хешування (так званий hashing trick), бладаря чому можна навчати моделі навіть у випадках, коли повний набір ваг просто не поміщається в пам'яті
  • Підтримується режим активного навчання, при якому об'єкти навчальної вибірки можна подавати навіть з декількох машин по мережі
  • Навчання може бути распараллелено на кілька машин
Отже, зупинимося детальніше на тому, як працювати на практиці з даними інструментом і які результати можна за допомогою нього отримати. В якості прикладу розглянемо відому задачу Titanic: Machine Learning from Disaster. Напевно, це не самий вдалий приклад з огляду на те, що даних у цій задачі небагато. Однак, т. к. стаття розрахована в першу чергу на новачків в машинному навчанні — дана пост буде відмінним продовженням офіційного Tutorial'а. До того ж, досить легко буде надалі переписати в цьому пості код для реальної (і актуальний на момент написання посту) завдання Click-Through Rate Prediction — в ній навчальна вибірка має розмір більш 5Гб.

Перед початком опису конкретних кроків зазначимо, що описаний далі код запускався досить давно (ще до того, як Vawpal Wabbit став популярний), а сам проект останнім часом активно оновлюється, тому далі всі наведені вихідні вірні з деякою точністю автор залишає це на перевірку читачеві.

Нагадаємо, що в розглянутій задачі пропонується побудувати класифікатор, який передбачав би для конкретної людини (пасажира Титаніка) — потоне він чи ні. Не будемо докладно описувати умову задачі і дані нам ознаки. Бажаючі можуть ознайомитися з цією інформацією на сторінці змагання.

Отже, почнемо з того, що Vowpal Wabbit приймає на вхід дані в певному форматі:

label |A feature1:value1 |B feature2:value2

Який в цілому нічим не відрізняється від звичної матриці «об'єкт-ознака» за винятком того, що ознаки можна розбити на категорії, щоб надалі при навчанні частину з них можна було «відключати». Тим самим, після того, як ми завантажили навчальну та тестову вибірки, насамперед необхідно перетворити дані в формат, який би читав Vowpal Wabbit

Підготовка навчальної та тестової вибірок
Для цього можна взяти простий скрипт (а можна просто скористатися чудовою бібліотекою phraug2), який читає файл train.csv порядково і перетворимо кожен об'єкт навчальної вибірки до потрібного формуту. Варто відзначити, що label у разі двохкласовому класифікації у нас приймає значення +1 або -1

import csv
re import

i = 0

def clean(s):
return " ".join(re.findall(r'\w+', s,flags = re.UNICODE | re.LOCALE)).lower()

with open("train_titanic.csv", "r") as infile, open("train_titanic.vw", "wb") as outfile:
reader = csv.reader(infile)

for line in reader:
i += 1
if i > 1:
vw_line = ""
if str(line[1]) == "1":
vw_line += "1 '"
else:
vw_line += "-1 '"

vw_line += str(line[0]) + " |f "
vw_line += "passenger_class_"+str(line[2])+" "

vw_line += "last_name_" + clean(line[3].split(",")[0]).replace(" ", "_") + " "
vw_line += "title_" + clean(line[3].split(",")[1]).split()[0] + " "
vw_line += "sex_" + clean(line[4]) + " "

if len(str(line[5])) > 0:
vw_line += "age:" + str(line[5]) + " "

vw_line += "siblings_onboard:" + str(line[6]) + " "
vw_line += "family_members_onboard:" + str(line[7]) + " "
vw_line += "embarked_" + str(line[11]) + " "

outfile.write(vw_line[:-1] + "\n")

Аналогічно поступимо і з тестовій вибірці:

i = 0

with open("test_titanic.csv", "r") as infile, open("test_titanic.vw", "wb") as outfile:
reader = csv.reader(infile)

for line in reader:
i += 1
if i > 1:
vw_line = ""
vw_line += "1 '"
vw_line += str(line[0]) + " |f "
vw_line += "passenger_class_"+str(line[1])+" "
vw_line += "last_name_" + clean(line[2].split(",")[0]).replace(" ", "_") + " "
vw_line += "title_" + clean(line[2].split(",")[1]).split()[0] + " "
vw_line += "sex_" + clean(line[3]) + " "

if len(str(line[4])) > 0:
vw_line += "age:" + str(line[4]) + " "

vw_line += "siblings_onboard:" + str(line[5]) + " "
vw_line += "family_members_onboard:" + str(line[6]) + " "
vw_line += "embarked_" + str(line[10]) + " "

outfile.write(vw_line[:-1] + "\n")

На виході отримуємо 2 файлу train_titanic.vw та test_titanic.vw відповідно. Варто відзначити, що це найчастіше самй складний і довгий етап — підготовка вибірки. Фактично далі ми будемо лише запускати кілька разів методи машинного навчання на цій вибірці і тут же отримувати результат

Навчання лінійних моделей в Vowpal Wabbit
Робота відбувається з командного рядка за допомогою запуску утиліти vw з переданими їй параметрами. Щоб запустити. Ми не будемо сосредатачиваться на детальному описі усіх параметрів, а запустимо лише один з прикладів:

vw train_titanic.vw-f model.vw --binary --passes 20-c-q ff --adaptive --normalized --l1 0.00000001 --l2 0.0000001-b 24

Тут ми повідомляємо про те, що хочемо вирішити завдання бінарної класифікації (--binary), хочемо зробити 20 проходів за навчальною вибіркою (--passes 20) хочемо зробити L1 і L2 регуляризацию (--l1 0.00000001 --l2 0.0000001), нормалізацію, а саму модель зберегти у model.vw. Параметр -b 24 використовується для вказівки функції хешування (як говорилося спочатку — всі ознаки хешуються, а самі хеші приймають значення від 0 до 2^b-1). Також, важливо зазначити параметр -q ff, який вказує, що ми також хочемо додати в модель парні ознаки (це дуже корисна фіча VW, деколи дозволяє помітно збільшити якість алгоритмів)

Після деякого часу ми отримаємо навчену модель. Залишається тільки запустити алгоритм на тестовій вибірці

vw-d test_titanic.vw-t-i model.vw-p preds_titanic.txt

І сконвертувати результат для відправки в систему kaggle.com:

import csv

with open("preds_titanic.txt", "r") as infile, open("kaggle_preds.csv", "wb") as outfile:
outfile.write("PassengerId,Survived\n")

for line in infile.readlines(): 
kaggle_line = str(line.split(" ")[1]).replace("\n","")
if str(int(float(line.split(" ")[0]))) == "1":
kaggle_line += ",1\n"
else:
kaggle_line += ",0\n"

outfile.write(kaggle_line)

Дане просте рішення показує досить непогану якість — більш 0.79 AUC. Ми застосували досить тривіальну модель. Оптимізуючи параметри і «погравши» з ознаками можна трохи поліпшити результат (читачеві пропонується зробити це в якості вправи). Сподіваюся, це введення допоможе новачкам справлятися з об'ємом даних у змаганнях з машинного навчання!

Джерело: Хабрахабр

0 коментарів

Тільки зареєстровані та авторизовані користувачі можуть залишати коментарі.