Как использовать Turi Create для распознавания действий человека

машинное обучение
Как использовать Turi Create для распознавания действий человека

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

Он собирает последовательные данные с помощью датчиков движения, извлекает функции и вводит их в предопределенную модель прогнозирования для определения соответствующей задачи действия. Таких датчиков много, например, акселерометры, гироскопы и так далее. Существует также множество соответствующих приложений, таких как использование данных акселерометра, встроенных в часы, для расчета количества плавательных кругов, использование данных гироскопа в мобильном телефоне для распознавания жестов и включение управляемой через Bluetooth подсветки для определенных жестов или использование пользовательских жестов для создания ярлыка и многое другое. Классификатор действий в Turi Create создает модель глубокого обучения, способную обнаруживать временные особенности в данных датчиков, что хорошо подходит для задачи классификации действий. Прежде чем мы углубимся в архитектуру модели, давайте рассмотрим рабочий пример.

Образец введения

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

Данные датчиков можно собирать на разных частотах. В наборе данных HAPT датчики собираются с частотой дискретизации 50 Гц, что означает 50 точек в секунду. Но в большинстве приложений вывод прогноза будет отображаться через более длительные интервалы времени, поэтому мы передадим параметрprediction_windowдля управления выводом скорости предсказания. Например, если бы мы хотели производить прогнозы каждые 5 секунд, а выборки датчика происходили с частотой 50 Гц, мы бы установили окно прогноза на 250 (5 секунд * 50 выборок в секунду).

Вот пример 3-секундных данных «прогулки» для одного сеанса в наборе данных HAPT:

Вот пример 3-секундных «постоянных» данных для одного сеанса в наборе данных HAPT:

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

предварительная обработка данных

В этой части мы будемHAPT-экспериментДанные преобразуются в формат SFram, ожидаемый классификатором действий Turi Create.

Во-первых, вам нужно загрузить файл данных в формате zip для набора данных, вы можете нажатьздесьСкачать. В приведенном ниже коде мы предполагаем, что данные в формате zip распаковываются вHAPT Data Setв папке. Папка содержит файлы трех типов: файлы, содержащие действия, выполняемые каждым экспериментом, файлы, содержащие собранные образцы акселерометра, и файлы образцов, собирающие данные гироскопа.

Среди них файлlabels.txtСодержит действия, выполненные для каждого эксперимента. Каждая активная метка определяется индексом образца. Например, в эксперименте 1 испытуемый выполнял 5-е действие между образцами, собранными в 250-й раз, и образцами, собранными в 1232-й раз. Активные метки кодируются числами от 1 до 6. Мы преобразуем эти числа в строки в конце этого раздела. Сначала загружаемlabels.txtcontent, преобразовать его в SFrame и определить функцию для поиска метки, соответствующей заданному выборочному индексу.

# import Turi Create
import turicreate as tc

# define data directory (you need use yours directory path)
data_dir = '../HAPT Data Set/RawData/'

# define find label for containing interval
def find_label_for_containing_interval(intervals, index):
    containing_interval = intervals[:, 0][(intervals[:, 1] <= index) & (index <= intervals[:, 2])]
    if len(containing_interval) == 1:
        return containing_interval[0]

# load labels
labels = tc.SFrame.read_csv(data_dir + 'labels.txt', delimiter=' ', header=False, verbose=False)
# rename CSV header
labels = labels.rename({'X1': 'exp_id', 'X2': 'user_id', 'X3': 'activity_id', 'X4': 'start', 'X5': 'end'})
print labels

Если он работает нормально, вывод будет следующим:

+--------+---------+-------------+-------+------+
| exp_id | user_id | activity_id | start | end  |
+--------+---------+-------------+-------+------+
|   1    |    1    |      5      |  250  | 1232 |
|   1    |    1    |      7      |  1233 | 1392 |
|   1    |    1    |      4      |  1393 | 2194 |
|   1    |    1    |      8      |  2195 | 2359 |
|   1    |    1    |      5      |  2360 | 3374 |
|   1    |    1    |      11     |  3375 | 3662 |
|   1    |    1    |      6      |  3663 | 4538 |
|   1    |    1    |      10     |  4539 | 4735 |
|   1    |    1    |      4      |  4736 | 5667 |
|   1    |    1    |      9      |  5668 | 5859 |
+--------+---------+-------------+-------+------+
[1214 rows x 5 columns]
Note: Only the head of the SFrame is printed.
You can use print_rows(num_rows=m, num_columns=n) to print more rows and columns.

