外出目的・帰宅予定時刻を管理するWEBサービスの開発

概要

玄関に端末が設置してあります

  1. 名前を選択します

  2. 外出目的を選択します

  3. 帰宅予定時刻を選択します

  4. 結果がLINEグループに送信されます

使用言語・技術

項目 言語・技術
フロントエンド HTML, CSS
バックエンド Ruby on Rails
サーバ Heroku

実装の手順

  1. Ruby on Rails, HerokuでWebアプリを実装

    RailsのViewに複数のsubmitボタンを設置して押されたボタンの値を取得する - Qiita

  2. Ruby on Rails, LINEBot APIでボタン入力内容をLINEグループに送信

    【Rails】LINEBotでPush送信 - Qiita

ベイジアンネットで遅刻の原因推定

[やること]
ベイズの定理、条件付確率を使ったベイジアンネットで、遅刻の原因を推定します

[ベイジアンネットについて]
事象をノード、条件付確率をエッジにして、ネットワークを構築して、原因推定、意思決定モデルなどを行う手法です。
参考文献[1]がわかりやすいです。

[結果1]
条件付確率からベイジアンネットを構築しました
以下がそれぞれの事象の説明です
B:BeLate:遅刻する
O:OverSleep:寝坊する
Ti:Tired:昨日疲れてた
S:StayUpLate:昨日夜更かしした
Tw:TweetALot:昨日のツイート数が多い

f:id:wada0421514:20200329222528p:plainf:id:wada0421514:20200329222531p:plain

[結果2]
遅刻した時に、昨日夜更かしした確率を計算します
P(S=1|B=1)です

P(S=1|B=1) = P(S=1,B=1)/P(B=1)です。

まず、分母P(B=1)から計算します
P(B=1) = P(B=1|Ov=1)P(Ov=1)+P(B=1|Ov=0)P(Ov=0)

P(B=1)の計算には、P(Ov=1)の値が必要です。
P(Ov=1)=P(Ov=1|Ti=[1,0],S=[1,0])P(Ti=[1,0])P(S=[1,0])
=0.8*0.5*0.4+0.5*0.4*0.5+0.6*0.6*0.5+0.4*0.6*0.5
=0.16+0.1+0.18+0.12
=0.56

分母P(B=1)が求まります。
P(B=1)=0.9*0.56+0.1*0.44=0.548

分子P(S=1,B=1)を計算します
P(S=1,B=1)
=P(S=1,B=1,Ti=[1,0],Ov=[1,0])
=P(B=1|Ov=[1,0])P(S=1,Ti=[1,0],Ov=[1,0])
=P(B=1|Ov=[1,0])P(Ov=[1,0]|Ti=[1,0],S=1)P(Ti=[1,0])P(S=1)
=0.9*0.8*0.4*0.5+0.8*0.6*0.6*0.5+0.4*0.2*0.4*0.5+0.4*0.4*0.6*0.5
=0.352

遅刻した時に、昨日夜更かしした確率が求まります。64%です。
P(S=1|B=1)=0.352/0.548=0.64

[結果3]
まず、寝坊した時に、夜更かしした確率を計算します。
その後、「昨日のツイート数が多い」という事象が加わった場合を計算します
P(S=1|O=1)、P(S=1|O=1,Tw=1)です

P(S=1|Ov=1)=P(S=1,Ov=1)/P(Ov=1)です。

分母P(Ov=1)は結果2で求まっています。
P(Ov=1)=0.56

分子P(S=1,Ov=1)を計算します
P(S=1,Ov=1)=P(Ov=1|S=1,Ti=[1,0])P(S=1)P(Ti=[1,0])
=0.8*0.5*0.4+0.6*0.5*0.6
=0.16+0.18
=0.34

寝坊した時に、夜更かしした確率が求まりました。61%です。
P(S=1|Ov=1)=0.34/0.56
=0.61

次に、「昨日のツイート数が多い」という事象が加わった場合を計算します

P(S=1|Tw=1,Ov=1)=P(S=1,Tw=1,Ov=1)/P(Tw=1,Ov=1)です。

