Как изградих класификатор за логистична регресия от нулата с Python, за да предскажа загубата на тегло

Хан Нгуен

19 февруари 2019 г. · 30 минути четене

За да видите кода, който написах за този проект, можете да разгледате репозитория на Github

загубата

Започнах пътуването си за отслабване в началото на 2018 г., следвайки често цитираните съвети за „отслабване = диета + упражнения“. От страна на диетата започнах да проследявам ежедневната си консумация на храна (използвайки хранителна скала и записвайки калории чрез приложението Loseit). От страна на упражненията започнах да следвам програмата Couch to 5K и към днешна дата завърших четири 5K, една 10K и отпреди няколко седмици полумаратон. И накрая, всяка сутрин се претеглях веднага след събуждане и записвах теглото си в същото приложение Loseit.

Всеки, който се опитва да отслабне, неизбежно ще удари плато за отслабване, където първоначалната бърза загуба на тегло започва да се забавя. Лично аз улучих голямо плато веднага след почивката си в началото на май. След това изоставих проследяването на напредъка си близо три месеца. Едва след като замених умрелия си часовник Pebble с Amazfit Bip, получих обратно някаква мотивация да натисна бутона за възобновяване, отчасти тъй като можех да започна да проследя стъпките си с новия часовник. Въпреки това, теглото ми продължи да плато и след още два месеца на разочароващи колебания в теглото (вижте пунктираната област по-долу), спрях да проследявам теглото и калориите си изобщо. Това беше ноември миналата година.

Сега е 2019 г. и тъй като Tet (виетнамска Нова година) наскоро наближи своя край, реших да разгледам по-отблизо данните, събрани през този двумесечен период, с надеждата да открия интересни връзки между загубата на тегло и проследените калории/стъпки, за да мога да изградя по-ефективен план за отслабване от преди.

Събиране на данни

Калории: експортирани от моя акаунт в Loseit като CSV файлове. По някаква странна причина Loseit позволява да се експортират данни за калории само една седмица, но обединяването им не е нищо, което бързият скрипт на Python не може да направи. Всяка дата има съответстващо броене на калории от цялата храна, която регистрирах през този ден, както и „бюджет“ на калориите, който приложението изчисли за мен въз основа на теглото ми през този ден и целта за отслабване, която първоначално посочих (да загубя 0,75 кг/седмица).

Стъпки: приложението за Android на моя умен часовник Amazfit Bip не позволява експортиране на данни, освен ако човек не използва някакво космат решение с инструменти на трети страни. Следователно най-бързият метод за получаване на данните за стъпките ми беше ръчно превъртане през десетките дати (в рамките на този двумесечен период) на телефона ми и въвеждане на стъпките за всяка дата в CSV файл. Изобщо не елегантен, но хей, работи!

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

След като се присъединих към тези 3 източника на данни заедно по дата, в крайна сметка получавам калории + стъпки + данни за тегло само за 46 дати от този двумесечен период. Очевидно пренебрегнах да запиша поне една от тези три данни на доста дати (много от тях през уикендите, по очевидни причини).

Преобразуване на данни

От тези три източника на сурови данни се изчисляват три допълнителни полета с данни:

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

Наддаване на тегло = утре тегло - днес тегло. Положителното наддаване на тегло за дадена дата означава, че съм наддал за този ден (да!), И обратно.

Състояние на наддаване на тегло: положителното наддаване на тегло ще бъде означено като 1, а отрицателното или нулево наддаване на тегло ще бъде обозначено като 0. Решавам да използвам състоянието на бинарното наддаване на тегло - независимо дали съм наддал или не - вместо по-гранулираното количество наддаване на тегло, откакто обсебвам напълняването с 0,5 кг срещу 0,3 кг е доста контрапродуктивно, не на последно място, защото количеството на наддаване на тегло може да бъде повлияно от много фактори освен калории и стъпки (като прием на вода, време на хранене и т.н.).

Визуализация на данни