Далее нам нужно получить данные акселерометра и гироскопа из экспериментальных данных. Для каждого эксперимента данные каждого датчика хранятся в отдельном файле. Далее мы загрузим данные акселерометра и гироскопа из всех экспериментов в один SFrame. При загрузке собранных сэмплов мы также используем ранее заданныйfind_label_for_containing_intervalФункция вычисляет метку для каждого образца. Последний SFrame содержит столбец с именем exp_id для идентификации каждого уникального сеанса.

from glob import  glob

acc_files = glob(data_dir + 'acc_*.txt')
gyro_files = glob(data_dir + 'gyro_*.txt')

# load datas
data = tc.SFrame()
files = zip(sorted(acc_files), sorted(gyro_files))
for acc_file, gyro_file in files:
    exp_id = int(acc_file.split('_')[1][-2:]) 

    # load accel data
    sf = tc.SFrame.read_csv(acc_file, delimiter=' ', header=False, verbose=False)
    sf = sf.rename({'X1': 'acc_x', 'X2': 'acc_y', 'X3': 'acc_z'})
    sf['exp_id'] = exp_id 

    # load gyro data
    gyro_sf = tc.SFrame.read_csv(gyro_file, delimiter=' ', header=False, verbose=False)
    gyro_sf = gyro_sf.rename({'X1': 'gyro_x', 'X2': 'gyro_y', 'X3': 'gyro_z'})
    sf = sf.add_columns(gyro_sf)

    # calc labels
    exp_labels = labels[labels['exp_id'] == exp_id][['activity_id', 'start', 'end']].to_numpy()
    sf = sf.add_row_number()
    sf['activity_id'] = sf['id'].apply(lambda x: find_label_for_containing_interval(exp_labels, x))
    sf = sf.remove_columns(['id'])

    data = data.append(sf)
+----------------+------------------+----------------+--------+---------+
|     acc_x      |      acc_y       |     acc_z      | exp_id | user_id |
+----------------+------------------+----------------+--------+---------+
| 0.918055589877 | -0.112499999424  | 0.509722251429 |   1    |    1    |
| 0.91111113046  | -0.0930555616826 | 0.537500040471 |   1    |    1    |
| 0.88194449816  | -0.0861111144223 | 0.513888927079 |   1    |    1    |
| 0.88194449816  | -0.0861111144223 | 0.513888927079 |   1    |    1    |
| 0.879166714393 | -0.100000002865  | 0.50555557578  |   1    |    1    |
| 0.888888957576 |  -0.10555556432  | 0.512500035196 |   1    |    1    |
| 0.862500011794 | -0.101388894748  | 0.509722251429 |   1    |    1    |
| 0.861111119911 | -0.104166672437  | 0.50138890013  |   1    |    1    |
| 0.854166660495 |  -0.10833333593  | 0.527777797288 |   1    |    1    |
| 0.851388876728 | -0.101388894748  | 0.552777802563 |   1    |    1    |
+----------------+------------------+----------------+--------+---------+
+------------------+------------------+------------------+-------------+
|      gyro_x      |      gyro_y      |      gyro_z      | activity_id |
+------------------+------------------+------------------+-------------+
| -0.0549778714776 | -0.0696386396885 | -0.0308486949652 |     None    |
| -0.0125227374956 | 0.0192422550172  | -0.0384845100343 |     None    |
| -0.0235183127224 |  0.276416510344  | 0.00641408516094 |     None    |
| -0.0934623852372 |  0.367740869522  | 0.00122173049022 |     None    |
| -0.124311074615  |  0.476780325174  | -0.0229074470699 |     None    |
| -0.100487336516  |  0.519846320152  | -0.0675006061792 |     None    |
| -0.149356558919  |  0.481056392193  | -0.0925460830331 |     None    |
| -0.211053937674  |  0.389121174812  |  -0.07483099401  |     None    |
| -0.222354948521  |  0.267864406109  | -0.0519235469401 |     None    |
| -0.173791155219  |  0.207083314657  | -0.0320704244077 |     None    |
+------------------+------------------+------------------+-------------+
[1122772 rows x 9 columns]

