KNIMEエクステンション開発
dodosoft Advent Calendar、uphy担当3記事目! www.adventar.org
ここ最近KNIME触ってます。 KNIMEは機械学習やデータの処理をGUIのぽちぽちで実現できるものです。
KNIME | KNIME Analytics Platform
KNIMEは、日立製作所のLumadaの構成要素の一つにもなっているようで、一部の人たちの間で熱いです。 http://www.hitachihyoron.com/jp/pdf/2016/07_08/2016_07_08_10.pdf
KNIMEで社内のAPIを入力としたかったのですが、それを実現するようなノードがなかったので、エクステンション(Eclipseプラグイン)を開発しました。今回はその手順を記事にします。 記事ではその実物ではなく、簡略化したものを掲載します。
KNIMEはEclipse RCPアプリケーションであるため、プラグインを開発することで、誰でも簡単に拡張できます。
開発対象
ダミーWeb APIからデータを取得する、KNIMEノードを開発します。
ダミーWeb API
ここは今回のテーマから外れるので、重要ではないです。
固定のJSONを返すダミーのWeb APIを実行しておきます。
このWeb APIから、データを取得するKNIMEノードを開発します。
dummyapi.py
from flask import Flask, jsonify app = Flask(__name__) @app.route("/api", methods=['GET']) def api(): columns = ['A', 'B', 'C'] rows = [] for i in range(1000): row = {} for col in columns: row[col] = "%d/%s" % (i, col) rows.append(row) return jsonify({ 'columns': columns, 'rows': rows }) app.run(host='0.0.0.0')
実行。
$ python dummyapi.py
開発環境構築
今回は、KNIME SDKを利用します。
KNIME | Download KNIME Analytics Platform & SDK
別の手段として、EclipseにプラグインとしてKNIMEの開発キットをインストールすることもできます。 上記のKNIME SDKは、開発キットのプラグインをインストール済みのEclipseです。
プロジェクト作成
- File > New
- KNIME > Create a new KNIME Node-Extension
- Finish
サンプルの実行
上記の設定で、サンプル込みのプロジェクトが生成されるので、サンプルをまず実行してみます。
- 作成したプロジェクトを右クリック
- Run > Eclipse Application
- 開いたもう一つのEclipseは、サンプルのプラグインを含むEclipseです。Window > Perspectives > Open Perspectiveから、KNIMEパースペクティブを開きます。
- KNIME ExplorerのLOCALを右クリックして、New KNIME Workflow Wizardをクリック
- Finish
- 開いたキャンバスに、Node RespositoryのWebApiCallノードをドラッグアンドドロップ。
サンプルのビルド
プラグインのjarファイルを生成して、KNIMEにインストールしてみます。
- プロジェクトを右クリック
- Export
- Plug-in Development > Deployable plug-ins and fragmentsを選択してNext
- 出力先のDirectoryを選択してFinish 出力先に、jarファイルが生成されます。
- KNIMEのdropinsにjarファイルを入れる。Macの場合はアプリケーションがパッケージ化されているので、KNIME X.Y.Z.appを右クリックして、「パッケージの内容を表示」をして、Contents/Eclipse/dropinsに入れてください。
- KNIMEを起動
WebApiCallノードが追加されたことを確認しました。 これでサンプルの実行&プラグインのビルドができたので、これをベースにプラグインを開発します。
開発
ノード定義
まずは見た目から。
WebApiCallNodeFactory.xmlで各種説明を入力します。
名前が気になったので、Web API Callと変更しました。
また、作成するノードはデータの入力は行わないので、inPortを削除しました。
ここで一旦実行します。
Node Descriptionビューにノードの説明が表示されました!
モデル定義
WebApiCallNodeModelを修正。
ノードの入力数、出力数を、コンストラクタで指定します。
/** * Constructor for the node model. */ protected WebApiCallNodeModel() { super(0, 1); }
モデルのパラメータを定義します。
KNIMEのノードをダブルクリックしたときの設定項目を全てパラメータとして定義します。
ここでは、endpointUrlを定義します。
static final String ENDPOINT_URL_KEY = "endpointUrl"; static final String ENDPOINT_URL_DEFAULT = "http://localhost:5000/api"; private final SettingsModelString m_endpointUrl = new SettingsModelString(ENDPOINT_URL_KEY, ENDPOINT_URL_DEFAULT);
ダイアログの画面定義でもKEY、DEFAULTは参照するので、定数として定義しています。
ダミーAPIからのデータ(JSON)をパースするために外部ライブラリ(org.json:json)を利用します。
取得はこのあたりからでも。
Maven Repository: org.json » json » 20160810
取得したライブラリをClasspathに追加します。plugin.xmlを開いてRuntimeタブでAdd...をクリックして下記のように設定。下記ではプロジェクト直下にlibディレクトリを作成し、そのなかに上記のライブラリを配置しました。
引き続きWebApiCallNodeModelに戻って、ダミーAPIからデータを取得するコード。
private JSONObject callApi(final URL url) throws IOException { final ByteArrayOutputStream baos = new ByteArrayOutputStream(); try (InputStream inputStream = url.openStream()) { final byte[] buf = new byte[0x1000]; int size; while ((size = inputStream.read(buf)) != -1) { baos.write(buf, 0, size); } } return new JSONObject(new String(baos.toByteArray())); }
そのデータを、KNIMEのデータに変換するコードを書きます。
@Override protected BufferedDataTable[] execute(final BufferedDataTable[] inData, final ExecutionContext exec) throws Exception { final URL url = new URL(this.m_endpointUrl.getStringValue()); final JSONObject response = callApi(url); final JSONArray jsonColumns = response.getJSONArray("columns"); final DataColumnSpec[] allColSpecs = new DataColumnSpec[jsonColumns.length()]; for (int i = 0; i < allColSpecs.length; i++) { allColSpecs[i] = new DataColumnSpecCreator(jsonColumns.getString(i), StringCell.TYPE).createSpec(); } final DataTableSpec outputSpec = new DataTableSpec(allColSpecs); final BufferedDataContainer container = exec.createDataContainer(outputSpec); final JSONArray jsonRows = response.getJSONArray("rows"); for (int i = 0; i < jsonRows.length(); i++) { final JSONObject jsonRow = jsonRows.getJSONObject(i); final RowKey key = new RowKey("Row " + i); final DataCell[] cells = new DataCell[jsonColumns.length()]; for (int j = 0; j < cells.length; j++) { cells[j] = new StringCell(jsonRow.getString(jsonColumns.getString(j))); } final DataRow row = new DefaultRow(key, cells); container.addRowToTable(row); exec.checkCanceled(); exec.setProgress(i / (double) jsonRows.length(), "Adding row " + i); } container.close(); BufferedDataTable out = container.getTable(); return new BufferedDataTable[] { out }; }
ビュー定義
WebApiCallNodeDialog.javaを変更します。
protected WebApiCallNodeDialog() { super(); addDialogComponent(new DialogComponentString( new SettingsModelString(WebApiCallNodeModel.ENDPOINT_URL_KEY, WebApiCallNodeModel.ENDPOINT_URL_DEFAULT), "Endpoint URL", true, 30)); }
簡単!! こんなダイアログができました。
実行
サンプルと同様の手順で実行できます。
ダミーAPIからデータを取得できました。
ビルド
サンプルと同様の手順でビルドできます。
Mavenプロジェクト化
まとめ
ダミーのWeb APIからデータを取得するKNIMEノードを作成しました。
コードは良くも悪くも素直で書きやすかったです。
もうちょっとアノテーション活用するとか、DIいれるとかしてくれるとより開発しやすくて助かります。