Edited at

DeepLearningの学習経過をslackで受け取れるようにする

More than 1 year has passed since last update.


モチベーション

DeepLearningの学習って結構時間がかかりますよね? (数時間とか、ヘタしたら数日とか・・・)

手元のPCで学習が回っていて常に状況を確認できるならともかく、リモートのPCで学習は回っていたり、大体時間がかかるからってどっかに出掛けてみたりするじゃないですか。

でも学習が進んでいるかどうかは定期的に知りたい。もっと言えば、エラーで途中終了しているのはすぐにでも知りたい(学習終わってるかな?って思って見てみたら止まってた・・・(泣)みたいなのは避けたい)。

そこで、Slackのpython-slackclientを使って学習結果をslackのchannelに定期的に通知する方法を紹介します。

前半は単純にpython-slackclientの簡単な使い方、後半はchainerのExtensionとしてこのSlack通知機能を実装する方法を説明します。

こんな感じを目指します↓

slack-bot.png


python-slackclientの使い方


0. Slackのワークスペースを作成 (Optional)

Slackのアプリ(Android, iPhone, Mac, Windows, Linuxどれでも!)からとりあえず適当にワークスペースを作ります。

ワークスペースの作り方がわからない場合はこちらの本家のページを見てみてください。


1. 通知をするためのslack appを作成する

ここから、Create a slack appをクリックして指示にしたがって新しくアプリを作ります。build-slack-app.png


2. 作ったslack appにメッセージの送信を許可する

アプリ作成後、アプリの管理画面から、FeaturesにあるOAuth & Permissionsを選択します。

manage-app.png

その後、真ん中くらいにあるSelect Permission Scopesを表示し、選択画面から、send messages as [作ったappの名前] chat:write:botを選択、設定を保存します。


3. tokenの取得

今回はSlackのWebapiをpythonから使うので、そのためのtokenを取得します。

2と同じく、OAuth & Permissionsから、一番上にあるOAuth Access Tokenをコピーして適当に保存します。

ここではslack_tokenという名前のファイルに保存したとして話を進めます。


4. channelにメッセージを送ってみる

ためしに、tokenを使ってメッセージが送れるか確認します。これができたら基本的な設定は終了です。

まずslackのwebapiをpythonから使えるようにするためslackclientを入れます

pip install slackclient

あとは次のお試しコードを使ってメッセージが送信できればokです。(channelは適宜変えてください。とりあえずgeneralに送るようになっています)

from slackclient import SlackClient

def send_message(client, text, channel):
client.api_call(
"chat.postMessage",
channel=channel,
text=text
)

def read_text(filename):
with open(filename, 'r') as f:
return f.read()

def main():
token = read_text('slack_token')

sc = SlackClient(token)
text = "Hello from Python! :tada:"
channel = "general"
send_message(sc, text, channel)

if __name__ == '__main__':
main()


chainerのExtensionとして実装する

ここまでで、slackにメッセージを送れるようになったので、ログ出力の要領で使えばもうやりたいことはだいたいできるのですが、chainerのExtensionとして実装しておくと、chainerのtrainerを使った時に便利なので、Extensionとして実装する方法を説明します。


Extensionとは?

chainerのtrainerを使って学習をさせるときに、学習経過に合わせてパラメータを変更したり、ログをとったり、今回のようにメッセージを送ったりするためにtrainerに付け足すことのできる拡張機能のようなものです。

具体的なtrainerとextensionの関係はchainerのmnistのチュートリアルがわかりやすいので見てみてください。


Slackにメッセージを送るExtensionを作成する

Extensionの作り方はfunctionを拡張するものやExtensionクラスを継承するものなどいくつかあるようですが、ここでは後者のExtensionを拡張する形で、slackにメッセージを送るExtensionの作り方を説明します。


extionsion.Extensionを拡張したクラスをつくる

chainer本体にあるPrintReportを参考に、SlackReportというクラスを作ります。(名前は適宜変更してください)

ここでは、entriesで指定したログをslackに送信するために、指定されたkeyを使ってheaderの作成と、実際に出たログの長さに合わせて表示するためのtemplateを作成しています。

from chainer.training import extension

from chainer.training.extensions import log_report as log_report_module
from slackclient import SlackClient

class SlackReport(extension.Extension):
def __init__(self, token, entries, channel='training report', log_report='LogReport'):
self._slack_client = None if token is None else SlackClient(token)
self._entries = entries
self._channel = channel
self._log_report = log_report

# format information
entry_widths = [max(10, len(s)) for s in entries]

self._header = ' '.join(('{:%d}' % w for w in entry_widths)).format(
*entries) + '\n'

templates = []
for entry, w in zip(entries, entry_widths):
templates.append((entry, '{:<%dg} ' % w, ' ' * (w + 2)))
self._templates = templates


学習開始、終了時にメッセージを送信する。

trainerは学習開始と終了時に必ずすべてのextensionのinitialize(trainer), finalize()メソッドを呼ぶようになっているので、これを利用して学習開始、終了をslackに通知します。

特にfinalizeを実装しておくことで、学習が終了したタイミングを知ることができるのでおすすめです。(Exceptionによる異常終了時も含む)

ここで使っているsend_messageは前半部分で説明したslackclientの使い方で書いたものと同じです

    def initialize(self, trainer):

send_message(client=self._slack_client, text='--------training started--------', channel=self._channel)

def finalize(self):
send_message(client=self._slack_client, text='--------training finished--------', channel=self._channel)


定期的にメッセージを送信する

trainerはextensionを追加したときに指定するtriggerの間隔に合わせて、extensionを呼ぶので、呼ばれた時にslackにログのメッセージを送るようにします。

ポイントはメッセージをひとつのtextとして連結して、send_messageでslackに通知するようにしておくことです。

    def __call__(self, trainer):

if self._slack_client == None:
return

log_report = self._log_report
if isinstance(log_report, str):
log_report = trainer.get_extension(log_report)
elif isinstance(log_report, log_report_module.LogReport):
log_report(trainer) # update the log report
else:
raise TypeError('log report has a wrong type %s' %
type(log_report))

message = self._header
observations = log_report.log
for observation in observations:
for entry, template, empty in self._templates:
if entry in observation:
message += template.format(observation[entry])
else:
message += empty
message += '\n'
send_message(client=self._slack_client, text=message, channel=self._channel)


trainerにExtensionを追加する

最後にtrainerにExtensionを追加して、slackに通知が行くようにしましょう。

追加は簡単でtrainerのextendで先ほど作ったSlackReportを指定するだけです。

このときtriggerを指定しないと毎iterationごとに呼ばれるようになってしまって、通知の嵐になるので、適当に(イテレーション数, 'iteration')や(エポック数, 'epoch')といったように指定して、ある程度学習が進んだタイミングで通知が来るようにしておくと便利です。

    trainer.extend(SlackReport(args.token,

entries=['iteration', 'main/loss','validation/main/loss', 'elapsed_time'],
channel='general'),
trigger=(100, 'iteration'))


おわりに

いかがでしたでしょうか?簡単に通知が送れるのがわかったかと思います。

みなさんもぜひこのslack通知機能を取り入れて快適なdeeplearningライフを送ってみてはいかがでしょうか。