Наконец, мы форматируем номера меток в более интуитивно понятную строковую форму и сохраняем обработанные данные в SFrame следующим образом:


target_map = {
    1.: 'walking',
    2.: 'climbing_upstairs',
    3.: 'climbing_downstairs',
    4.: 'sitting',
    5.: 'standing',
    6.: 'laying'
}

# Use the same labels used in the experiment
data = data.filter_by(target_map.keys(), 'activity_id')
data['activity'] = data['activity_id'].apply(lambda x: target_map[x])
data  = data.remove_column('activity_id')

data.save('hapt_data.sframe')
+---------------+-----------------+-----------------+--------+---------+
|     acc_x     |      acc_y      |      acc_z      | exp_id | user_id |
+---------------+-----------------+-----------------+--------+---------+
| 1.02083339474 | -0.125000002062 |  0.10555556432  |   1    |    1    |
| 1.02500007039 | -0.125000002062 |  0.101388894748 |   1    |    1    |
| 1.02083339474 | -0.125000002062 |  0.104166672437 |   1    |    1    |
| 1.01666671909 | -0.125000002062 |  0.10833333593  |   1    |    1    |
| 1.01805561098 | -0.127777785828 |  0.10833333593  |   1    |    1    |
| 1.01805561098 | -0.129166665555 |  0.104166672437 |   1    |    1    |
| 1.01944450286 | -0.125000002062 |  0.101388894748 |   1    |    1    |
| 1.01666671909 | -0.123611110178 | 0.0972222251764 |   1    |    1    |
| 1.02083339474 | -0.127777785828 | 0.0986111170596 |   1    |    1    |
| 1.01944450286 | -0.115277783191 | 0.0944444474879 |   1    |    1    |
+---------------+-----------------+-----------------+--------+---------+
+--------------------+-------------------+-------------------+----------+
|       gyro_x       |       gyro_y      |       gyro_z      | activity |
+--------------------+-------------------+-------------------+----------+
| -0.00274889357388  | -0.00427605677396 |  0.00274889357388 | standing |
| -0.000305432622554 | -0.00213802838698 |  0.00610865233466 | standing |
|  0.0122173046693   | 0.000916297896765 | -0.00733038317412 | standing |
|  0.0113010071218   | -0.00183259579353 | -0.00641408516094 | standing |
|  0.0109955742955   | -0.00152716308367 | -0.00488692196086 | standing |
|  0.00916297826916  | -0.00305432616733 |   0.010079276748  | standing |
|   0.010079276748   | -0.00366519158706 | 0.000305432622554 | standing |
|  0.0137444678694   |  -0.0149661982432 |  0.00427605677396 | standing |
|  0.00977384392172  | -0.00641408516094 | 0.000305432622554 | standing |
|  0.0164933614433   |  0.00366519158706 |  0.00335975876078 | standing |
+--------------------+-------------------+-------------------+----------+
[748406 rows x 9 columns]

На этом предобработка данных закончилась, но есть проблема, почему мы так обрабатываем данные? Далее рассмотрим его подробно.

Введение в теорию предварительной обработки данных

В этом разделе мы представим формат входных данных классификатора деятельности и различные доступные выходные форматы.

формат входных данных

Классификаторы активности создаются на основе данных от разных датчиков, собираемых с определенной частотой в течение определенного периода времени. **В классификаторе активности Turi Create все датчики замеряются с одинаковой частотой. **Например, в эксперименте HAPT данные содержат трехосное ускорение и трехосный гироскоп, и в каждый момент времени генерируется 6 значений (признаков). Частота сбора выборки каждого датчика составляет 50 Гц, то есть в секунду собирается 50 точек выборки. На рисунке ниже показаны данные 3-секундной ходьбы, полученные от одного субъекта в эксперименте HAPT:

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

