2016年4月8日金曜日

RFIDでディープラーニング その2

前回の投稿ではあまりよい結果が出ませんでしたが、まー、いろいろ端折ってやったしー、みたいなところがあったので、もう少しましな結果が出るように試してみました。

前回ミスったと思われるところは、

・トレーニングのバッチ内のデータが全部 [1.0, 0.0] か [0.0, 1.0] のどちらかに揃っていて、バラバラに入り混じった状態でトレーニングしていない。
・トレーニング数が少ない。

他にも

・学習発散を抑えるために勾配降下のステップ長を学習の進み具合で変化させる。
・ドロップアウト率を学習の進み具合で変化させる。

のようなのもありますがひとまず上側の2つをやってみました。

コードは前回と違うところだけ。データ読み込みの関数を変更。読み込んでリストを返すだけにしました。
※前回同様モバイルでご覧の方はコードが表示されません。すいません。すいません。

# ファイルからデータ読込
def fromFile(file_name, y__):
  rssi = np.zeros([1, X_DATA_SIZE])
  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:
      x_list = rssi
      y_list = y__
    if count > X_DATA_SIZE:
      x_list = np.vstack((x_list, rssi))
      y_list = np.vstack((y_list, y__))
  rssilist.close()
  return x_list, y_list
で、データの読み込みと学習を実施。

data_num = 0

# 動いてないときのデータを読み込み
print 'no move phase read'
lists = fromFile('nomove.dat', np.array([[1., 0.]]))

x_all = lists[0]
y_all = lists[1]

# 動いているときのデータを読み込み
print 'move phase read'
lists = fromFile('move.dat', np.array([[0., 1.]]))

x_all = np.vstack((x_all, lists[0]))
y_all = np.vstack((y_all, lists[1]))

print 'total data:' + str(x_all.shape[0])
data_num = x_all.shape[0]

start = 0
for i in range(10000):
  if start + BATCH_SIZE > data_num:
    start = 0
  if start == 0:
    # データをシャッフル
    perm = np.arange(data_num)
    np.random.shuffle(perm)
    x_all = x_all[perm]
    y_all = y_all[perm]
  sess.run(train_step, feed_dict={x: x_all[start:start + BATCH_SIZE], y_: y_all[start:start + BATCH_SIZE], keep_prob: 0.5})
  if i%500 == 0:
    # 正確性と交差エントロピーを表示
    print sess.run(accuracy, feed_dict={x: x_all[start:start + BATCH_SIZE], y_: y_all[start:start + BATCH_SIZE], keep_prob: 1.0})
    print sess.run(cross_entropy, feed_dict={x: x_all[start:start + BATCH_SIZE], y_: y_all[start:start + BATCH_SIZE], keep_prob: 1.0})

学習が終わったら実際にタグが止まっている状態のデータを受信すると...

[ 0.91049373  0.08950625]

キタ―!止まってると認識してますね。

さらにタグを動かしてみます...

[ 0.85994738  0.14005256]
[ 0.78625971  0.21374026]
[ 0.73439723  0.2656028 ]
[ 0.71011215  0.28988785]
[ 0.65806454  0.34193543]
[ 0.27353901  0.72646099]
[ 0.01137005  0.98862994]

※データは見やすいように間引きしています。
おー、徐々に動いてる状態へと変化していきます。すごーい。MNISTのチュートリアルでも十分な精度が出ますねー。
これで何とか足がかりがついた感じです。実際の環境にどれだけ耐えられるかはまだまだ検証が必要ですけどね。



2016年4月1日金曜日

RFIDでディープラーニング その1

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まで進まないとダメでしょうか。先は長そうです。