分母P(Tw=1,Ov=1)を計算します
P(Tw=1,Ov=1)
=P(Tw=1,Ov=1,Ti=[1,0],S=[1,0])
=P(Ov=1|Ti=[1,0],S=[1,0])P(Tw=1,Ti=[1,0],S=[1,0])
=P(Ov=1|Ti=[1,0],S=[1,0])P(Ti=[1,0])P(Tw=1|S=[1,0])P(S=[1,0])
=0.8*0.4*0.7*0.5+0.5*0.4*0.5*0.5+0.6*0.6*0.7*0.5+0.4*0.6*0.5*0.5
=0.348

分子P(S=1,Tw=1,Ov=1)を計算します
P(S=1,Tw=1,Ov=1)
=P(S=1,Tw=1,Ov=1,Ti=[1,0])
=P(Ov=1|Ti=[1,0],S=1)P(Ti=[1,0])P(Tw=1|S=1)P(S=1)
=0.8*0.4*0.7*0.5+0.6*0.6*0.7*0.5
=0.238

「昨日のツイート数が多い」という事象が加わった場合の、寝坊した時に、夜更かしした確率が求まりました。68%です。少し確率が上がっています。直感と一致する結果です。
P(S=1|Tw=1,Ov=1)=0.238/0.348
=0.68

[今後の展望]
ベイジアンネットによる計算を自動化したいです

[参考文献]
[1]ベイジアンネットワーク
https://www.sist.ac.jp/~kanakubo/research/reasoning_kr/bayesiannetwork.html

最適なラーメンハシゴルート

[やること]
駅とラーメンのお店をネットワークで表現して、一番満足度の高いラーメンハシゴルートを計算します。
知識グラフを扱う練習が目的です。

[結果1]
青ノードが駅、赤ノードがラーメンのお店です。実際にあるお店です。
赤ノードの大きさが、ラーメンのお店の個人的なスコアです
エッジの数字はラーメンの値段、電車賃などを表します
f:id:wada0421514:20200329201246p:plain

[結果2]
以下のプログラムで、以下の設定で、スコアが一番高いルートを探索しました
設定:東京駅スタート、ラーメン2店舗ハシゴが上限
結果:['TOKYO', 'Rokurinsha', 'SHINJUKU', 'Konjiki']、1980円

[プログラム]

import networkx as nx
import matplotlib.pyplot as plt
import itertools