Как правило, приложения, использующие классификаторы активности, будут предоставлять пользователям более медленные прогнозы, чем частота дискретизации датчика, основанная на различных действиях. Например, шагомеру может потребоваться делать прогнозы каждую секунду, в то время как для обнаружения сна он может делать прогнозы только каждую минуту. При построении модели важно предоставить те же метки, что и ожидаемая частота прогнозирования, количество отсчетов датчика, связанных с одной меткой, называетсяокно предсказания. Классификаторы активности используют окно предсказания для определения скорости предсказания, т. е. делают предсказания после каждой выборки окна предсказания. Для набора данных HAPT мы используемprediction_windowравно 50, что дает одно предсказание в секунду, когда датчик производит выборку с частотой 50 Гц.

Каждый набор последовательных образцов, полученных из одной записи об испытуемом, называетсябеседа. Пример, когда сеанс может содержать несколько действий, сеанс не обязательно должен содержать все действия или иметь одинаковую длину. Входные данные для классификатора активности должны содержать данные, ориентированные на столбцы, чтобы однозначно назначать каждую выборку сеансу. Классификатор действий в Turi Create ожидает выборки данных, связанных с каждым идентификатором сеанса и отсортированных в порядке возрастания времени.

Вот пример входного формата SFrame, ожидаемого классификатором действий после предварительной обработки набора данных HAPT. Пример содержит 2 сеанса сexp_idЧтобы различать, в этом примере первая сессия содержит только образцы ходьбы, а вторая сессия содержит образцы стояния и сидения.

+--------+----------+----------+-----------+----------+-----------+-----------+-----------+
| exp_id | activity |  acc_x   |   acc_y   |  acc_z   |   gyro_x  |   gyro_y  |   gyro_z  |
+--------+----------+----------+-----------+----------+-----------+-----------+-----------+
|   1    | walking  | 0.708333 | -0.197222 | 0.095833 | -0.751059 |  0.345444 |  0.038179 |
|   1    | walking  | 0.756944 | -0.173611 | 0.169444 | -0.545503 |  0.218995 |  0.046426 |
|   1    | walking  | 0.902778 | -0.169444 | 0.147222 | -0.465785 |  0.440128 | -0.045815 |
|   1    | walking  | 0.970833 | -0.183333 | 0.118056 | -0.357662 |  0.503964 | -0.206472 |
|   1    | walking  | 0.972222 | -0.176389 | 0.166667 | -0.312763 |  0.64263  | -0.309709 |
|   2    | standing | 1.036111 | -0.290278 | 0.130556 |  0.039095 | -0.021075 |  0.034208 |
|   2    | standing | 1.047222 | -0.252778 |   0.15   |  0.135612 |  0.015272 | -0.045815 |
|   2    | standing |  1.0375  | -0.209722 | 0.152778 |  0.171042 |  0.009468 | -0.094073 |
|   2    | standing | 1.026389 |  -0.1875  | 0.148611 |  0.210138 | -0.039706 | -0.094073 |
|   2    | sitting  | 1.013889 | -0.065278 | 0.127778 | -0.020464 | -0.142332 |  0.091324 |
|   2    | sitting  | 1.005556 | -0.058333 | 0.127778 | -0.059254 | -0.138972 |  0.055589 |
|   2    | sitting  |   1.0    | -0.070833 | 0.147222 | -0.058948 | -0.124922 |  0.026878 |
+--------+----------+----------+-----------+----------+-----------+-----------+-----------+
[12 rows x 8 columns]

В этом примере, еслиprediction_windowЕсли установлено значение 2, то никакие две строки данных в сеансе не будут использоваться в качестве входных данных для прогноза.Когда сеанс завершится, прогноз также будет сгенерирован, если количество строк данных в окне прогноза меньше, чем количество строк в окно предсказания. Окно предсказания 2 приведет кexp_id 13 прогноза на и наexp_id 24 прогноза, а окно прогноза 5 будет дляexp_id 1создать единый прогноз и цельexp_id 2Выдает 2 прогноза.

прогнозируемая частота

Как упоминалось ранее, частота предсказания классификатора активности является параметром окна предсказания.prediction_windowопределенный. Таким образом, каждая строка окна предсказания в сеансе создает предсказание. Для приведенного выше набора данных HAPT установка ловушки прогнозирования на 50 означает, что каждые 50 выборок производят прогноз.

model.predict(walking_3_sec, output_frequency='per_window')
+---------------+--------+---------+
| prediction_id | exp_id |  class  |
+---------------+--------+---------+
|       0       |   1    | walking |
|       1       |   1    | walking |
|       2       |   1    | walking |
+---------------+--------+---------+
[3 rows x 3 columns]