Когато начертавам излишък от калории и брой стъпки на всеки ден спрямо състоянието ми на наддаване на тегло през този ден (вж. Първите два панела по-долу), изглежда няма очевидни модели за това как тези два според съобщения важни фактора могат да предскажат дали ще наддавам или не.

Когато обаче начертаете излишък от калории и стъпки заедно (десният панел по-горе), се появяват много интересни модели! Например веднага мога да кажа, че има две отделни групи дати по отношение на броя на моите стъпки: тези под 5000 стъпки (моите „базови“ мързеливи дни), които лежат спретнато около хоризонтална линия, и тези над 5000 стъпки (моите активни дни), до голяма степен благодарение на моето бягане. По отношение на излишъка от калории има три основни наблюдения:

  1. В моите мързеливи дни, ако ям над лимита на бюджета си за калории, ще бъде лоша новина на следващия ден, когато стъпя на кантара. Имаше някои чудеса, като онзи ден, когато изядох почти 1500 калории над границата си и не напълнях на следващия ден, но това са малко и много.
  2. От друга страна, ако в ленивите си дни ям под лимита на бюджета си за калории, и аз не съм напълно на ясно: има доста дни, в които бях добро момче и си ядох салатите (образно), но все пак качил килограми. Това предполага, че трябва да бъда още по-консервативен в храненето си в мързеливи дни.
  3. Въпреки това, в моите активни дни изглежда, че мога да си позволя да ям повече от ограничението на калориите си, тъй като има няколко активни дни, в които съм ял повече от това, което ограничението позволява и въпреки това не съм наддал на тегло.

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

Кой линеен класификатор да използвам?

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

  • Лесно е да се тълкува: например уравнението за границата на класификация може лесно да се получи от коефициентите на логистична регресия.
  • Това е лесно за изпълнение: много важна причина, тъй като друга цел на този проект е за мен да реализирам проект за машинно обучение, без да използвам вече съществуващи библиотеки (като scikit-learn). Както ще покажа по-късно, ядрото на учебния алгоритъм за логистична регресия може да бъде изпълнено само с 3 реда код!

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

Моля, обърнете внимание, че този математически преглед е предимно с цел установяване на обща нотация, така че да сме на една и съща страница, преди алгоритъмът да бъде приложен от математическите уравнения. Ако искате да разберете как се получават тези уравнения и интуицията зад тях, аз се свързах с други ресурси в края на тази публикация в блога, които могат да вършат тези задачи по-добре от мен.

Прогнозирайте вероятността от наддаване на тегло от характеристиките

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

  • y (i): тегло отново състояние на дата i (1 = наддаване на тегло, 0 = отслабване)
  • P (y (i) = 1): прогнозна вероятност да напълнея на i дата
  • x (i): наблюдавани стойности на съответните характеристики (излишък на калории и брой стъпки) към датата i *
  • θ: регресионни коефициенти/тегла на съответните характеристики (да се научат от данните) *

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

Следователно, след като θ са научени от данни, логистичната регресия може да се използва, за да класифицирам дали ще наддавам на даден ден - като се има предвид излишъкът ми от калории и броят на стъпките - чрез проверка дали вероятността да напълнея през този ден е по-висока определен праг (обикновено 50%).

Как се научават θ

Коефициентите на регресия (θ’s) се научават чрез максимизиране на логаритъма на вероятността да наблюдавам данните си за обучение (наричан още логаритмична вероятност). Формулата за логаритмична вероятност е:

  • L: вероятност за регистрация на данните за обучение (m точки от данни)
  • y (i): истинско състояние на наддаване на тегло от дата i (1 = наддаване на тегло, 0 = отслабване)
  • P (y (i) = 1): прогнозирана вероятност да напълнея на дата i (получена от сигмоидната функция по-рано)

От сигмоидната функция различните стойности на θ ще произведат различни прогнозни вероятности за наддаване на тегло - P (y (i) = 1) - и в резултат на това различна логаритмична вероятност. Следователно целта е да се намери набор от θ, които максимизират вероятността за регистрация на данните ми за обучение, т.е. θ, които „обясняват“ данните ми за обучение най-добре.

Увеличете вероятността за регистрация, като използвате изкачване с градиент на партида