class Noodle():

    def __init__(self):
        #ネットワーク読み込み
        self.G = nx.read_edgelist('nodelist.txt', nodetype=str)
        nx.set_node_attributes(self.G, name='score', values=200)
        #スコア
        self.G.nodes["Rokurinsha"]["score"]=850
        self.G.nodes["Konjiki"]["score"]=900
        self.G.nodes["Ozeki"]["score"]=800
        self.G.nodes["TOKYO"]["score"]=0
        self.G.nodes["SHINJUKU"]["score"]=0
        self.G.nodes["SHIBUYA"]["score"]=0
        #コスト
        self.edge_labels={}
        self.edge_labels[("SHINJUKU","TOKYO")]=200
        self.edge_labels[("TOKYO","SHINJUKU")]=200
        self.edge_labels[("SHIBUYA","TOKYO")]=200
        self.edge_labels[("TOKYO","SHIBUYA")]=200
        self.edge_labels[("SHIBUYA","SHINJUKU")]=160
        self.edge_labels[("SHINJUKU","SHIBUYA")]=160
        self.edge_labels[("TOKYO","Rokurinsha")]=830
        self.edge_labels[("TOKYO","Hyottoko")]=670
        self.edge_labels[("TOKYO","Oboroduki")]=850
        self.edge_labels[("TOKYO","Hachigo")]=850
        self.edge_labels[("SHINJUKU","Gonokami")]=800
        self.edge_labels[("SHINJUKU","Funji")]=700
        self.edge_labels[("SHINJUKU","Konjiki")]=950
        self.edge_labels[("SHINJUKU","Sho")]=700
        self.edge_labels[("SHINJUKU","Hayashida")]=800
        self.edge_labels[("SHIBUYA","Kiraku")]=700
        self.edge_labels[("SHIBUYA","Hayashi")]=800
        self.edge_labels[("SHIBUYA","Ozeki")]=830

    #ネットワークをプロットする
    def plot(self):
        #色、サイズなど
        pos = nx.spring_layout(self.G, k=0.8)
        nx.draw_networkx_edges(self.G, pos, edge_color='y')
        c = ['blue' if n.isupper() else 'red' for n in self.G.nodes()]
        score_size = [1000 if n.isupper() else self.G.nodes[n]["score"] for n in self.G.nodes()]
        nx.draw_networkx_nodes(self.G, pos, node_color=c, alpha=0.5, node_size=score_size)
        nx.draw_networkx_edge_labels(self.G, pos, edge_labels=self.edge_labels, font_size=24)
        nx.draw_networkx_labels(self.G, pos, font_size=24)
        plt.figure(figsize=(10, 8))
        plt.axis('off')
        plt.show()

    #ルートのコスト計算
    def costscore_cal(self,route):
        score = 0
        cost = 0
        for a in range(len(route) - 1):
            if route[a] == route[a + 1]:
                continue
            else:
                if not (route[a].isupper()) and route[a + 1].isupper():
                    continue
                else:
                    cost = cost + self.edge_labels[(route[a], route[a + 1])]
                    score = score + self.G.nodes[route[a + 1]]["score"]
        return score, cost

    #店の一番近い駅
    def store2station(self,storename):
        station = {'Rokurinsha': 'TOKYO', 'Hyottoko': 'TOKYO', 'Oboroduki': 'TOKYO', 'Hachigo': 'TOKYO',
                   'Gonokami': 'SHINJUKU', 'Funji': 'SHINJUKU', 'Konjiki': 'SHINJUKU', 'Sho': 'SHINJUKU',
                   'Hayashida': 'SHINJUKU',
                   'Kiraku': 'SHIBUYA', 'Hayashi': 'SHIBUYA', 'Ozeki': 'SHIBUYA'}
        return station[storename]

    #全ルート生成
    def allroutes(self, noodle_count):
        noodle_shop = ('Rokurinsha', 'Hyottoko', 'Oboroduki', 'Hachigo',
                       'Gonokami', 'Funji', 'Konjiki', 'Sho', 'Hayashida',
                       'Kiraku', 'Hayashi', 'Ozeki')
        all_routes = ()
        for a in range(noodle_count):
            all_routes = all_routes + tuple(itertools.permutations(noodle_shop, a + 1))

        return all_routes

    #一番スコアが高いルート探索
    def allroutes_costscore_cal(self,allroutes, start_station):
        result_route = []
        for noodle_route in allroutes:
            list_kari = []
            for a in noodle_route:
                list_kari.append(start_station)
                list_kari.append(self.store2station(a))
                list_kari.append(a)
                list_kari.append(self.store2station(a))
            result_route.append(list_kari)

        result_score = []
        result_cost = []

        for a in result_route:
            score, cost = self.costscore_cal(a)
            result_score.append(score)
            result_cost.append(cost)

        result_route=result_route[result_score.index(max(result_score))]
        result_cost=result_cost[result_score.index(max(result_score))]

        return list(dict.fromkeys(result_route)),result_cost

if __name__=='__main__':

    n=Noodle()
    #2店舗はしごが限界
    allroutes=n.allroutes(2)
    #東京駅スタート
    result_route,cost=n.allroutes_costscore_cal(allroutes,"TOKYO")
    #結果:['TOKYO', 'Rokurinsha', 'SHINJUKU', 'Konjiki']
    print(result_route)
    #結果:1980円
    print(str(cost)+"円")
    n.plot()

[参考文献]
[1]Pythonでネットワークを分析・可視化しよう!必要手順まとめ
https://www.sejuku.net/blog/91371

[2]【Python】NetworkX 2.0の基礎的な使い方まとめ
https://qiita.com/kzm4269/items/081ff2fdb8a6b0a6112f

カオスの軌跡のプロット

[やること]
微分方程式で表現されるカオスをPythonで解いて、軌跡をプロットします。

[カオスについて]
カオスは式で明確に定義されているのに、初期値の選び方で将来の状態が予測できない現象のことです。
参考文献[1][2]がわかりやすいです

[結果1]
ローレンツ方程式