Однако во многих рабочих процессах машинного обучения прогнозы одной модели обычно используются в качестве входных данных для дальнейшего анализа и моделирования. В этом случае может быть более выгодно возвращать прогноз для каждой строки входных данных. Мы можем сделать это, установив для параметра output_frequency значениеper_rowпопросить модель сделать это.

model.predict(walking_3_sec, output_frequency='per_row')
dtype: str
Rows: 150
['walking', 'walking', 'walking', 'walking', 'walking', 'walking', 'walking', 'walking', 'walking', 'walking', 'walking', 'walking', 'walking', 'walking', 'walking', 'walking', 'walking', 'walking', 'walking', 'walking', 'walking', 'walking', 'walking', 'walking', 'walking', 'walking', 'walking', 'walking', 'walking', 'walking', 'walking', 'walking', 'walking', 'walking', 'walking', 'walking', 'walking', 'walking', 'walking', 'walking', 'walking', 'walking', 'walking', 'walking', 'walking', 'walking', 'walking', 'walking', 'walking', 'walking', 'walking', 'walking', 'walking', 'walking', 'walking', 'walking', 'walking', 'walking', 'walking', 'walking', 'walking', 'walking', 'walking', 'walking', 'walking', 'walking', 'walking', 'walking', 'walking', 'walking', 'walking', 'walking', 'walking', 'walking', 'walking', 'walking', 'walking', 'walking', 'walking', 'walking', 'walking', 'walking', 'walking', 'walking', 'walking', 'walking', 'walking', 'walking', 'walking', 'walking', 'walking', 'walking', 'walking', 'walking', 'walking', 'walking', 'walking', 'walking', 'walking', 'walking', ... ]

Эти прогнозы производятся путем репликации каждого прогноза для каждого окна прогноза по всем образцам, связанным с этим окном.

обучение модели

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

import  turicreate as tc

# load sessions from preprocessed data
data = tc.SFrame('hapt_data.sframe')

# train/test split by recording sessions
train, test = tc.activity_classifier.util.random_split_by_session(data, session_id='exp_id', fraction=0.8)

# create an activity classifier
model = tc.activity_classifier.create(train, 
	session_id='exp_id', 
	target='activity', 
	prediction_window=50)

# evaluate the model and save result into dictionary
metrics = model.evaluate(test)
print (metrics['accuracy'])

Во время выполнения обучения будет следующий вывод журнала:

Pre-processing 575999 samples...
Using sequences of size 1000 for model creation.
Processed a total of 47 sessions.
Iteration: 0001
	Train loss    : 1.384639084 	Train accuracy: 0.423688752
Iteration: 0002
	Train loss    : 0.975227836 	Train accuracy: 0.604033018
Iteration: 0003
	Train loss    : 0.858876649 	Train accuracy: 0.658348667
Iteration: 0004
	Train loss    : 0.747760415 	Train accuracy: 0.696624932
Iteration: 0005
	Train loss    : 0.717178401 	Train accuracy: 0.710401664
Iteration: 0006
	Train loss    : 0.708376906 	Train accuracy: 0.720765597
Iteration: 0007
	Train loss    : 0.727093298 	Train accuracy: 0.712437319
Iteration: 0008
	Train loss    : 0.701619904 	Train accuracy: 0.730136608
Iteration: 0009
	Train loss    : 0.719597752 	Train accuracy: 0.713592718
Iteration: 0010
	Train loss    : 0.618533716 	Train accuracy: 0.766228394
Training complete
Total Time Spent: 12.3062s
0.804323490346

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

model = tc.activity_classifier.create(train, 
	session_id='exp_id', 
	target='activity', 
	prediction_window=50, 
	max_iterations=20)

Точность, полученная в это время, увеличится до: 0,835319045889. Поэтому также требуется настройка подходящего количества итераций.

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

# save the model for later use in Turi Create
model.save('mymodel.model')

Кроме того, вы также можете напрямую экспортировать в формат файла модели, поддерживаемый Core ML, следующим образом:

# export for use in Core ML
model.export_coreml('MyActivityClassifier.mlmodel')

