行爲識別也可稱爲活動分類器,是模式識別技術的一種,是特徵提取和行爲分類的結合。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所支持的文件格式mlmodel格式了。而Core ML是iOS平臺上進行快速機器學習模型集成和使用的框架,使用簡單並且快速,咱們將使用Swift語言編寫整個集成部署代碼。
首先,建立一個空的工程項目,並制定語言使用Swift。導入上一步的MyActivityClassifier.mlmodel文件到Xcode項目,Xcode會自動生成一些相關的API代碼和參數說明:
更多此方面的信息,可參考Core ML 官方文檔。
從上圖中能夠看到,整個模型的數據交互分爲兩大部分,一部分爲輸入(inputs),另外一部分爲輸出(outputs):
關於模型詳細的結構信息以及是如何工做的,能夠參考若是工做的?。
在iOS/watchOS應用中部署活動分類模型涉及3個基本步驟:
活動分類器模型指望接收的數據是包含傳感器數據並符合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文檔。
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可以在社區中茁壯成長,更加完善。