Как стать автором
Обновить

Определение пользовательских сценариев энергопотребления по встроенным в системную плату датчикам и Python + LightAutoML

Уровень сложностиСредний
Время на прочтение8 мин
Количество просмотров624

Привет, Хабр!

С момента предыдущей публикации прошел год, и наступил момент закрыть гештальт, возникший, в том числе, по результатам ваших комментариев. А именно: можно ли вообще обойтись без внешних устройств при решении задачи профилирования активности пользователей по данным энергопотребления их устройств?

И хотя лично мне это кажется контр-продуктивным — имхо, сугубо имхо, лучше вообще не связываться с системой, которая может быть скомпрометирована, и получать данные из дополнительного источника, никак с тестируемой системой не связанного. Ни гальванически ни, тем более, в рамках одной операционной системы. Умная розетка (не обязательно от Сбера) казалась вполне себе доступной по цене альтернативой амперметру. Но вопрос был задан, и спустя год (ну извините, это все-таки pet-проект, а не основная работа) на него есть ответ:

Да, это возможно. Причем точность классификации разных сценариев поведения может достигать 93%.

Шаг 1. Получение информации от встроенных в системную плату сенсоров

Для начала, надо было понять, какие доступны утилиты сбора информации о сенсорах. Не то, чтобы я считаю себя закоренелым питонистом, но на C предпочитаю кодить только когда дело доходит до embedded-устройств, поэтому фокусировался на Python. Немаловажный вопрос - а какая, собственно, информация и с каких сенсоров с помощью этих утилит может быть получена. Использовавшаяся ранее кросс-платформенная утилита psutils для этой задачи не годилась — она собирает только информацию по процессам, утилизации процессора и памяти и т. д.

Информация об испытуемой системе в ОС Windows
Информация об испытуемой системе в ОС Windows

Ни поиск, ни генеративный ИИ мне не помогли, пришлось погрузиться в специфику Windows Management Instrumentation (так уж сложилось, что исследуемая система работала на Windows) и разобраться в том, как подключаться к WMI-сервисам.

На мое счастье, нашелся проект LibreHardwareMonitor, который изначально задумывался для мониторинга сенсоров температуры (если есть тут те, кто помнит про оверклокинг — привет, олды!), но, к счастью для меня, также умеет добывать показания датчиков напряжения процессора.

Скриншот из LibreHardwareMonitor
Скриншот из LibreHardwareMonitor

Если верить скриншоту, утилита умеет измерять потребляемую процессором мощность (Power). Если это действительно так, то решение уже у нас в кармане!

Но чтобы получить этот показатель, как и любой другой, нужно подписаться на сообщения LibreHardwareMonitor в WMI:

import wmi

w = wmi.WMI(namespace="root\LibreHardwareMonitor")

Есть два способа обращения к сенсорам через WMI:

1. Путем выполнения WQL (микро-аналог SQL внутри WMI)-запроса, выглядящего примерно так:

sensors = w.query("SELECT * FROM Sensor")

2. С помощью специального метода .sensor():

sensors = w.Sensor()

Этот метод возвращает список объектов типа Sensor, поддерживающих некоторый набор методов и характеристик, из которых меня в рамках моего исследования интересовали только две, Sensor.Name и Sensor.Value. Для удобства обработки их можно объединить в словарь, впрочем, у каждого аналитика свой набор тараканов предпочтений. Полный код утилиты Power_monitor.py для сбора значений и записи в лог доступен в открытом репозитории по ссылке.

Чтобы минимизировать нагрузку на систему, рекомендуется скомпилировать в исполняемый файл командой:

!pyinstaller -F Power_monitor.py

Впрочем, если кто-то предпочитает держать открытой консоль интерпретатора Python или IDE — не осуждаю, хотя и замечу, что в этом случае интерпретатор может дополнительно влиять на энергопотребление системы.

Шаг 2: сбор данных

По аналогии с предыдущим исследованием, я смоделировал несколько интересующих меня сценариев: закачку данных с испытуемой системы в облако (FileUpload, сценарий, в том числе, моделирует эксфильтрацию данных), просмотр видео (поскольку активный период исследования совпал с кампанией по замедлению YouTube, на испытуемой системе запускался просмотр видео на RuTube, впрочем, полагаю, что паттерны энергопотребления у них, скорее всего, похожи) и, конечно же, «контрольный» режим «ничегонеделания» (Idle). В этот раз мне почти не пришлось писать текстов на исследуемой системе, а число классов хотелось довести до четырех, то, чтобы обеспечить сравнение с предыдущим экспериментом, был добавлен еще один класс нагрузки — прослушивание музыки (Music).

