如何使用Turi Create进行人类行为识别

行为识别也可称为活动分类器,是模式识别技术的一种,是特征提取和行为分类的结合。python

它经过运动传感器收集序列性数据,进行特征提取并输入到预约义的预测模型中,识别出其所对应的动做的任务。此类传感器有不少,例如加速度计、陀螺仪等等。而对应的应用也不少,例如使用集成在手表中的加速度计数据来计算游泳的圈数,使用手机中的陀螺仪数据识别手势,并在特定手势时打开蓝牙控制的灯光,或者使用自定义的手势来为建立一个快捷方式等等。Turi Create 中的活动分类器建立了一个深度学习模型,可以检测传感器数据中的时间特征,可以很好地适应活动分类的任务。在咱们深刻模型架构以前,让咱们看一个可行的例子。git

示例简介

在这个例子中,咱们将使用手持设备的加速度计和陀螺仪数据建立一个活动分类模型,以识别用户的物理活动行为。这里咱们将使用公开的数据集 HAPT 实验的相关数据,这些数据中包含多个用户的会话记录,每一个用户执行特定的身体活动。执行的身体活动包括:步行、上楼梯、下楼梯、坐、站和躺。github

传感器的数据能够以不一样的频率进行收集。在HAPT数据集中,传感器以 50Hz 的采样频率进行的采集,也就是说每秒钟会采集50个点。可是在大部分的应用程序中,都会以更长的时间间隔来展现预测输出,所以咱们会经过参数 prediction_window 来控制预测率的输出。例如,若是咱们但愿每5秒钟产生一次预测,而且传感器以50Hz进行采样,则咱们将预测窗口设置为250(5秒 * 每秒50个采样)。swift

如下是HAPT数据集​​中单个会话3秒的 “步行” 数据的示例:数组

如下是HAPT数据集​​中单个会话3秒的 “站立” 数据的示例:bash

活动分类器的初级目标是区分这些数据样本,可是在开始以前,咱们须要对这些数据进行预处理,以获取数据的SFrame结构体,做为Turi Create活动分类器的输入。markdown

数据预处理

在这部分,咱们将会对 HAPT 实验 的数据转换为Turi Create活动分类器所指望的SFrame格式。session

首先,须要下载数据集的zip格式数据文件,你能够点击 这里 直接下载。在下面的代码中,咱们假定zip格式的数据被解压缩到了 HAPT Data Set 的文件夹中。文件夹中包含三种类型的文件 --- 包含每一个实验所执行的活动的文件、包含收集的加速度计样本的文件和收集陀螺仪数据的样本文件。架构

其中,文件 labels.txt 包含为每一个实验所执行的活动。每一个活动标签是经过样本的索引指定的。例如,在实验1中,受试者在第250次收集的样品和第1232次收集的样品之间进行了第5次活动。活动标签被编码为 1 到 6 的数字。咱们将在本节最后转换这些数字为字符串。首先,咱们加载 labels.txt 内容,转换到SFrame中,并定义一个函数来查找给定样本索引所对应的标签。app

# 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个值(特征)。每一个传感器的样本收集频率为50Hz,也就是每秒钟收集50个样本点。下图显示了HAPT实验中从单个受试者收集的3秒步行数据:

而传感器的采样频率取决于被分类的活动和各类实际状况的限制。例如,尝试检测极小的活动(好比手指抖动),则可能须要较高的采样频率,而较低的频率则可能须要检测那些比较粗糙的活动(好比游泳),更进一步来讲,还要考虑设备的电量问题和模型的构建时长问题等。高频率的采样行为,则须要更多传感器和其数据捕获,这会致使更高的电量消耗和更大的数据量,增长了模型的复杂性和建立时长等。

通常状况下,使用活动分类器的应用程序都会根据不一样的活动来为用户提供比传感器采样率更慢的预测。例如,计步器可能须要每秒钟进行一次预测,而为了检测睡眠,可能每分钟才进行一次预测。在构建模型的时候,重要的是要提供和指望的预测速率相同的标签,与单个标签关联的传感器样本的数量被称之为预测窗口。活动分类器就是使用预测窗口来肯定预测速率,即在每一个预测窗口样本以后进行预测。对于HAPT数据集来讲,咱们使用的prediction_window是50,当传感器以50Hz的频率采样时,每秒产生一个预测。

从对象的单个记录产生的每组连续的样本称为会话。一个会话能够包含多个活动的示例,会话并不须要包含全部活动或者具备相同的长度。活动分类器的输入数据必须包含一个列向数据,以便将每一个样本惟一地分配给一个会话。Turi Create中的活动分类器指望与每一个会话id的数据样本关联并按照时间升序排序。

一下是HAPT数据集通过预处理后,获得的活动分类器所指望的输入SFrame格式示例。该示例包含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 1 的 3 个预测和针对 exp_id 2 的 4 个预测,而预测窗口 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')
复制代码

因为咱们已经建立了采样频率为50Hz的模型,并将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),另外一部分为输出(outputs):

模型输入

  • **features:**特征数组,其长度为prediction_window,宽度为特征的数量。其中包含传感器的读数,这些读数已经进行了汇总。
  • **hiddenIn:**模型中LSTM recurrent层的输入状态。当开始一个新的会话是初始化为0,不然应该用前一个预测的hiddenOut进行输入。
  • **cellIn:**模型中LSTM recurrent层的神经元输入状态。当开始一个新的会话是初始化为0,不然应该用前一个预测的cellOut进行输入。

模型输出

  • **activityProbability:**几率字典。其中key为每种标签,也就是活动类型,value为属于该类别的几率,其值范围是[0.0,1.0]。
  • **activity:**表明预测结果的字符串。该值和*activityProbability:*中几率最高的那种活动类别相对应。
  • **hiddenOut:**模型中LSTM recurrent层的输出状态。这个输出应该保存下来,并在下次预测调用时输入到模型的hiddenIn中。
  • **cellOut:**模型中LSTM recurrent层的神经元输出状态。这个输出应该保存下来,并在下次预测调用时输入到模型的cellIn中。

关于模型详细的结构信息以及是如何工做的,能够参考若是工做的?

在应用程序中应用Core ML模型

在iOS/watchOS应用中部署活动分类模型涉及3个基本步骤:

  1. 启用相关传感器,并将其设置为所需的频率。
  2. 未来自传感器的读数汇总到一个prediction_window长阵列中。
  3. 当数组阵列变满时,调用模型的*prediction()*方法来得到预测的活动。

建立用于汇总输入的数组

活动分类器模型指望接收的数据是包含传感器数据并符合prediction_window读数的数组。

应用程序须要将传感器的读数汇合成一个尺寸为 1 x prediction_window x number_of_featuresMLMultiArray

另外,应用程序还须要保存每层最后的hiddenOutcellOut输出,以便在下一次预测中输入到模型中。

首先咱们定义个结构体,用来设定相关的数值类型参数:

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()
复制代码

咱们还须要初始化一些变量,包括数据数组、当前窗口大小、最后hiddenOutcellOut输出变量:

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机器学习框架,使用上简洁了不少,功能上也基本知足当下的一些机器学习任务,但愿开源的Turi Create可以在社区中茁壮成长,更加完善。

参考资料

相关文章
相关标签/搜索