Так как мы создали модель с частотой дискретизации 50 Гц и установили для Prediction_window значение 50, мы будем получать один прогноз в секунду. Затем давайте проверим это, используя данные о 3-секундной ходьбе, приведенные в начале статьи:

# load saved model
activityClassifier = tc.load_model('mymodel.model')

# load sessions from preprocessed data
data = tc.SFrame('hapt_data.sframe')

# filter the walking data in 3 sec
walking_3_sec = data[(data['activity'] == 'walking') & (data['exp_id'] == 1)][1000:1150]

# do predict
predicts = activityClassifier.predict(walking_3_sec, output_frequency='per_window')
print predicts
+---------------+--------+---------+
| prediction_id | exp_id |  class  |
+---------------+--------+---------+
|       0       |   1    | walking |
|       1       |   1    | walking |
|       2       |   1    | walking |
+---------------+--------+---------+
[3 rows x 3 columns]

Теперь, когда мы увидели, как быстро создать классификатор активности с использованием данных датчиков, давайте посмотрим, как использовать этот классификатор активности в iOS с помощью Core ML.

Развертывание в Core ML

В предыдущем разделе мы экспортировали обученную модель в формат файла, поддерживаемый Core ML.mlmodelформат. Core ML — это фреймворк для быстрой интеграции моделей машинного обучения и использования на платформе iOS. Он прост и быстр в использовании. Мы будем использовать язык Swift для написания всего кода развертывания интеграции.

Сначала создайте пустой проект и укажите язык для использования Swift. импортировать предыдущий шагMyActivityClassifier.mlmodelфайл в проект Xcode, Xcode автоматически сгенерирует некоторые связанные коды API и описания параметров:

Для получения дополнительной информации об этом см.Официальная документация Core ML.

Как видно из рисунка выше, взаимодействие данных всей модели разделено на две части, одна часть — это вход (inputs), а другая часть — output (outputs):

ввод модели

  • **features:** массив признаков, длина которыхprediction_window, а ширина — это количество признаков. Он содержит показания датчиков, которые были агрегированы.
  • **hiddenIn: **Входное состояние рекуррентного слоя LSTM в модели. Он инициализируется 0 при запуске нового сеанса, в противном случае следует использовать предыдущий прогноз.hiddenOutвходить.
  • **cellIn: **Входное состояние нейронов в рекуррентном слое LSTM в модели. Он инициализируется 0 при запуске нового сеанса, в противном случае следует использовать предыдущий прогноз.cellOutвходить.

вывод модели

  • **ActivityProbability: **Словарь вероятностей. Ключ — это каждый тип метки, то есть вид деятельности, а значение — вероятность принадлежности к категории, а диапазон его значений — [0,0, 1,0].
  • **активность:** строка, представляющая результат прогноза. Это значение соответствует категории активности с наибольшей вероятностью в *activityProbability:*.
  • **hiddenOut: **Выходное состояние рекуррентного слоя LSTM в модели. Этот вывод должен быть сохранен и загружен в модель.hiddenInсередина.
  • **cellOut: **Выходное состояние нейронов рекуррентного слоя LSTM в модели. Этот вывод должен быть сохранен и загружен в модель.cellInсередина.

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

Применение моделей Core ML в приложении

Развертывание модели классификации действий в приложении iOS/watchOS включает 3 основных шага:

  1. Включите соответствующий датчик и установите его на желаемую частоту.
  2. Совокупность показаний датчиков вprediction_windowв длинных массивах.
  3. Когда массив массива заполнится, вызовите метод модели *prediction()*, чтобы получить прогнозируемую активность.

Создайте массив для суммирования ввода

Модель классификатора деятельности ожидает получить данные, которые содержат данные датчика и соответствуютprediction_windowмассив показаний.

Приложению необходимо агрегировать показания датчика в единый размер1 x prediction_window x number_of_featuresизMLMultiArray.

Кроме того, приложению также необходимо сохранять последниеhiddenOutиcellOutвывод для ввода в модель в следующем прогнозе.

Во-первых, мы определяем структуру для установки соответствующих параметров числового типа:

struct ModelConstants {
    static let numOfFeatures = 6
    static let predictionWindowSize = 50
    static let sensorsUpdateInterval = 1.0 / 50.0
    static let hiddenInLength = 200
    static let hiddenCellInLength = 200
}