Получить данные о режиме Idle было проще всего. Просто оставить компьютер включенным на ночь с запущенным на нем утилитой по сбору данных. Все остальные сценарии потребовали чуть больше ухищрений, в результате чего датасет получился… не самым сбалансированным. Но попробуем обучить модель, которая классифицирует сценарий поведения пользователей только на основании… Кстати, а что в итоге пишется в лог-то?

Шаг 3: анализ данных

На уровне самой утилиты Process_monitor.py ограничений в количестве записываемых в лог полей нет — пишется все, что отдает LibreHardwareMonitor в WMI. Посмотрим на список названий в Sensor.Name:

Index(['Data Uploaded', ' Data Downloaded', ' Upload Speed', ' Temperature #2', ' Download Speed', ' Used Space', ' Network Utilization', ' CPU Core #1', ' Fan #4', ' CPU Core #3', ' CPU Core #2', ' CPU Package', ' Memory Used', ' CPU Cores', ' Read Rate', ' Virtual Memory Available', ' +3V Standby', ' Fan #1', ' Temperature #3', ' Write Activity', ' CPU Core #4 Distance to TjMax', ' D3D Other', ' Bus Speed', ' CPU Core #4', ' CPU Core #2 Distance to TjMax', ' CPU Total', ' Memory', ' CPU Platform', ' D3D Shared Memory Free', ' Voltage #6', ' D3D Copy', ' Temperature #1', ' Voltage #4', ' Core Max', ' Voltage #3', ' D3D Overlay', ' D3D Video Processing', ' Voltage #2', ' Write Rate', ' Virtual Memory', ' Temperature', ' Core Average', ' Fan #3', ' CPU Core Max', ' Total Activity', ' Virtual Memory Used', ' D3D Video Decode', ' GPU Power', ' D3D Shared Memory Total', ' Voltage #5', ' Read Activity', ' CPU Core', ' CPU Core #3 Distance to TjMax', ' CMOS Battery', ' Temperature #4', ' CPU Core #1 Distance to TjMax', ' Memory Available', ' CPU Memory', ' D3D Shared Memory Used', ' Voltage #7', ' Voltage #1', ' Fan #2', ' D3D 3D', ' timestamp'], dtype='object')  

Внимательный читатель обратит внимание, что названия характеристик Sensor.Name не совпадают с тем, что мы видим на скриншоте LibreHardwareMonitor – так, например, нет ничего, что напоминало бы Power, так что, увы, простого решения задачи не будет.

Давайте разбираться в данных. Для начала выкинем все те характеристики, выборочное стандартное отклонение среднего для которых равно нулю (то есть, они не меняются со временем). А заодно добавим фильтр по названию и дальше в анализе будем использовать только те, в названиях которых присутствует строка Voltage.

Почему Voltage? Потому что формуле расчета мощности энергопотребления участвует напряжение, ток или сопротивление. Из всех этих величин сенсоры LibreHardwareMonitor выдают только напряжение.

Сенсоров напряжения в логах семь штук: Voltage #1... Voltage #7. Соответствуют ли они нумерации в GUI LibreHardwareMonitor? Запишем как гипотезу для проверки на следующем шаге.

Но сначала поймем, с какой частотой писался лог - в самой утилите Power_monitor.py нет никаких таймеров задержки, так что частота опроса системы ограничивалась исключительно быстродействием системы. Как оказалось, испытуемая система записывала показания в среднем раз в секунду — в два раза реже, чем мультиметр (в пред-предыдущем эксперименте) или умная розетка. Точнее, если построить график задержек времени между каждый отсчетом, он выглядел так:

График разностей временных меток между последовательными записями
График разностей временных меток между последовательными записями

Среднее время между отсчетами составляет 1,0±0,15 с, но иногда видны задержки, достигающие трех секунд (видимо, в эти моменты система была загружена чем-то еще). Конкретно этот датасет, для которого был построен график, пришлось «подчистить», удалив из него последние 9700 слишком уж «грязных» записей.

Шаг 4: обучение классификатора

Не будем изобретать велосипед и воспользуемся ансамблем LGBM-классификаторов, который обучается в рамках фреймворка LightAutoML, использованного и в предыдущем эксперименте. Разобьем датасет на фрагменты длительностью от 30 до 120 секунд и посмотрим на сходимость метрик.

Но есть нюанс — какую именно величину напряжения выбрать? Попробуем взять Voltage #6 с самым большим средним значением. Вдруг нам повезет и именно эта характеристика соответствует CPU Core?

Общая accuracy и F-мера для каждого из сценариев активности в зависимости от числе отсчетов для показателя Voltage #6
Общая accuracy и F-мера для каждого из сценариев активности в зависимости от числе отсчетов для показателя Voltage #6

