NLC (Natural Language Classifier) の学習が終わるまで何分かかったか表示するプログラムを書いてみた


最近NLCを使って交差検証をすることが多いです。交差検証なのでKの数だけ分類器を作るのですが、それぞれ**Availableになるのを待つのがとっても面倒くさい**です。

終わったら終わったよー、終わってないなら終わってないよー、と**画面を見るだけでわかるスクリプト**があるといいなということで、他にもありそうですが書いてみました。

(追記1) 唯一すらすらかける言語がperlなのでperlで書いています。悪しからず。
(追記2) Qiitaは目次が右に表示されるんですね。だからみんな本文に目次なしなのか。

# 1. やること

必要なのはこれくらいでしょうか。上から順にプログラムに直していきます。

- 社内がプロキシ必須なのでプロキシ設定
- WatsonのAPIをたたいてNLCのサービス情報や分類器情報をとってくる
- 終了条件を書く
 - 分類器のステータスが全部 **Training 以外** だったら終了
 - 一定時間たっても終了
- 終了したら経過時間を表示する

# 2. ポイント

要点だけコメントしていきます。

## プロキシ設定

%ENVに代入したら環境変数設定できたなーということでこんな感じ。
(追記) 認証があるときに@が入るとうまく設定できないので、シングルクォートにしました。

```.pl
# proxy settings
$ENV{http_proxy} = 'http://your-proxy:8080';
$ENV{https_proxy} = 'http://your-proxy:8080';
```

## Watson APIを読んでサービスやら分類器やらの情報を取る

curlを呼ぶだけ、非常にらくちん。
[API Reference](https://www.ibm.com/watson/developercloud/natural-language-classifier/api/v1/curl.html?curl) のサンプルほぼそのままコピペでよいのはさっと書きたいときに便利。
得られた文字列は適当に処理します。

```.pl
# 分類器情報取得
my @classifiers;
my $re = `curl -s -u $username:$password $url/v1/classifiers`;
my @line = split(/\r|\n/,$re);
for(@line){
	if(/"classifier_id" : "(.+)"/){
		my $id = $1;
		push @classifiers, $id;
		print "$id\n";
	}
}
```

## 経過時間を表示

timeにはそのときの秒数が入っているので適当に差分を取って出力します。
localtimeをscalarで評価すると `Tue Dec 26 19:19:13 2017` のような現在時刻の文字列になります。

```.pl
# 開始時刻を保存
my $start = time;

# ～時間のかかる処理～

# 終了時刻をとってきて適当に表示
my $end = time;
my $sec = $end - $start;
my $min = int($sec / 60);
print scalar(localtime), " Elapsed $min minutes ($sec seconds)\n";
```

# 3. 完成!

コードと実行結果を張っておきます。もし使いたい人がいたら使ってください。(無保証です)

## ソースコード

```wait.pl
# proxy settings
#$ENV{http_proxy} = 'http://your-proxy:8080';
#$ENV{https_proxy} = 'http://your-proxy:8080';

#credentials
my $url = "https://gateway.watsonplatform.net/natural-language-classifier/api";
my $username = "your-username"; # ← NLC の credential から username をコピペ
my $password = "your-password"; # ← NLC の credential から password をコピペ

# 開始時刻を保存
my $start = time;

# 分類器情報取得
my @classifiers;
my $re = `curl -s -u $username:$password $url/v1/classifiers`;
my @line = split(/\r|\n/,$re);
for(@line){
	if(/"classifier_id" : "(.+)"/){
		my $id = $1;
		push @classifiers, $id;
		print "$id\n";
	}
}

my %status;
my $count = 3600; # 最大10時間
while($count--){
	my$found = 0;
	for $id (@classifiers){
		if(!$status{$id} || $status{$id} eq "Training"){
			$found = 1;
			my $re = `curl -s -u $username:$password $url/v1/classifiers/$id`;
			my @line = split(/\r|\n/,$re);
			my $status;
		
			for(@line){
				if(/"status" : "(.+)"/){
					$status = $1;
					print scalar(localtime), " $id $status\n";
					$status{$id} = $status;
				}
			}
		}
	}
	# 1つもTrainingの分類器がなければ終了
	if(!$found){
		print scalar(localtime), " Finished\n";
		my $end = time;
		my $sec = $end - $start;
		my $min = int($sec / 60);
		print scalar(localtime), " Elapsed $min minutes ($sec seconds)\n";
		exit;
	}
	sleep(10);
}

# 10時間たって終了したらこっち
print scalar(localtime), " Timed out\n";
my $end = time;
my $sec = $end - $start;
my $min = int($sec / 60);
print scalar(localtime), " Elapsed $min minutes ($sec seconds)\n";
```

（終了時のコードが2か所に分かれていて気になるけどそのままです）
（追記) githubに上げてみました。https://github.com/nmakino/wait-for-nlc

## 使い方

- 上のコードをコピペして保存する
- プロキシが必要なら2~3行目のコメントを外して、右辺にプロキシサーバのURLを指定する
- NLC の credential 情報 (username, password) を6~7行目の右辺にコピペする
- 必要な人は一行目に `#!/path/to/perl` 的なものを入れて `chmod + x wait.pl` とかしてください

## 実行結果

最初に分類器のIDが列挙されて、あとはステータスがひたすら出ます。
**すべてAvailableになったら時間を表示して終了**します。
この例では最初からすべて Available ですが、Training の分類器があればひたすら待ちます。

```
> perl wait.pl
3363cfx256-nlc-31811
3363cfx256-nlc-31810
3363cfx256-nlc-31808
e554c3x251-nlc-88506
e554c3x251-nlc-88601
Tue Dec 26 19:19:13 2017 3363cfx256-nlc-31811 Available
Tue Dec 26 19:19:14 2017 3363cfx256-nlc-31810 Available
Tue Dec 26 19:19:15 2017 3363cfx256-nlc-31808 Available
Tue Dec 26 19:19:17 2017 e554c3x251-nlc-88506 Available
Tue Dec 26 19:19:18 2017 e554c3x251-nlc-88601 Available
Tue Dec 26 19:19:28 2017 Finished
Tue Dec 26 19:19:28 2017 Elapsed 0 minutes (17 seconds)
```

## 動作環境

- Windows 10
- perl 5.26
- curl 7.53.1 

(追記) スクリプトから呼んでいますのでWindows環境の人はcurlのパス通しておきましょう。

# 4. まとめ

いつになったら学習おわるのかなーとイライラすることがこれでなくなりました。
**作ったもので手間が減るのはほんとにうれしい**ですね。
macなら `./wait.pl && say "hello"` とかすると終わったら hello って言ってくれるかもしれません。