После этого инициализируем объект модели:

let activityClassificationModel = MyActivityClassifier()

Нам также нужно инициализировать некоторые переменные, включая массив данных, текущий размер окна и, наконец,hiddenOutиcellOutВыходная переменная:

var currentIndexInPredictionWindow = 0
let predictionWindowDataArray = try? MLMultiArray(
       shape: [1, ModelConstants.predictionWindowSize, ModelConstants.numOfFeatures] as [NSNumber],
       dataType: MLMultiArrayDataType.double)
var lastHiddenOutput = try? MLMultiArray(
       shape: [ModelConstants.hiddenInLength as NSNumber],
       dataType: MLMultiArrayDataType.double)
var lastHiddenCellOutput = try? MLMultiArray(
       shape: [ModelConstants.hiddenCellInLength as NSNumber],
       dataType: MLMultiArrayDataType.double)

Включить датчик CoreMotion

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

Для получения дополнительной информации о датчиках CoreMotion см.Документация CoreMotion.

let motionManager: CMMotionManager? = CMMotionManager()

func startMotionSensor() {
    guard let motionManager = motionManager, motionManager.isAccelerometerAvailable && motionManager.isGyroAvailable else { return }
    motionManager.accelerometerUpdateInterval = TimeInterval(ModelConstants.sensorsUpdateInterval)
    motionManager.gyroUpdateInterval = TimeInterval(ModelConstants.sensorsUpdateInterval)
        
    // Accelerometer sensor
    motionManager.startAccelerometerUpdates(to: .main) { (accelerometerData, error) in
        guard let accelerometerData = accelerometerData else {return}
            
        // add the current acc data sample to the data array
        
    }
    // Gyro sensor
    motionManager.startGyroUpdates(to: .main) { (gyroData, error) in
        guard let gyroData = gyroData else { return }
            
        // add the current gyro data sample to the data array
    }
}

Суммарные показания датчиков

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

Всякий раз, когда от датчика будет получено новое показание, мы добавим это показание к нашемуprediction_windowв длинном массиве данных.

Когда массив достигает ожидаемого размера, приложение может использовать массив и вызывать модель для прогнозирования новых действий.

func addAccelerometerSampleToDataArray(accelerometerSample: CMAccelerometerData) {
    guard let dataArray = predictionWindowDataArray  else {
        return
    }
        
    dataArray[[0, currentIndexInPredictionWindow, 0] as [NSNumber]] = accelerometerSample.acceleration.x as NSNumber
    dataArray[[0, currentIndexInPredictionWindow, 1] as [NSNumber]] = accelerometerSample.acceleration.y as NSNumber
    dataArray[[0, currentIndexInPredictionWindow, 2] as [NSNumber]] = accelerometerSample.acceleration.z as NSNumber
        
    // update the index in the prediction window data array
    currentIndexInPredictionWindow += 1
        
    // If the data array is full, call the prediction method to get a new model prediction.
    // We assume here for simplicity that the Gyro data was added to the data array as well.
    if (currentIndexInPredictionWindow == ModelConstants.predictionWindowSize) {
        // predict activity
        let predictedActivity = performModelPrediction() ?? "N/A"
            
        // user the predicted activity here
            
       // start a new prediction window
        currentIndexInPredictionWindow = 0
    }
}

Данные гироскопа те же, и его здесь больше нет.

делать предсказания

когдаprediction_windowКак только показания в модели агрегированы, интерфейс прогнозирования модели может быть вызван для прогнозирования последней активности пользователя.

func performModelPrediction () -> String?{
     guard let dataArray = predictionWindowDataArray else { return "Error!"}
        
     // perform model prediction
     let modelPrediction = try? activityClassificationModel.prediction(features: dataArray, hiddenIn: lastHiddenOutput, cellIn: lastHiddenCellOutput)
        
     // update the state vectors
     lastHiddenOutput = modelPrediction?.hiddenOut
     lastHiddenCellOutput = modelPrediction?.cellOut
        
     // return the predicted activity -- the activity with the highest probability
     return modelPrediction?.activity
}

Окончательный результат запуска выглядит следующим образом:

Этот результат показан только в примере кода, и его правильность не гарантируется.

Суммировать

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

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

использованная литература