На графике отложены значения F-меры для каждого из классов (а цветовая легенда продублирована в подписи) в зависимости от размера фрагмента. И сразу видно, что сценарий выгрузки файлов определяется крайне плохо — его F-мера флуктуирует около нуля и от числа отсчетов не зависит.

Как дата-аналитик со стажем скажу: примерно так выглядит ситуация, когда "пациент" 100% мертв

Значит ли это, что нас постигло фиаско? Нет, у нас же есть показания еще шести сенсоров. Найдем среди них тот, у которого максимально выборочное среднеквадратичное отклонение. Это Voltage #1:

Общая accuracy и F-мера для каждого из сценариев активности в зависимости от числе отсчетов для показателя Voltage #1 (c FFT-преобразованием)
Общая accuracy и F-мера для каждого из сценариев активности в зависимости от числе отсчетов для показателя Voltage #1 (c FFT-преобразованием)

А вот это уже интересно. Во-первых, с ростом числа измерений подрастает F-мера для каждого из классов, как и общая Accuracy. Во-вторых, все сценарии определяются, в целом, неплохо — и это при том, что FFT-преобразование (я не буду подробно разбирать, что дает переход от временных рядов к Фурье-гармоникам, так как это уже было в предыдущей статье) не учитывает разброс значений отсчетов времени. Но напомню как выглядят флуктуации напряжения в разных сценариях:

Пример флуктуаций напряжения в разных сценариях энергопотребления для фрагмента длительностью 115 с
Пример флуктуаций напряжения в разных сценариях энергопотребления для фрагмента длительностью 115 с

и соответствующие им FFT-гармоники:

FFT-спектры для разных сценариев энергопотребления, определенные для фрагмента длительностью 115 с
FFT-спектры для разных сценариев энергопотребления, определенные для фрагмента длительностью 115 с

Даже визуально видно, что отделить один спектр от другого проще, чем их временные ряды.

Важный нюанс: FFT-преобразование для этой задачи применяется потому, что мы изначально знаем, что это квазипериодический процесс

Максимальное значение макроусредненной F-меры для всех классов составляет 93% (совпадает с Accuracy) и достигается на 115 измерениях:

Сценарий

Точность

Полнота

F-мера

Количество примеров

Idle

0,98

1,00

0,99

113

FileUpload

0,86

0,83

0,84

23

RuTube

0,82

0,82

0,82

22

Music

0,82

0,78

0,80

18

Макроусреднение

0,87

0,86

0,86

176

Средневзвешенное

0,93

0,93

0,93

176

Конечно, видно, что датасет не сбалансирован: так как получить замеры для «ничегонеделания» проще всего, их поэтому и много. Немного страдает полнота для сценария прослушивания музыки, что вполне объяснимо, потому что такие классификаторы склонны к ложноположительным срабатываниям. Но в целом, с поправкой на малое число примеров, выглядит все неплохо. И да, если говорить о средневзвешенной F-мере классификации на этом участке с поправкой на малое число классов, посмотрев на соседние отсчеты (113 и 114 с), то корректнее утверждать, что она находится в диапазоне от 90 до 93%.

Код обучения и тестирования ансамбля LGBM-классификаторов полностью аналогичен базовому примеру применения TabularAutoML для задачи многоклассовой классификации. Все, что нужно сделать, это определить соответствующую задачу классификации в константах AutoML:

 task = Task('multiclass')

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

Выводы.

  • Главный вывод: ответ на вопрос «можно ли для решения задачи классификации поведения пользователя по паттернам энергопотребления обойтись вообще без внешних устройств?» получен, и он положительный. За почти десять лет моего личного интереса к этой теме удалось пройти путь от лабораторных мультиметров с 32-разрядными АЦП до 20-разрядных АЦП, встроенных в умные розетки. А теперь - и до считывания показаний сенсора питания процессорного ядра, которое варьируется всего-то в пределах 0,2 В.

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

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

  • Было бы здорово воспроизвести данный эксперимент и на других системах — есть предположение, что на более мощных процессорах, нежели испытуемый N100, можно добиться и большей частоты дискретизации. Ну или хотя бы — такой же, как для умных розеток (2 Гц). Если есть желающие — весь исходный код доступен в репозитории. Повторюсь, моя личная рекомендация — скомпилировать в исполняемый файл и запускать отдельно от интерпретатора Python и/или IDE.

  • Было бы также здорово обойтись без дополнительной утилиты LibreHardwareMonitor и провести эксперимент на других ОС (пока что код Power_monitor.py написан под Windows). Но это если к данной теме будет интерес со стороны существенно большего числа людей, чем один отказывающийся стареть душой гик.

В общем, буду признателен за обратную связь - как тут, так и в сообществе DIY-энтузиастов.

Теги:
Хабы:
Рейтинг0
Комментарии0

Публикации

Работа

Ближайшие события