如何使用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可以在社區中茁壯成長,更加完善。

參考資料

相關文章
相關標籤/搜索