2023年4月19日水曜日

機械学習でRFIDの向きを予測

過去にRFIDシールを指に貼りつけてデバイスを操作するという研究がありました。

半分に分けてつがいになったタグを指のうえで重ねて、出来上がったタグで複数のパターンを表現しています。

あとは完成したタグを読み取るだけでよいので確度の高い操作ができますが、機械学習でRSSIを学習させれば、特殊なタグを用いずともタグの状態を判別させていくつかのパターンを作れるのではないかと思いました。

そしてこんなものを用意してみました。



段ボール片の裏表に3枚ずつ、計6枚のタグを貼りつけました。
読み取らせるタグの数は多いほうが機械学習的によいかなと思いました。ただ、手持ちのリーダーの電波出力の制約上、6枚が限界みたいです。

今回は、とりあえずこれをリーダーに対して垂直、水平にかざして、それを分類するのを目標にしました。



はじめに、こんな感じで2,30分くらい読み取らせてデータセットを作ります。

このデータセットをTensorFlowで学習させます。
以下がコードです。

import tensorflow as tf
from tensorflow.keras.models import Sequential
from tensorflow.keras.layers import Dense
from tensorflow.keras.optimizers import Adam
import numpy as np
import pandas as pd
from sklearn.model_selection import train_test_split

#データセットの読み込み・numpy配列に変換
h = (np.genfromtxt('data/h.csv', delimiter=',')).reshape(10000, 6, 2)
v = (np.genfromtxt('data/v.csv', delimiter=',')).reshape(10000, 6, 2)

#ラベルの作成・結合
h_labels = np.ones((10000, 1, 2))
v_labels = np.zeros((10000, 1, 2))
h = np.concatenate([h, h_labels], axis=1)
v = np.concatenate([v, v_labels], axis=1)
data = np.vstack((h, v))

#一応シャッフル
np.random.shuffle(data)

#訓練用とテスト用に分割
train, test = train_test_split(data, test_size=0.2)

#ターゲットデータとラベルの指定
train_x = train[:, :6, :]
train_y = train[:, -1, 1]
train_y = train_y.reshape(train_y.shape[0], 1)

test_x = test[:, :6, :]
test_y = test[:, -1, 1]
test_y = test_y.reshape(test_y.shape[0], 1)

#モデルの作成
model = tf.keras.Sequential([
    tf.keras.layers.Dense(32, activation='relu',
    kernel_initializer=tf.keras.initializers.HeNormal(),
    input_shape=(6, 2)),
    tf.keras.layers.Dense(64, activation='relu'),
    tf.keras.layers.Dense(1, activation='sigmoid')
])

model.compile(optimizer=Adam(learning_rate=0.001),
              loss='binary_crossentropy',
              metrics=['accuracy'])

model.fit(train_x, train_y, epochs=10, batch_size=32)

#モデルの評価
loss, accuracy = model.evaluate(test_x, test_y, verbose=0)

print('loss:', loss, 'accuracy:', accuracy)

#実際に水平・垂直のデータを予測できているかテスト
input1 = (np.genfromtxt('data/hpredict.csv', delimiter=',')).reshape(16, 6, 2)
input2 = (np.genfromtxt('data/vpredict.csv', delimiter=',')).reshape(16, 6, 2)

prediction1 = model.predict(input1)
prediction2 = model.predict(input2)

print(prediction1)
print(prediction2)

評価は以下のようになりました。

Epoch 1/10
500/500 [==============================] - 2s 2ms/step - loss: 0.6687 - accuracy: 0.5763
(省略)
Epoch 10/10
500/500 [==============================] - 1s 2ms/step - loss: 0.3698 - accuracy: 0.8954
loss: 0.3550117611885071 accuracy: 0.8889164924621582

ニューロンの数やoptimizerをいろいろ変えたりして、正確性が89%まで出るようになりました。

実際にいくつかのデータを予測させると以下のようになりました。

水平のデータ
[[0.9883703 ]
  [0.9930196 ]
  [0.69164467]
  [0.8856425 ]
  [0.869069  ]
  [0.95285755]]

 [[0.6108472 ]
  [0.9883703 ]
  [0.83953756]
  [0.9930196 ]
  [0.8858758 ]
  [0.95285755]]

 [[0.83953756]
  [0.9883703 ]
  [0.9970184 ]
  [0.69164467]
  [0.8858758 ]
  [0.9757535 ]]

垂直のデータ
[[6.9464278e-01]
  [4.5149717e-03]
  [2.5530994e-01]
  [1.7064719e-02]
  [4.1041663e-01]
  [5.3049397e-01]]

 [[1.3248680e-02]
  [6.1752790e-01]
  [2.3598520e-01]
  [6.9464278e-01]
  [3.9535922e-01]
  [1.7615510e-02]]

 [[2.5456597e-03]
  [3.9535922e-01]
  [1.7038897e-02]
  [6.9464278e-01]
  [4.3180758e-01]
  [2.5530994e-01]]

水平データに0、垂直データに1のラベリングをしました。
正規化ができていないのでわかりづらいですが、水平と垂直で明らかに値が異なっているように見えます。成功でしょうか。そうであってほしいです・・・

ターゲットデータの形が6x2で、ラベルの形もそれに合わせたのでひとつのデータに対してひとつの予測が出てほしかったのですが、なぜか6個出てしまっています。まだまだ改善の余地があるので今後も勉強していきたいです。