f:id:wada0421514:20200329140013p:plain

p,r,b=10,28,8/3

初期値 x,y,z=1,1,1
f:id:wada0421514:20200329135713p:plain
初期値 x,y,z=1.0001,1,1
f:id:wada0421514:20200329135717p:plain
初期値 x,y,z=1.001,1,1
f:id:wada0421514:20200329135721p:plain
初期値 x,y,z=1.01,1,1
f:id:wada0421514:20200329135727p:plain

横軸に時間、縦軸にxを取ったプロットです。
初期値が少し変わるだけで軌跡が大きく変わることがわかります。

f:id:wada0421514:20200329140511p:plain


[結果2]
レスラー方程式
f:id:wada0421514:20200329140830g:plain

a,b,c=0.2,1.0,5.7

初期値 x,y,z=1.0,0.0,0.0
f:id:wada0421514:20200329140958p:plain
初期値 x,y,z=8.01,0.0,0.0
f:id:wada0421514:20200329141003p:plain
初期値 x,y,z=8.02,0.0,0.0
f:id:wada0421514:20200329141007p:plain

[プログラム]
微分方程式を解くライブラリodeintについては、参考文献[3]がわかりやすいです

import numpy as np
from scipy.integrate import odeint
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import Axes3D

#レスラー方程式
def rossler(var,t,a,b,c):
    x_next=-var[1]-var[2]
    y_next=var[0]+a*var[1]
    z_next=b*var[0]-(c-var[0])*var[2]
    return np.array([x_next,y_next,z_next])

#ローレンツ方程式
def lorentz(var,t,p,r,b):
    x_next=-p*var[0]+p*var[1]
    y_next=-var[0]*var[2]+r*var[0]-var[1]
    z_next=var[0]*var[1]-b*var[2]
    return np.array([x_next,y_next,z_next])

if __name__ == "__main__":

    #レスラー方程式のパラメータと初期値
    #abc=(0.2,1.0,5.7)
    #xyz_first=[1.0,0.0,0.0]

    #ローレンツ方程式のパラメータと初期値
    prb=(10,28,8/3)
    xyz_first=[1.01,1,1]

    #ステップ数
    t=np.linspace(0,1000,100000)

    #微分方程式を解く
    #var=odeint(chaos,xyz_first,t,args=abc)
    var=odeint(lorentz,xyz_first,t,args=prb)

    #グラフの枠を作っていく
    fig = plt.figure()
    ax = Axes3D(fig)

    #軸にラベルを付ける
    ax.set_xlabel("X")
    ax.set_ylabel("Y")
    ax.set_zlabel("Z")

    #プロットして表示
    ax.plot(var[:, 0], var[:, 1], var[:, 2], marker="o", linestyle='None')
    plt.show()


[参考文献]
[1]カオスアトラクタとは
http://www.gifu-nct.ac.jp/elec/deguchi/sotsuron/hisaki/node9.html#eqresura
[2]カオス
http://www.aoni.waseda.jp/yuuka/Sim/Chaos.html
[3]Python運動方程式を解く(odeint)
https://qiita.com/binaryneutronstar/items/ad5efa27fd626826846f

自己組織化マップで色マップ画像作成

[やること]

自己組織化マップ(Self-Organizing Maps)で、RGBで表現された色データを、教師なし学習して、色マップを作成します。

 

[自己組織化マップの説明]

教師なし学習の手法です。

入力データに関して、近いデータを近くに配置するマップを作成します。

参考資料[1]がわかりやすいです

  

[結果1]

黒、緑、黄、赤、紫、白、水、青の8色を学習させた結果です。

青と水、赤と紫、など、近い色が近くに配置されています。

f:id:wada0421514:20200329114954p:plain

 

[結果2]

以下の12色相環の12色を学習させた結果です。

12色相環を再現できています。

f:id:wada0421514:20200329115153p:plain

f:id:wada0421514:20200329115156p:plain

 

[プログラムについて]

参考資料[2]をかなり参考にしました

import numpy as np
import cv2

class SOM:
def __init__(self, n_side, n_learn=1000, c=0.5):
#n_side:マップのサイズ,n_learn:学習回数,c:更新率
self.n_side = n_side
self.n_learn = n_learn
self.c = c
self.n_weight = self.n_side * self.n_side

