RFIDを使ったシステムの特徴の1つはデータがしこたま溜まるということですね。リーダが読み続ける限りアンテナの近くにあるタグは読まれ続け、データが通知され続けるわけです。
となると、
RFID → データいっぱい → ビッグデータ! → ディープラーニング!!
というのはきっと自然の流れですよね。たぶん。
そんなわけでRFIDリーダから得られたデータを使ってディープラーニングにトライしてみました。
リーダから得られるデータですが、基本的にはタグのIDとRSSI値と呼ばれる電波強度が得られます。このRSSI値に注目してやってみました。
ディープラーニングはいろんなツールが出ていますが何となくで Google の TensorFlow を使います。
チュートリアルが単純な順伝搬型のMNISTからはじまって、次がCNNを使ったディープMNISTと進んでいるのでまずはこれを参考にしてみます。
MNISTは手書きの数字の画像を対象としているのですが、こっちはRSSIしかない1次元ですのであんまりしっくりこないですが自然言語処理でも使われてるようなので気にしないでやってみます。
何を学習させるかですが、RSSIのみでとりあえずタグが動いているのか止まっているのかを判別させたいと思います。簡単そうに見えますが、これの難しいところはタグが止まっていても周囲の環境(人が通ったとか)でRSSIもぐぐっと変わるところです。このRSSIの変化をタグが動いているのか周辺のせいなのかを判別させたいとそういうことです。
で、RSSIは連続して得られますので、「過去32個のRSSIの値をみて動いているか止まっているか判別させる」という風にします。
学習用に大量のデータが必要になりますのでデータを溜めます。アンテナの上にタグを吊るしておきます。
止まった状態でデータを採り続ける
扇風機でタグを動かし続けてデータを採り続ける
これでそれぞれ10000個ほどのデータが採れました。
ここから TensorFlow を使ったコード。まずはパラメータの設定とか各処理の関数。関数の部分はチュートリアルとほぼ一緒です。データが1次元なのでプーリングのマトリクスを1×2の1次元にしています。
※モバイルでご覧の方はコードが表示されません。すいません。
import tensorflow as tf
import numpy as np
import socket
from contextlib import closing
X_DATA_SIZE = 32 # RSSIの個数
BATCH_SIZE = 128 # トレーニングのバッチサイズ
FILTER_SIZE = 5 # 畳み込みフィルタのサイズ
CONV_1_CHANNEL_SIZE = 16 # 畳み込み層1の出力チャンネル数
CONV_2_CHANNEL_SIZE = 32 # 畳み込み層2の出力チャンネル数
FC_NEURON_SIZE = 512 # 結合層のニューロン数
# Weightの初期化
def weight_variable(shape):
initial = tf.truncated_normal(shape, stddev=0.1)
return tf.Variable(initial)
# Biasの初期化
def bias_variable(shape):
initial = tf.constant(0.1, shape=shape)
return tf.Variable(initial)
# 畳み込み
def conv2d(x, W):
return tf.nn.conv2d(x, W, strides=[1, 1, 1, 1], padding='VALID')
# プーリング
def max_pool_2x1(x):
return tf.nn.max_pool(x, ksize=[1, 1, 2, 1], strides=[1, 1, 2, 1], padding='VALID')
そしてモデルの作成と初期化。モデルはチュートリアルと同じで畳み込み+プーリングの2層になっています。
# プレースホルダを設定
x = tf.placeholder(tf.float32, [BATCH_SIZE, X_DATA_SIZE])
y_ = tf.placeholder(tf.float32, [BATCH_SIZE, 2])
# xを4次元テンソルに変形
x_image = tf.reshape(x, [-1, 1, X_DATA_SIZE, 1])
# 畳み込み層1つめ
W_conv1 = weight_variable([1, FILTER_SIZE, 1, CONV_1_CHANNEL_SIZE])
b_conv1 = bias_variable([CONV_1_CHANNEL_SIZE])
h_conv1 = tf.nn.relu(conv2d(x_image, W_conv1) + b_conv1)
# プーリング層
h_pool1 = max_pool_2x1(h_conv1)
# 畳み込み層2つめ
W_conv2 = weight_variable([1, FILTER_SIZE, CONV_1_CHANNEL_SIZE, CONV_2_CHANNEL_SIZE])
b_conv2 = bias_variable([CONV_2_CHANNEL_SIZE])
h_conv2 = tf.nn.relu(conv2d(h_pool1, W_conv2) + b_conv2)
# プーリング層
h_pool2 = max_pool_2x1(h_conv2)
# 畳み込み、プーリングが完了したあとのデータサイズを計算
after_conv_size = ((X_DATA_SIZE - (FILTER_SIZE - 1))/2 - (FILTER_SIZE - 1))/2
# 高密度結合層
W_fc1 = weight_variable([after_conv_size * 1 * CONV_2_CHANNEL_SIZE, FC_NEURON_SIZE])
b_fc1 = bias_variable([FC_NEURON_SIZE])
h_pool2_flat = tf.reshape(h_pool2, [-1, after_conv_size * 1 * CONV_2_CHANNEL_SIZE])
h_fc1 = tf.nn.relu(tf.matmul(h_pool2_flat, W_fc1) + b_fc1)
# ドロップアウト
keep_prob = tf.placeholder("float")
h_fc1_drop = tf.nn.dropout(h_fc1, keep_prob)
# ソフトマックスで出力
W_fc2 = weight_variable([FC_NEURON_SIZE, 2])
b_fc2 = bias_variable([2])
y_conv = tf.nn.softmax(tf.matmul(h_fc1_drop, W_fc2) + b_fc2)
# トレーニング
cross_entropy = -tf.reduce_sum(y_ * tf.log(y_conv))
train_step = tf.train.AdamOptimizer(0.0001).minimize(cross_entropy)
#train_step = tf.train.GradientDescentOptimizer(0.002).minimize(cross_entropy)
# 評価
correct_prediction = tf.equal(tf.argmax(y_conv, 1), tf.argmax(y_, 1))
accuracy = tf.reduce_mean(tf.cast(correct_prediction, "float"))
saver = tf.train.Saver(max_to_keep=10)
init = tf.initialize_all_variables()
sess = tf.Session()
sess.run(init) # 初期化
続いて先ほど溜めこんだRSSIのデータファイルからデータを読み込んで学習する関数。
# ファイルからデータ読み込んでトレーニングする
def fromFile(file_name, y__, batch_size):
rssi = np.zeros([1, X_DATA_SIZE])
# 教師用データは先に作っておく
y_batch = y__
for i in range(batch_size - 1):
y_batch = np.vstack((y_batch, y__))
count = 0
rssilist = open(file_name, 'r')
for rssivalue in rssilist:
rssi = np.roll(rssi, 1)
rssi[0, 0] = float(rssivalue) / 60000.
count += 1
if count == X_DATA_SIZE:
rssi_batch = rssi
if count > X_DATA_SIZE:
rssi_batch = np.vstack((rssi_batch, rssi))
if count == (batch_size + X_DATA_SIZE - 1):
# 学習データが揃ったところでトレーニング
sess.run(train_step, feed_dict={x: rssi_batch, y_: y_batch, keep_prob: 0.5})
print sess.run(accuracy, feed_dict={x: rssi_batch, y_: y_batch, keep_prob: 1.0})
count = 0
rssilist.close()
リーダからデータを受信しながら評価する関数。トレーニングのバッチサイズを128にしたので評価時も128個必要なんですが面倒なのでRSSIが32個溜まったらそれをコピーして突っ込んでます。
# リーダからのデータを受信して評価してみる
def receiveRSSI():
host = '192.168.75.107'
port = 4000
bufsize = 4096
rssi = np.zeros([1, X_DATA_SIZE])
count = 0
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
with closing(sock):
# リーダからのストリーミングを待ちうけ
sock.bind((host, port))
sock.listen(1)
while True:
conn, address = sock.accept()
with closing(conn):
msg = conn.recv(bufsize)
list = msg.split('\x00')
for m in list:
if m.startswith("EPC"):
epclist = m.split('\r\n')
for epc in epclist:
if len(epc) > 0 and epc.startswith("EPC:E200 1AC1 9288"): # 特定のタグで
tmp = epc.split(',')[2].split(':')[1]
rssi = np.roll(rssi, 1)
rssi[0, 0] = float(tmp) / 60000.
count += 1
print count
if count >= X_DATA_SIZE:
# バッチサイズ複製して突っ込む
rssi_batch = rssi
for i in range(BATCH_SIZE - 1):
rssi_batch = np.vstack((rssi_batch, rssi))
print sess.run(y_conv, feed_dict={x: rssi_batch, keep_prob: 1.0})[1]
if count >= 100: # とりあえず100回でおわり
return
で、実際に実行します。止まっているときの教師データは [1.0, 0.0] にして、動いているときの教師データは [0.0, 1.0] にして学習させます。
# 動いてないときのデータで訓練
print 'no move phase start'
fromFile('nomove.dat', np.array([[1., 0.]]), BATCH_SIZE)
# 動いているときのデータで訓練
print 'move phase start'
fromFile('move.dat', np.array([[0., 1.]]), BATCH_SIZE)
# 結果は保存しておく
save_path = saver.save(sess, 'reader_deep', global_step = 0)
print "Model:%s" % save_path
# 実際にリーダから受信して試してみる
print 'valid phase start'
receiveRSSI()
結果
学習後にタグが止まった状態で評価してみると、
[ 3.66762222e-04 9.99633193e-01]
うはー!、ほとんど動いてると思ってますねー(笑 どうも過学習が強いようです。いろいろパラメータ変えてみても、
[ 0.36865199 0.63134807]
とちょっとはましですがどうもというところですね。しかしこの状態でタグを動かすと、0.63 が 0.67 くらいまで上がったりします。より動いてるのでは?という判断はしているようです。ここの判断が大きく値として現れるフィルタが必要なんですかね。
今回はここまで。やはりRNNまで進まないとダメでしょうか。先は長そうです。