「過去データを元に機械学習モデルを用いて故障予測を行う」アプリケーション(続き)
Predictive Analyticsサービスの登録
WebブラウザでBluemixのコンソールにログインし、ダッシュボードから本稿のサンプルアプリケーション(codezineIoTdemo)の画面を表示させて「+サービスまたはAPIの追加」を選択します(画面57)。
カタログ画面が表示されるので、「Predictive Analytics」を選択します(画面58)。
Predictive Analyticsの画面になるので、右側の「アプリ」に本稿のサンプルアプリケーション「CodeZine IoT demo Predict」が指定されていることを確認し、サービス名を任意で設定して「作成」ボタンをクリックします(画面59)。
再ステージングが必要といわれた場合は再ステージングしてください(画面60)。
しばらく待つと「アプリは稼働しています」と表示されるので、左のメニュー「サービス」に表示されている「Predictive Analytics」をクリックします(画面61)。
Predictive Analyticsモジュールの画面が表示されます。先ほど入力した名前になっていることと、画面下部に「No Models, Yet」と表示されていることを確認し、画面下部の「New Model Stream」の「Select File」をクリックし、SPSS Modelerで作成した予測モデルファイル(サンプルファイル「secom_predict_model.str」)をアップロードします(画面62)。
Context Idを聞かれるので、任意の名前を付けて「Deploy」をクリックします(画面63)。
すると、モデル登録されて「Manage Models」にて一覧表示に追加されます。「Deployed Model Usage」でPredictive Analyticsの利用状況(何回スコアリングが実施されたのか)がわかります。Freeプランでは毎月5000回までとなっています。
Predictive AnalyticsをAPIとして利用するための認証情報を確認します。画面左のメニューから「環境変数」を選択します(画面64)。
環境変数に、先ほど作成したPredictive Analyticsの認証情報が追加されています。アプリケーションからAPI利用をする際にaccess_key
とurl
を利用するのでコピーしておきます(画面65)。
Predictive Analyticsを呼び出して利用する
Node-REDのフローエディターを起動し、画面66のフローを作成してください。本記事のサンプルファイル「codezine_iotdemo_nodered_app3-a.json」をインポートすることもできます。
各ノードをダブルクリックして設定を確認・編集します。
① input >「Inject」ノード
デフォルトのままでOKです。
② function >「function」ノード
-
Name: 任意(画面66では「POST Parameter」)
-
Function: 後に示すコードを入力
Function欄に入力するコード
msg.payload = {
"tablename":"SECOM_DEMO_DATA.csv",
"header":["seq_no","failure","temperature","humidity","objecttemperature"],
"data":[["test1","",25,50,40]]
};
return msg;
API呼び出しにあたって、headerのほうで"seq_no"
と"failure"
も含める必要があるようですが、この2つの項目は予測には用いないのでダミーのデータで構いません。"temperature"
以下3つの項目はとりあえず動作確認したいので直データを指定します。値は任意です。
③ function >「http request」ノード
両側に端子がついているノードです。
-
Method: POST
-
URL:「認証情報URL/score/コンテキストID?accesskey=認証情報アクセスキー」という形式で入力
【例】
https://palbyp.pmservice.ibmcloud.com/pm/v1/score/codezinepredict1?accesskey=tsiabababZ1TxWvabbabtstabbxogqabbimc/BuB1U284a+WZII4a8o25pvJYhSGzcpmqjabbmlwVtsabbPiHxGxQ3pIogjgEOjN0TGtsTcL0h32gVzPabwMbmHXNpi+HL4aabtst4aPabGZUtstXsOpV+04Mu/ngj4ajy8qabbab4WCLtst4ITE1+S/abb4LjOWjtsZg8ZPabs6B3E=)
-
「Use basic authentication?」にチェックなし
-
Return: a parsed JSON object
-
Name: 任意(画面66では「[post] Predict API」)
④ output >「debug」ノード
デフォルトのままでOKです。
ここまで確認・編集できたら動作確認を行います。Deploy後、debugノードの出力をONにしてInjectノードをクリックすると処理が実施されます。Predictive Analytics APIの呼び出しに成功すると、Debugタブに以下のようなAPI結果が表示されます。
msg.payload : array [1]
[ { "header": [ "temperature", "humidity", "objecttemperature", "$XF-failure", "$XFC-failure" ], "data": [ [ 25, 50, 40, "yes", 0.4914826378710285 ] ] } ]
入力した値("temperature", "humidity", "objecttemperature")
に基づいて予測した結果("$XF-failure", "$XFC-failure")
が出力されます。$XF-failure
は予測した故障の有無(yes/no)、$XFC-failure
は予測に対する確信度(0~1)です。
予測結果が取得できることが確認できたので、あとはセンサーデータの値をパラメーターとしてAPIを呼び出すように変更してあげればOKです。
WebアプリケーションからのPredictive Analytics呼び出し
いよいよラストです。ブラウザにてNode-REDフローエディターを開いて、画面69に示すフローを作成してください。本記事の添付ファイル(codezine_iotdemo_nodered_all.json)をインポートすることもできます。
各ノードをダブルクリックして設定を確認・編集していきます。
① input >「http」ノード
Injectノード(Timestamp)と差し替えます。
-
Method: GET
-
URL: /predict
-
Name: 任意(画面69では「[get]/predict」)
② function >「function」ノード
-
Name: 任意(画面69では「POST Parameter」)
-
Function: 後に示すコードを入力。これにより、HTTPリクエストのパラメーターで気温・湿度・機器温度を指定できるようにします。
Function欄に入力するコード
msg.payload = {
"tablename":"SECOM_DEMO_DATA.csv",
"header":["seq_no","failure","temperature","humidity","objecttemperature"],
"data":[["test1","",
msg.payload.param_temp,
msg.payload.param_hum,
msg.payload.param_objt
]]
return msg;
③ output >「http response」ノード
Predicitive Analyticsを呼び出しているpostノード(Predict API)からつなげます。設定はデフォルトのままでOKです。
また、「Webアプリケーション」フローも次のように修正します。
④ 「Template」ノード(「index.html」)
-
Template: 後に示すコードを入力(サンプルファイル「index_app3-b.html」)
Template欄に入力するコード(▼クリックするとプルダウンしてコードが表示されます)
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<!-- jQuery -->
<script src="http://code.jquery.com/jquery-1.12.0.min.js"></script>
<!-- d3.js -->
<script src="http://d3js.org/d3.v3.min.js" charset="utf-8"></script>
<title>IoT demo chart</title>
<style>
.axis path,
.axis line {
fill: none;
stroke: #000;
shape-rendering: crispEdges;
}
.x.axis path {
display: none;
}
.line {
fill: none;
stroke: royalblue;
stroke-width: 1.5px;
}
.line2 {
fill: none;
stroke: orangered;
stroke-width: 1.5px;
}
.line3 {
fill: none;
stroke: forestgreen;
stroke-width: 1.5px;
}
</style>
<script type="text/javascript">
//グラフ用のデータ配列
var data = [];
//WebSocket接続用 Node-REDで生成した文字列を指定
var wsUrl = 'ws://codezineiotdemo2.mybluemix.net/ws/sensor';
var socket;
window.addEventListener('load', function () {
//Cloudant APIのURL
var recordnum = 10;
var dataurl = "./data?num=" + recordnum;
//Cloudantからデータ取得して初期表示
d3.json(dataurl, function(datas){
//console.log("===== d3.json ========");
//console.log(datas);
//JSONで取得する項目はそのまま使えるのでグラフ用配列にセット
data = datas;
//配列の順序を入れ替えてセット→D3.jsがよしなにしてくれるので不要
// for (var i in datas){
// data.unshift(datas[i]);
// }
//console.log("==== data object =====");
//console.log(data);
//グラフ描画処理
drawInitialChart();
//最新データを表示
var dispdata = {
timestamp : formatDateTime(new Date(datas[0].timestamp)),
temp : datas[0].temp,
humidity : datas[0].humidity,
objectTemp : datas[0].objectTemp
};
disp_info(dispdata);
});
});
</script>
</head>
<body>
<div style="margin:30px;">
<h1>IoTセンサーデータ可視化&故障予測デモ</h1>
<div>
<p>最新データ :
<span id="latestts">timestamp</span>
, <span style="color:royalblue;">気温:<span id="latesttemp">xx</span></span>
, <span style="color:forestgreen;">湿度:<span id="latesthum">xx</span></span>
, <span style="color:orangered;">機械温度:<span id="latestot">xx</span></span>
<span> </span>
故障予測:<span id="predict_failure"></span>
(スコア:<span id="predict_score"></span>)
</p>
</div>
<div id="chartarea"></div>
<p></p>
<div>
<button onclick="wsConnect()">WebSocket接続</button>
<button onclick="wsDisconnect()">WebSocket切断</button>
<span id="con-msg">message</span>
</div>
</div>
<script>
//D3.js 初期処理
//表示サイズを設定
var areasize = {width: 960, height: 500};
var margin = {top: 40, right: 40, bottom: 40, left: 40};
//グラフ表示用の高さと幅
var chartwidth = areasize.width - margin.left - margin.right;
var chartheight = areasize.height - margin.top - margin.bottom;
var formatDateTime = d3.time.format("%Y-%m-%d_%H:%M:%S");
var parseDate = d3.time.format("%Y-%m-%d_%H:%M:%S").parse;
//SVG領域の設定
var svg = d3.select("#chartarea").append("svg")
.attr("width", areasize.width)
.attr("height", areasize.height)
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
var x = d3.time.scale()
.range([0, chartwidth]);
var y = d3.scale.linear()
.range([chartheight, 0]);
var y2 = d3.scale.linear()
.range([chartheight, 0]);
var xAxis = d3.svg.axis()
.scale(x)
.orient("bottom");
//.orient("bottom")
//.tickFormat(d3.time.format("%m/%d_%H:%M"));
//温度のY軸(左側)
var yAxis = d3.svg.axis()
.scale(y)
.orient("left");
//湿度のY軸(右側)
var yAxis2 = d3.svg.axis()
.scale(y2)
.orient("right");
//ライン・気温
var line = d3.svg.line()
.x(function(d){ return x(d.timestamp); })
.y(function(d){ return y(d.temp); });
//ライン・機器温度
var line2 = d3.svg.line()
.x(function(d){ return x(d.timestamp); })
.y(function(d){ return y(d.objectTemp); });
//ライン・湿度
var line3 = d3.svg.line()
.x(function(d){ return x(d.timestamp); })
.y(function(d){ return y2(d.humidity); });
//グラフの初期描画処理 データ取得後にコールされる
function drawInitialChart(){
//console.log("===== drawInitialChart ======");
data.forEach(function(d){
d.timestamp = parseDate(d.timestamp);
d.temp = +d.temp;
d.objectTemp = +d.objectTemp;
d.humidity = +d.humidity;
});
//X軸のドメイン=Timestampの最小値と最大値
x.domain(d3.extent(data, function(d){ return d.timestamp; }));
//Y軸のドメイン=0から、気温Maxと機器温度Maxの大きい方
var maxtemp = d3.max(data, function(d){ return d.temp; });
var maxobjtemp = d3.max(data, function(d){ return d.objectTemp; });
var max_y = maxtemp > maxobjtemp ? maxtemp : maxobjtemp;
y.domain([0, max_y]);
//Y軸(湿度)は0-100固定
y2.domain([0, 100]);
//X軸 描画
svg.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0, " + chartheight + ")")
.call(xAxis);
//Y軸(左) 描画
svg.append("g")
.attr("class", "y axis")
.call(yAxis)
.append("text")
.attr("transform", "translate(60,-20) rotate(0)")
.attr("y", 6)
.attr("dy", ".7em")
.style("text-anchor", "end")
.text("温度(℃)");
//Y軸(右) 描画
svg.append("g")
.attr("class", "y axis")
.attr("transform", "translate(" + chartwidth + " ,0)")
.call(yAxis2)
.append("text")
.attr("transform", "translate(0,-20) rotate(0)")
.attr("y", 6)
.attr("dy", ".7em")
.style("text-anchor", "end")
.text("湿度(%)");
svg.append("path")
.datum(data)
.attr("class", "line")
.attr("d", line);
svg.append("path")
.datum(data)
.attr("class", "line2")
.attr("d", line2);
svg.append("path")
.datum(data)
.attr("class", "line3")
.attr("d", line3);
}
//データ更新時の描画処理
function updateDraw() {
console.log("===== updateDraw =======");
console.log(data);
data.forEach(function(d){
d.timestamp = d.timestamp;
d.temp = +d.temp;
d.objectTemp = +d.objectTemp;
d.humidity = +d.humidity;
});
//ドメイン(入力値の範囲)更新
x.domain(d3.extent(data, function(d){ return d.timestamp; }));
var maxtemp = d3.max(data, function(d){ return d.temp; });
var maxobjtemp = d3.max(data, function(d){ return d.objectTemp; });
var max_y = maxtemp > maxobjtemp ? maxtemp : maxobjtemp;
y.domain([0, max_y]);
y2.domain([0, 100]);
//アニメーション宣言
svg = d3.select("#chartarea").transition();
svg.select(".line")
.duration(750)
.attr("d", line(data));
svg.select(".line2")
.duration(750)
.attr("d", line2(data));
svg.select(".line3")
.duration(750)
.attr("d", line3(data));
svg.select(".x.axis")
.duration(750)
.call(xAxis);
svg.select(".y.axis")
.duration(750)
.call(yAxis);
}
//接続・切断時のメッセージを表示
function disp_msg(str) {
var msgarea = $("#con-msg");
msgarea.text(str);
msgarea.stop().fadeIn(0).fadeOut(2000);
}
//最新データ表示
function disp_info(wsdata){
$("#latestts").text(wsdata.timestamp);
$("#latesttemp").text(wsdata.temp);
$("#latesthum").text(wsdata.humidity);
$("#latestot").text(wsdata.objectTemp);
}
//WebSocket接続
function wsConnect() {
socket = new WebSocket(wsUrl);
disp_msg('接続しました。');
socket.onmessage = function(e) {
var wsData = JSON.parse(e.data);
console.log("timestamp:" + wsData.timestamp + " , temp:" + wsData.temp);
//最新データの情報を表示
disp_info(wsData);
//グラフのデータを更新
//グラフ用のデータが新しい順になっているので、配列の先頭に追加する
data.unshift({
timestamp : parseDate(wsData.timestamp),
temp : wsData.temp,
humidity: wsData.humidity,
objectTemp: wsData.objectTemp
});
//配列の末尾を取り除く
data.pop();
//グラフ描画
updateDraw();
//Predictive Analytics呼び出し
$.ajax({
url: "./predict",
data: {
"param_temp" : wsData.temp,
"param_hum" : wsData.humidity,
"param_objt" : wsData.objectTemp
}
}).done(function(result){
console.log("===== Predictive Analytics Call ======");
console.log(result);
//alert("success!");
$("#predict_failure").text(result[0].data[0][3]);
$("#predict_score").text( (Math.round( result[0].data[0][4] * 1000) /1000) );
}).fail(function(result){
alert("error!");
});
};
};
//WebSocket切断
function wsDisconnect() {
socket.close();
disp_msg('切断しました');
};
</script>
</body>
</html>
以下、コードの追加部分の説明です。
96~98行目:故障予測の結果を表示する領域です。
323~339行目:Predictive Analyticsを呼び出す処理です。WebSocketで受け取った最新のデータをHTTPリクエストのパラメーターとしてセットして呼び出しています。
では、確認が終わったらDeployして動作確認します。Webブラウザで、本プリケーションのルートURL(例 http://codezineiotdemo.mybluemix.net/)にアクセスします。画面72のようなグラフが表示された後に「WebSocket接続」ボタンを押して、しばらく待ってグラフが更新され、故障予測結果が表示されれば成功です。
これで今回のアプリケーション構築は完了です。いかがでしたでしょうか? 比較的簡単にクラウド上に保存したIoTセンサーデータを可視化したり、分析したりすることができたと思います。それでは皆さんも、Let's enjoy IoT!!
IoT成功のカギを握る:データ管理・分析クラウドサービスのご紹介
現象をデータ化するセンサーやデバイスに注目が集まりがちなIoTですが、価値を生むのはデータ化したその後の処理です。
「多数のセンサー、デバイスからインターネット経由で送信されてくるJSONデータを効率よく保管する」
「保管した大量のデータから未知のパターンを割り出し、ビジネスを成長させる知見を提供する」
こうしたIoTが価値を生むためのサービス・製品がIBMにあります。ぜひ、下記の資料をご覧ください。(編集部)