def fit(self, input_vector):

input_vector = np.array(input_vector)
n_input = len(input_vector)
n_vector = input_vector.shape[1]

# pointsにはそれぞれの重みの(x,y)座標が入っている(範囲は[0,1))
points = np.array([[i // self.n_side, i % self.n_side] for i in range(self.n_weight)])
points = points / (1.0 * self.n_side)

# 重みベクトルの初期化
self.weight = np.zeros((self.n_weight, n_vector))

# ランダムなインデックス
random_index = np.arange(n_input)
np.random.shuffle(random_index)

for t in range(self.n_learn):

# ランダムに一つ抽出
vec = input_vector[random_index[t % n_input]]

# 勝ちニューロン決定
winner_index = np.argmin(np.linalg.norm(diff, axis=1))
winner_point = points[winner_index]

# 近傍関数の計算
alpha = 1.0 - float(t) / self.n_learn#α=1-t/T:学習回数に応じて単調に減少
delta_point = points - winner_point
dist = np.linalg.norm(delta_point, axis=1)#勝ちニューロンとの距離
h = self.c * alpha * np.exp(- (dist / alpha) ** 2)

# 重みを更新
diff = vec - self.weight# 入力ベクトルと重みの差
self.weight += np.atleast_2d(h).T * diff


if __name__ == "__main__":

    #8色のRGB
    input_vector=[[255,255,255],[255,255,0],[255,0,255],[0,255,255],
                  [0,0,255],[0,255,0],[255,0,0],[0,0,0]]

    #12色相環のRGB
    color_wheel=[[255, 0, 0],[255, 127, 0],[255, 255, 0],[127, 255, 0],
                 [0, 255, 0],[0, 255, 127],[0, 255, 255],[0, 127, 255],
                 [0, 0, 255],[127, 0, 255],[255, 0, 255],[255, 0, 127]]

    # SOMクラスの作成・学習
    n_side = 500 # 一辺の長さ
    som = SOM(n_side, n_learn=1000,c=0.75)
    som.fit(color_wheel)

    # 重みベクトルの取得
    output_imgs = som.weight

    # 重みベクトルを並べて、画像作成
    output_imgs = output_imgs.reshape(n_side, n_side, 3)
    tile = np.zeros((n_side, n_side,3))
    for x in range(n_side):
    for y in range(n_side):
    tile[(x):(x+1), (y):(y+1)] = output_imgs[x, y]

    # 画像の保存
    print(tile)
    cv2.imwrite("tile.png", tile)

  

[参考資料]

[1]自己組織化特徴マップ(SOM)

http://www.sist.ac.jp/~kanakubo/research/neuro/selforganizingmap.html

[2]MNISTの自己組織化マップ(SOM)を作った[Python]-もりとーにのブログ

https://tony-mooori.blogspot.com/2016/01/mnistpython.html

山手線を徒歩で一周した

山手線を徒歩で一周しました。

新宿がスタート、ゴールで外回りで歩きました。

約40キロ、8時間21分かかりました。

f:id:wada0421514:20191120194455p:plain

1. 新宿駅:スタート

大きな駅。なんでもある。雑多。

f:id:wada0421514:20191120194950j:plain

2. 新大久保駅

韓国料理のお店がたくさんある。

新大久保ー高田馬場:きれいなマンション。落ち着いた感じ。

f:id:wada0421514:20191120195930j:plain

3. 高田馬場駅

豚カツ。全体的に低めの建物。とん太。

高田馬場ー目白:住宅街。

f:id:wada0421514:20191120195927j:plain

4. 目白駅

駅前オシャレ。住宅街。住みやすそう。落ち着いた感じ。ケーキ。

f:id:wada0421514:20191120195924j:plain

5. 池袋駅

大きな駅。雑多。なんでもある。飲み屋街。SEIBUビル。

池袋ー大塚:キレイめ。タワマンがあった。

 f:id:wada0421514:20191120195922j:plain

6. 大塚駅

路面電車。落ち着いた感じ。緑多め。カッチャルバッチャル。

f:id:wada0421514:20191120195918j:plain

7. 巣鴨

地味な感じ。

f:id:wada0421514:20191120195914j:plain

8. 駒込駅

地味な感じ。

f:id:wada0421514:20191120195912j:plain

9. 田端駅

地味な感じ。

f:id:wada0421514:20191120195910j:plain

10. 西日暮里駅

神田っぽい。栄えている。

f:id:wada0421514:20191120195907j:plain

11. 日暮里駅

駅前がオシャレ。タワマンがある。

f:id:wada0421514:20191120195902j:plain

12. 鶯谷

駅前にラブホたくさんある。鶯谷苑。

f:id:wada0421514:20191120195859j:plain

13. 上野駅

上野動物園。公園。不忍池。博物館。

f:id:wada0421514:20191120195856j:plain

14. 御徒町駅

神田っぽい。

f:id:wada0421514:20191120195853j:plain

15. 秋葉原駅

大きい駅。オタク。なんでもある。

f:id:wada0421514:20191120195850j:plain

16. 神田駅

線路下に店色々。飲み屋色々。低めの建物多い。

f:id:wada0421514:20191120195848j:plain

17. 東京駅

大きな駅。駅前かっこいい。なんでもある。皇居。

中間地点。ちょっと休憩した。

f:id:wada0421514:20191120195844j:plain

18. 有楽町駅

東京駅の続き見たいな感じ。

f:id:wada0421514:20191120195841j:plain

19. 新橋駅

大きな駅。なんでもある感じ。

この辺りで、脚を痛めた。ラスト10kmくらい、かなりしんどかった。

f:id:wada0421514:20191120195838j:plain

20. 浜松町駅

品川っぽい。

f:id:wada0421514:20191120195836j:plain

21. 田町駅

品川っぽい。大きな駅。

f:id:wada0421514:20191120195833j:plain

22. 品川駅

大きな駅。なんでもある。結構きれい。

f:id:wada0421514:20191120195831j:plain

23. 大崎駅

オシャレ。新しい。キレイ。

f:id:wada0421514:20191120195828j:plain

24. 五反田駅

雑多な感じ。品川っぽい。

f:id:wada0421514:20191120195826j:plain

25. 目黒駅

色々ある感じ。ちょっとオシャレ。

f:id:wada0421514:20191120195823j:plain

26. 恵比寿駅

オシャレ。色々ある。蕃 YORONIKU。

f:id:wada0421514:20191120195821j:plain

27. 渋谷駅

大きな駅。なんでもある。若い。人多い。

f:id:wada0421514:20191120195818j:plain

28. 原宿駅

竹下通り。若い。人多い。

f:id:wada0421514:20191120195816j:plain

29. 代々木駅

新宿駅の続きっぽい。

f:id:wada0421514:20191120195814j:plain

30. 新宿駅:ゴール

f:id:wada0421514:20191120195811j:plain

物体検出+クラス分類で白のトイプードルの画像収集

やること

以下のようなことがやりたいです。

  1. 様々な画像がフォルダに含まれている

    f:id:wada0421514:20191102113725p:plain

  2. 白のトイプードルだけ取り出して保存する

    f:id:wada0421514:20191102113729p:plain

学習

学習用の画像準備

347枚の犬が写っている画像を集めました

各画像にラベルを付けました

  • 白のトイプードル : 164枚

  • それ以外 : 183枚

画像から犬部分を切り出す

犬が小さいと学習に使えないので、画像から犬部分を切り出します

学習済みの物体検出モデルYOLOv3を使いました

クラス分類モデルVGG16を学習する

集めた犬の画像で学習します

50epochで収束しました

バリデーションの精度は95%でした

推論

  1. テスト用の9枚の犬画像があります

    f:id:wada0421514:20191102195624p:plain

  2. 物体検出モデルYOLOv3に入力し、犬部分を切り出します

  3. 今回学習したクラス分類モデルVGG16に切り出した犬画像を入力します

  4. 白のトイプードルの画像のみ取り出すことができました

    f:id:wada0421514:20191102195631p:plain

コード

作成したコードは全てgithubに上げてあります。 github.com

参考文献

qiita.com

www.itd-blog.jp