Един ясен алгоритъм за намиране на θ, които максимизират вероятността за регистрация на данните ми за обучение, е изкачване с градиент на партида, което е описано по-долу:

Стъпка 0: Инициализирайте някои стойности за θ (θ_intercept, θ_surplus, θ_step)

Етап 1: За всяка точка от данните за обучение i, изчислете вероятността за увеличаване на теглото, като използвате стойностите на характеристиките на тази точка от данни (x_intercept *, x_surplus, x_step) и стойностите на θ, инициализирани в стъпка 0. Това се прави с помощта на познатата сигмоидна функция:

* извикване на x_intercept = 1

Стъпка 2: За всяка характеристика j - прихващане/излишък/стъпка - намерете частичната производна на логаритмичната вероятност по отношение на θ на тази характеристика, използвайки уравнението по-долу:

  • ∂L/∂θⱼ: частична производна на log-вероятност по отношение на θ на характеристика j
  • y (i): истинско тегло отново състояние на дата i (1 = наддаване на тегло, 0 = отслабване)
  • P (y (i) = 1): прогнозирана вероятност за увеличаване на теглото от дата i (от стъпка 1)
  • xⱼ (i): наблюдавана стойност на характеристика j (прихващане/излишък/стъпка) на дата i, с x_intercept (i) = 1 за всеки i

Стъпка 3: За всяка характеристика j актуализирайте θ с частичната производна на логаритмичната вероятност по отношение на тази θ (от стъпка 2), умножена по малка константа (наричана още скорост на обучение α):

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

Стъпка 4: С тези актуализирани θ, повторете стъпки 1 до стъпка 3 до сближаване. Един от начините за тестване за конвергенция е да се види дали вероятността за регистрация се е сближила до стабилна стойност, т.е. че е достигнала вероятния максимум.

Партида срещу стохастичен градиент изкачване

Знакът за сумиране в стъпка 2 - сумиране (y (i) - P (y (i) = 1)) * xⱼ (i) над всички точки от данни i, за да се изчисли частично производно - това е причината, поради която този алгоритъм за изкачване с градиент е от партидно разнообразие, тъй като всяко частично производно се изчислява, като се използват всички точки от данни в данните за обучение. Без този знак за сумиране, т.е. частичната производна се изчислява, като се използва само една точка от данни; ако приемем, че точката с данни е избрана на случаен принцип, алгоритъмът за изкачване с градиент се нарича стохастичен.

За този проект решавам да реализирам изкачване на градиент на партида, тъй като данните ми за обучение са много малки (само 46 точки от данни), така че няма проблем да се използва целият набор от данни наведнъж за изчисляване на частични производни. Друга причина е, че изкачването с градиент на партида може да бъде изпълнено по-лесно, като се използват векторизирани операции на numpy (моля, вижте неговото изпълнение по-долу, за да видите как).

Предварителна обработка на данни

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

Преди обаче да мога да приложа алгоритъма за изкачване на градиент на партида върху тези данни, първо трябва да:

  1. Добавете колона с 1 към данните ми за обучение, за да представя стойностите на функцията x_intercept.
  2. Мащабирайте излишъка от калории и функциите за преброяване на стъпки чрез (а) изваждане на всяка колона на характеристиките от нейната средна стойност и (б) разделяне на нейното стандартно отклонение. Това се прави по две причини:

След горните две стъпки моята матрица на характеристиките (X) се преобразува в двумерна масивна матрица с размери (46, 3), а моят вектор на етикета (y) в едномерна масивна матрица с размери (46).

Прилагане на изкачване с градиент на партида

Стъпка 0: Инициализирайте θ

Инициализирам всичките си θ на 0,5. Резултатът е 1-D numpy масив тита на измерение (3)

Стъпка 1: За всяка точка от данни изчислете вероятността за увеличаване на теглото, като използвате θ от стъпка 0 и сигмоидната функция

Тук са полезни векторизираните операции на numpy, тъй като вместо да изчислява вероятността за увеличаване на теглото за всяка точка от данни, numpy го прави за всички точки от данни наведнъж: