asyncio なんもわからんから オライリー買って抜粋和訳してみた part 2 ~ asyncio の塔からコルーチン ~

tl;dr

asyncio なんもわからんからこれを買って、私にとって重要そうな場所のみを抜粋和訳してみたよ!

part1

asyncio の塔

前のセクション(part1)で見たように、エンドユーザ開発者(=フレームワーク開発者でない)として asyncio を使うために知っておく必要のあるコマンドはわずかです。残念なことに asyncio のドキュメントは膨大な数の API を提示していますが、フラットなフォーマットで書かれているためで、一般的な使用を目的としたもので、エンドユーザ向けかがわかりづらいです。フレームワーク設計者が同じドキュメントを見る場合は新しいフレームワークサードパーティのライブラリと接続するためのフックポイントを探すために膨大なAPIがあります。このセクションでは、フレームワーク設計者の目を通して asyncio を見て、新しい非同期互換ライブラリの構築にどのようにアプローチするかを理解します。表3-1 は階層構造になっているので理解の助けになるかと思います。

Level Concept Implementation
Tier 9 Network: Streams StreamReader, StreamWriter, asyncio.open_connection(), asyncio.start_server()
Tier 8 Network: TCP&UDP Protocol
Tier 7 transports BaseTransport
Tier 6 Tools asyncio.Queue
Tier 5 Subprocesses & threads run_in_executor(), asyncio.subprocess
Tier 4 Tasks asyncio.Task, asyncio.create_task()
Tier 3 Futures asyncio.Future
Tier 2 Event Loop asyncio.run(), BaseEventLoop
Tier 1 Coroutines async def, async with, async for, await

Tier1 には、コルーチンがあります。非同期フレームワークが他に2つあり、 Curio と Trio が上げられます。どちらも Python のネイティブコルーチンにのみ依存しており、 asyncio ライブラリは利用しておりません。

Tier2 はイベントループです。コルーチンはそれ自体ではなんの役にもたたず、実行のためのループがなければ何もしません。(Curio と Trio は独自のイベントループを実装します)使用と実装が明確に分離されているので、サードパーティの開発者がイベントループのだいたい実装を行うことも可能です。Tier3 と 4 は future と task があり、これらは密接に関連しています。task は future のサブクラスであるために分類されていますがある意味で同じ階層にあるとも言えます。task はイベントループ上で実行されるコルーチンを表します。簡単に言うと、 future はループ対応ですが、 task はループ対応とコルーチン対応の両面を持っています。

Tier 5 は別のスレッド、あるいは別のプロセスで実行されなければならない作業を起動したり待機したりするための機能を表しています。

Tier 6 はasyncio.Queue のような追加の async 対応ツールです。asyncio が提供するキューは、キューモジュールのスレッドセーフキューと非常によく似た API を持っており、 asyncio バージョンでは get() と put() でawait キーワードを必要とすることを除けば、かなり似ています。queue.Queue を直接コルーチンで使うことはできません。最後に Tier7 ~ 9 ですが、エンドユーザが最も利用しやすいのが Tier9 のストリーム API です。Tier8 は 9 をより詳細にしたもので、Tier7 はトランスポート層で、他の人が使用するフレームワークを作成して、トランスポートの設定方法をカスタマイズする必要がない限り、触る必要はほとんどありません。Quickstart ではasyncio ライブラリを使い始めるために必要な最低限の知識を見てきましたが、ここから asyncio ライブラリの API 全体がどのようにまとめられているかを見ます。

コルーチン

async def キーワード

3-4

async def f():
    return 123
print(type(f))
import inspect
print(inspect.iscoroutinefunction(f))

実行結果

<class 'function'>
True

これが最もシンプルなコルーチンの宣言ですが、fの正確な型は "coroutine "ではありません。async def関数をコルーチンと呼ぶのが一般的ですが、厳密にはPythonではコルーチン関数と考えられています。この動作は、Pythonでのジェネレータ関数の動作と同じです。

generator の例

def g():
    yield 123
print(type(g))
gen = g()
print(type(gen))

実行結果

<class 'function'>
<class 'generator'>

g が「ジェネレータ」と誤って呼ばれることがありますが、g は関数であることに変わりはなく、この関数が評価されて初めてジェネレータが返されます。コルーチン関数は全く同じように動作します。コルーチンオブジェクトを取得するには、async def関数を呼び出す必要があります。 標準ライブラリの inspect モジュールは、組み込みの type() 関数よりもはるかに優れたイントロスペクティブ機能を提供することができます。 通常の関数とコルーチン関数を区別するための iscoroutinefunction() 関数があります。

先程の f 関数のつづき

async def f():
    return 123
coro = f()
print(type(coro))
print(inspect.iscoroutine(coro))

実行結果

<class 'coroutine'>
True

ここで元の質問に戻ります。コルーチンとは、完了前に中断されていた基礎となる関数を再開する機能をカプセル化したオブジェクトです。聞き覚えがあるかもしれませんが、コルーチンはジェネレータに非常に似ているためです。実際、Python 3.5 で async def と await キーワードを持つネイティブのコルーチンが導入される前は、Python 3.4 で特別なデコレータを持つ通常のジェネレータを使用することで、すでに asyncio ライブラリを使用することが可能でした。Python がどのようにそれらを利用しているかを見るために、もう少しコルーチンオブジェクトを使って遊ぶことができます。最も重要なことは、Pythonがどのようにしてコルーチン間で実行を "切り替える "ことができるのかを見たいということです。まず、戻り値がどのようにして得られるのかを見てみましょう。コルーチンが戻ると、実際に何が起こるかというと、StopIteration例外が発生します。先ほどの例と同じセッションを続けている3-6は、そのことを明確にしています。

3-6

async def f():
    return 123
coro = f()
try:
    coro.send(None)
except StopIteration as e:
    print('The answer was:', e.value)

実行結果

The answer was: 123

コルーチンは、Noneを「送信」することで開始されます。内部的には、これがイベントループがコルーチンに対して行うことになりますので、これを手動で行う必要はありません。あなたが作成したすべてのコルーチンは、loop.create_task(coro)かawait coroのどちらかで実行されます。.send(None)を裏で実行するのはループです。 coroutineが戻ってくると、StopIterationと呼ばれる特殊な種類の例外が発生します。例外自体のvalue属性を使って、コアーチンの戻り値にアクセスできることに注意してください。繰り返しになりますが、このように動作することを知る必要はありません。エンドユーザから見ると、async def関数は、通常の関数と同じように、単にリターン文で値を返すだけです。 send()とStopIterationの2つのポイントは、それぞれ実行中のコルーチンの開始と終了を定義します。これまでのところ、これは関数を実行するための本当に複雑な方法のように見えましたが、それでも構いません。イベントループは、これらの低レベルの内部構造を持つコルーチンを駆動する役割を果たします。エンドユーザは、ループ上で実行するためにコルーチンをスケジュールするだけで、通常の関数のようにトップダウンで実行されます。次のステップでは、どのようにしてコルーチンの実行を中断できるかを見てみましょう。

await キーワード

この新しいキーワード await は常にパラメータを取り、下記のどちらか(AND や OR ではなく XOR)で定義される awaitable と呼ばれるものだけを受けつけます。

  • コルーチン( つまりasync def で定義された関数を呼び出した結果)
  • await() を実装した特殊なオブジェクト。 await() メソッドはイテレータを返さなければなりません。

2 つ目の await() を実装したオブジェクト)は、この本の対象外ですが(日々の asyncio プログラミングでは決して必要ないでしょう)、最初のユースケースは、例 3-7 で示されているように、非常に簡単です。

3-7 using await on a coroutine ※一部私が改変

import asyncio
async def f():
    await asyncio.sleep(1.0)
    return 123
async def main():
    result = await f() # await を指定した場合は 123 の int オブジェクトが格納される
    # result = f() # 仮にこっちだった場合は result はコルーチンオブジェクト
    print(result)
    print(type(result))
    return result
asyncio.run(main())

実行結果

123
<class 'int'>

f() をした場合はコルーチンが生成されます。それは実行の完了を待つことが許可されていることを意味しています。 result には f() が完了した際には 123 の値の int オブジェクトが格納されます。

このセクションを終わってイベントループに移る前に、コルーチンにどのようにして例外が与えられるかを見ておくと理解が進みます。これは一般的にタスクのキャンセルに用いられ、task.cancel() した際、イベントループは内部的に coro.throw() を使用して、コルーチンの内部で asyncio.CancelelledError を発生させます(例 3-8)

3-8 Using coro.throw() to inject exceptions into a coroutine ※一部私が改変

import asyncio
async def f():
    print('start sleep')
    await asyncio.sleep(5.0)
    print('end sleep')
    return 123

coro = f() # コルーチン関数 f() から新しいコルーチンが作成
coro.send(None)
coro.throw(Exception, 'Blah') # 別の send() を行う代わりに throw() を呼び出し、例外クラスと値を指定します。これにより、コルーチン内の待機ポイントで例外が発生します。 throw() メソッドは、タスクのキャンセルに (asyncio の内部で) 使用されます。

実行結果

start sleep
Traceback (most recent call last):
  File "***/main.py", line 10, in <module>
    coro.throw(Exception, 'Blah')
  File "***/main.py", line 4, in f
    await asyncio.sleep(5.0)
  File "***\asyncio\tasks.py", line 595, in sleep
    return await future
Exception: Blah

5 秒の sleep の間に例外を発生させ、処理をストップさせることに成功しています。

例3-9では、新しいコルーチンの中でキャンセルを処理することにします。

例 3-9. Coroutine cancellation with CancelledError ※一部私で改変

import asyncio
async def f():
    try:
        while True:
            print('f exec')
            await asyncio.sleep(0) # pause 的な役割をし、 イベントループが戻ってくると次に進む模様
            print('f executed')
    except asyncio.CancelledError: # 注 1
        print('I was cancelled!') # 注 2
    else:
        return 111
coro = f()
coro.send(None)
coro.send(None)
coro.throw(asyncio.CancelledError) # 注 3

実行結果

Traceback (most recent call last):
  File "***/main.py", line 15, in <module>
    coro.throw(asyncio.CancelledError)
StopIteration # 注 4
f exec
f executed
f exec
I was cancelled!# 注 5
  1. コルーチン関数は例外を扱えるようになりました。実際にはタスクキャンセルのために asyncio ライブラリ全体で使用されている特定の例外タイプである asyncio.CancelledError を処理します。この例外は、外部からコルーチンに注入されていることに注意してください。実際のコードでは、後述されていますが、タスクがキャンセルされると、 CanceledError はタスクがラップされたコルーチンの内部で発生します。
  2. タスクがキャンセルされたことを伝える出力です。例外を処理することで、例外が伝搬しなくなり、コルーチンが戻ってくることを保証していることに注意してください。
  3. CancelledError 例外を throw() しています。
  4. これで、コルーチンは正常に終了します。(すでに説明したとおり、StopIteration 例外は、コルーチンが終了する正常な方法です)
  5. 予想通りキャンセルされています

タスクのキャンセルがどのように通常の例外の発生(および処理)以外の何物でもないかという点について、例 3-10 を見てみましょう。

3-10 For educational purpose only - don't do this !

import asyncio
async def f():
    try:
        while True: await asyncio.sleep(0)
    except asyncio.CancelledError:
        print('Nope!')
        while True: await asyncio.sleep(0) # 注1
    else:
        return 111
coro = f()
coro.send(None)
coro.throw(asyncio.CancelledError) # 注 2
coro.send(None) # 注 3

実行結果

Nope!
  1. メッセージを出力する代わりにキャンセルした後、待機状態に戻ると何が起こるのでしょうか?
  2. 当然のことながら外側のコルーチンは行き続け、すぐに新しいコルーチンでサスペンドします。
  3. 全てが正常に進行し、コルーチンは予想道理にサスペンドし、再開し続けます。

もちろんこれを実際にはやってはいけないことだというのは言うまでも有りません。もしあなたのコルーチンがキャンセル信号を受信した場合は、それは必要なクリーンアップだけを行って終了するように、との明確な指示であり、無視してはいけません。 全ての .send(None) を手動で行ってイベントループを行うのは辛いので、 3-11 では asyncio で提供されているループを利用し、それに応じて3-10をクリーンアップします。

3-11 Using the event loop

import asyncio
async def f():
    await asyncio.sleep(0)
    return 111
loop = asyncio.get_event_loop()
coro = f()
print(loop.run_until_complete(coro)) # コルーチンを完了するまで実行します。内部的には、.send(None)メソッドの呼び出しをすべて行っています。

出力結果

111

asyncio なんもわからんから オライリー買って抜粋和訳してみた part 1 ~序章から Quickstart ~

tl;dr

asyncio なんもわからんからこれを買って、私にとって重要そうな場所のみを抜粋和訳してみたよ!

序章

スレッドと asyncio の違いや、良さ悪さ、使い所について書いてある。

私の理解だと、複数の似たようなタスクがあり、ただしCPUを消費するものではなく大半は待機時間のような、ネットワークを介した処理は asyncio が向いている。

例えば、request.get(URL)みたいな処理を 10000 回繰り返したい場合、get してからその結果が帰ってくるまではデータのダウンロード時間が大半であり、CPUはアイドル状態である。 1 サイト 1 秒ダウンロードかかる場合は 10000 秒かかってしまうわけだが、CPUはその間ほとんど遊んでしまっている。 その場合、回線などの問題はさておき、10000 回分の get を同時に実行すれば早くなる。 asyncio を使わないとマルチスレッドで動かせばできるが、スレッドはオーバヘッドが大きく、スレッドの数はそんなに増やせない (10 個くらいとか?)。 そんなときは asyncio でイベントドリブンな処理にすると(例えば)シングルスレッドで get を非同期に実行し、結果が帰ってきたら次の処理をトリガーのように仕込むことをすれば、 効率よく処理が回せる。

そんなことを言っている。気がする。

(正直たとえ話が長くて読むのが飽きた)

Quickstart

  • 7 つの関数を知っておくだけで、通常利用に限って asyncio を使うことができる
  • asyncio はフレームワーク設計者向けであるが、エンドユーザが知っておくべきなのは以下にまとめられる
    • asyncio のイベントループの開始
    • async/await 関数の呼び出し
    • イベントループ上で実行するタスクの作成
    • 同時に実行しているタスクの完了待機
    • 同時実行タスクが完了後のイベントループのクローズ これらのコアの機能を見て、 Python のイベントベースのプログラミングを使ってループを実行する方法を見る。

asyncio の hello world はこんなコード(私が多少改変)

3-1 quickstart.py

import asyncio, datetime

async def main():
    print(datetime.datetime.utcnow(),end=' : ')
    print('Hello!')
    await asyncio.sleep(1.0)
    print(datetime.datetime.utcnow(),end=' : ')
    print('GoodBye!')

asyncio.run(main())

実行結果

2020-12-30 02:42:22.451943 : Hello!
2020-12-30 02:42:23.453100 : GoodBye!

asynciorun() を提供しており、 async def で定義された関数や他のコルーチンを呼び出すことができる。 コルーチンについてはこちらの公式ドキュメントに記載があるが、 async 構文で定義された関数と思っていてよさそう。

hello の後、1 秒間の sleep を経て GoodBye が表示されていることを確認できた。

asyncio.run() が何をするものなのか、というのは高レベル API なので、実際にどんなことをやっているのかを、完全には等価ではないが、イメージのために記載すると、 3-2 のような処理を行っている。 3-2 のコードは本の残りの部分を読んでいく上での考え方を紹介するのに十分近いものである。

3-2 quickstart.py

import asyncio, datetime

async def main():
    print(datetime.datetime.utcnow(),end=' : ')
    print('Hello!')
    await asyncio.sleep(1.0)
    print(datetime.datetime.utcnow(),end=' : ')
    print('GoodBye!')

# asyncio.run(main())

loop = asyncio.get_event_loop()
task = loop.create_task(main())
loop.run_until_complete(task)
pending = asyncio.all_tasks(loop=loop)
for task in pending:
    task.cancel()
group = asyncio.gather(*pending, return_exceptions=True)
loop.run_until_complete(group)
loop.close()

実行結果

2020-12-30 02:53:14.428308 : Hello!
2020-12-30 02:53:15.428343 : GoodBye!

同じように Hello の 1 秒後に GoodBye が表示されているのが確認できる。

ここから各処理を見ていく。

loop = asyncio.get_event_loop()

どのコルーチンを実行するにも、 loop インスタンスが必要。1 つのスレッドだけを使用している限り、get_event_loop() は、どこでも毎回同じループインスタンスを取得できます。 もし async def で定義した関数の中からループインスタンスを取得したい場合は、代わりに asyncio.get_running_loop() を呼び出すべき。後ほど解説します。

task = loop.create_task(coro)

これを実行するまでコルーチンは実行されません。返されたタスクオブジェクトは、タスクの状態(例えばタスクがまだ実行中なのか、完了したのか)を監視するために使用することができ、完了したコルーチンから返り値を取得するためにも使用できます。タスクをキャンセルするには task.cancel() を使用します。

※coro はコルーチンを指している

loop.run_until_complete(coro)

run_until_complete() をコールすると、現在のスレッドをブロックします。run_until_complete() は与えられたコルーチンが完了するまでループを実行し続けることに注意してください。 ループ上でスケジュールされている他のすべてのタスクは、ループが実行されている間も実行されます。

group = asyncio.gather(task1,task2,task3,…)

プロセスシグナルを受信したか、 あるいは loop.stop() でループが停止したかで、プログラムの main 部分がアンブロックされると、 run_until_complete() の後のコードが実行されます。 ここで示している標準的な文法は、保留中のタスクを集めてキャンセルし、それらのタスクが完了するまで loop.run_until_complete() を再び使用するというものです。asyncio.run() はキャンセル、ギャザリング、保留中のタスクの終了待ちを全て行うことに注意してください。

loop.close()

loop.close() は通常最後に行うもので、停止した loop インスタンスで呼び出されなければならず、すべてのキューをクリアして Executor をシャットダウンします。停止した loop は再開できますが、 close した場合は消えます(再開できない)

3-1 では asyncio.run() を使用すれば、これらのステップはどれも必要ないことを示していますが、これらのステップを理解することは重要です。なぜなら実際はもっと複雑な状況が出るので、それらに対処するための知識が必要だからです。

基本的な機能として、最後に知るべきはブロッキング関数を実行する方法です。協調的なマルチタスクについては全てのIOバウント関数が必要です。 asyncio は concurrent.future パッケージの API と非常によく似た API を提供しています。このパッケージは ThreadPoolExecutor と ProcessPoolExecutor を提供します。デフォルトはスレッドベースですが、プールベースも選択可能です。先程の例では Executor を省略しましたが、今回は見てみましょう。

3-3 The Basic executor interface

import asyncio, datetime
from time import sleep

async def main():
    print(datetime.datetime.utcnow(),end=' : ')
    print('Hello!')
    await asyncio.sleep(3.0)
    print(datetime.datetime.utcnow(),end=' : ')
    print('GoodBye!')

def blocking():
    print('Start Blocking!')
    sleep(0.5)
    print('GoodBye Blocking!')

loop = asyncio.get_event_loop()
task = loop.create_task(main())
loop.run_in_executor(None, blocking)
loop.run_until_complete(task)

pending = asyncio.all_tasks(loop=loop)
for task in pending:
    task.cancel()
group = asyncio.gather(*pending, return_exceptions=True)
loop.run_until_complete(group)
loop.close()

実行結果

Start Blocking!
2020-12-30 05:08:40.774532 : Hello!
GoodBye Blocking!
2020-12-30 05:08:43.777301 : GoodBye!

blocking() は従来の(asyncio ではない) sleep() を内部的に呼び出しています。これはこの関数をコルーチンにしてはいけないということを意味します。この問題を解決するには、この関数を Executor で実行する必要があります。このセクションとは関係ないが、blocking の中で実行している sleep より main で実行している sleep (非 blocking ) のほうが長いことに注意してください。blocking の sleep のほうが長い場合、run_until_complete(main())により、main が終わったら次の処理に行き、blocking が途中にも関わらず loop が close されてしまうため、エラーが発生します。つまり、run_in_executor( はメインスレッドをブロックするわけではないということです。

今までasyncio の本質的な部分を見てきましたが、次に範囲を広げて、asyncio API を階層に整理してみましょう。

~つづく(はず)~

「テキスト解析 AI サービス Amazon Comprehend で本を読まずに読書感想文に挑戦してみる」で書けなかったこと

tl;dr

記事書いたけど諸事情で割愛した文章だよ!

書けなかった内容

会社でブログを書く、という初体験をしました。

aws.amazon.com

実はここには、諸事情があって書けなかった内容があって、

現代文の先生に見せてボコボコにしてもらったよ!というお話です。


後日談

せっかくなので、予備校の現代文講師(匿名希望)の方に読んで感想をいただきました! ひとしきりコメントを頂いたのですが、今回のテーマは「読まずに読書感想文を書いてみる」だったので、 ついでに「対談せずに対談っぽく」を書いてみました。

私「本日はありがとうございます。」

講師「いやー、面白いね。」

私「ありがとうございます。」

講師「まず、読まずに読書感想文って発想がいいよね。怠け者の発想。」

私「…」

講師「ただね、…いや、まずは褒めよう」

私「…」

講師「まずエンティティを抽出することで3人の主要人物を絞り込んだ点は評価できる」

私「(おお…!)」

講師「ただし、要約は本文のあらすじを完結にまとめたもの、読書感想文は読み手の読後感をまとめて表明したもの、と仮に区別するならば…」

私「(うっ…)」

講師「前者の要約の要素がかなり強い。一応感想らしくものは書かれているが、刹那的でかつ『破片』的、『印象』のレベルを超えていない」

私「(確かに十分にイメージできるとか、当たり障りのないことしか言ってない…)」

講師「さらに言うならば、小説はエンティティが一度出たら、以後人称代名詞や別称で話を展開し、そこに新情報を加えていく。」

私「(あ、代名詞とかは完全に無視していた…)」

講師「つまり、「メロス」や「セリヌンティウス」「王」についても作品内容に大きくかかわることは指示代名詞や具体的名称の2度目、3度目・・・のあとに重要のことが加えられる。この読書感想文にはそこが考慮されて いなく、この点が感想が表面的になってしまうことの一因とも考えられる。」

私「おっしゃるとおりですね…。」

講師「感想文の重要構成要素である①読み手の作品への関心や主題把握がどのようなものか、②作品のどの部分を重要視して感想を述べているかが今後の課題になるだろう。」

私「ありがとうございます。」

たしかに読書感想文においては固有名詞は何度も出現せず、代名詞で以後展開することも多いですね。読書感想文を書くときはこれらも考慮するとよさそうです。

とはいえ、 Amazon Comprehend を用いてストーリーの主要部分の理解、主人公の抽出が出来たことは間違いないようです。

みなさまも読書感想文を書く時は上記に留意して書きましょう!

講師の方、本当にありがとうございました!


もろもろさておき、読書感想文はちゃんと読書して書きましょうね。

読書感想文がスラスラ書ける本 小学1・2年生

読書感想文がスラスラ書ける本 小学1・2年生

Amazon Transcribe のジョブをバルスする魔法の呪文

Amazon Transcribe はジョブ名が一意でないといけないのだが、

同じジョブ名を使いまわしたいことが多いので、

全消去を雑にやることが多いのに、よく忘れてググるので備忘的に。

import boto3
client = boto3.client('transcribe')
while True:
    job_list = client.list_transcription_jobs()['TranscriptionJobSummaries']
    if job_list == []:
        break
    for job in job_list:
        job_name = job['TranscriptionJobName']
        client.delete_transcription_job(TranscriptionJobName=job_name)

Amazon Transcribe Developer Guide

Amazon Transcribe Developer Guide

木構造の幅優先探索・深さ優先探索コピペ用

tl;dr

AtCoder木構造幅優先探索深さ優先探索が出た場合のコピペ用ソースコードを記す。

解けなかった問題

これ

atcoder.jp

幅優先探索+色探索を実装していたらTLEしか出ませんでした…。

幅優先探索

木構造があったとき、こんな感じでとけます。

pythonのdefaultdictで表現します。

# Atcoderでよく与えられる形
T_list = [[1,2],[2,3],[3,4],[3,5],[2,7],[7,8],[7,9]]
# defaultdictでtreeを表現
from collections import defaultdict as ddict
tree = ddict(list)
for t in T_list:
    tree[t[0]].append(t[1])
print(tree)

こんな感じで表現されます。

defaultdict(<class 'list'>, {1: [2], 2: [3, 7], 3: [4, 5], 7: [8, 9]})

幅優先探索関数

pythonのdequeでやります。

from collections import deque
def bfs(T,i):
    Q = deque([i])
    print(i)
    while Q:
        q = Q.popleft()
        for c in T[q]:
            print(c)
            Q.append(c)

使い方は第一引数に木を、第二引数にどこから探索するかを入れます。

# 1から探索
bfs(tree,1)

結果は下記の通り

1
2
3
7
4
5
8
9

ちゃんと階層が浅い順に探索されています。

深さ優先探索

今回の問題ではなかったのですが、ついでに深さ優先探索も。

深さ優先探索関数

再帰を利用して実装。

def dfs(T,i,init=True):
    if init==True:
        print(i)
    Q = deque([i])
    q = Q.popleft()
    for c in T[q]:
        print(c)
        dfs(T,c,False)

使い方は幅優先探索と一緒だけれども、

探索開始地点の出力のために、

第三引数を用意します。(初回のみTrueで出力)

from collections import deque
def dfs(T,i,init=True):
    if init==True:
        print(i)
    Q = deque([i])
    q = Q.popleft()
    for c in T[q]:
        print(c)
        dfs(T,c,False)

こんな感じで使えます。

dfs(tree,1)

結果もちゃんと深さ優先で出ています。

1
2
3
4
5
7
8
9

Pythonで学ぶアルゴリズムとデータ構造 (データサイエンス入門シリーズ)

Pythonで学ぶアルゴリズムとデータ構造 (データサイエンス入門シリーズ)

  • 作者:辻 真吾
  • 出版社/メーカー: 講談社
  • 発売日: 2019/11/28
  • メディア: 単行本(ソフトカバー)

組み合わせ数(nCr)の答えにmodを用いる際の関数のコピペ用

tl;dr

atcoder.jp

この問題で、nCrで10**9+7で割ったあまりを、求める部分で、 nCrの関数をこれにしたらうまく回ったよ、って話。


def nCr(n, r, MOD):
    if n < r:
        return 0
    if n-r < r:
        r = n-r
    comb = 1
    for x in range(n-r+1, n+1):
        comb = (comb * x) % MOD
    d = 1
    for x in range(1, r+1):
        d = (d * x) % MOD
    comb *= pow(d, MOD-2, MOD)
    return comb % MOD

参考、というか関数丸パクリした提出がこちら。

atcoder.jp

場合の数 1―作業性の特訓 書き上げて解く順列 (思考力算数練習張シリーズ 23)

場合の数 1―作業性の特訓 書き上げて解く順列 (思考力算数練習張シリーズ 23)

ことのはじまり

ABC145のD問題"Knight"で、

見覚えある数字の分布だなぁ、と気づいたのが11/16 22:00の時点。

これが、パスカルの三角形であり、パスカルの三角形はnCrと三角形の座標(N段目のM番目)を知ることができれば解けると知ったのが22:20の時点。

座標はX+=1,Y-=1し続けて(逆でもよい)、

2 * Y == Xとなった場所と、

その操作をした回数でN,Mを出せるのこともわかり、

残りはnCrだ・・・と思って、気合入れてググりまくり、

拾ってきたソースをちょっと改変して、出したのがこのソース

atcoder.jp

testcaseも全部時間内に通って、残り6分ちょっと、なんとかD突破・・・と思ったら、

1ケースでWAが出て通らなかったのが昨日のハイライト。

ぐぬぬ

nCrの恐怖

組み合わせ数の計算で、nが100000とか超えてくると値が爆発します。

なので、出題者はだいたい10**9 + 7で割った余り、など言ってくることが多いです。

しかしnCrの場合は割り算があり、pythonは巨大数の割り算は精度が危ういため、

よく死ぬことがある。

んで、参考にしたnCr(mod対応版)を用いて、

そこだけ差し替えたら通ってしまったとさ・・・。

ぐぬぬ

train_xを減らしてmnistでval_accを0.02%伸ばす方法

TL;DR

trainデータで間違えるところだけを学習するとちょっと0.02%伸びたよ

やったこと

mnistでモデルを作っていると、

学習率を落としても、数値が上がりそうで上がらず振幅して悲しいことが多いと思いますが、

train_xを工夫すると若干(0.02%)val_accを向上させられたので、ご紹介。

まず普通にモデル作成

こんなモデルを作りました。

# 乱数固定おまじない
import os
import numpy as np
import random as rn
import tensorflow as tf

os.environ['PYTHONHASHSEED'] = '0'
np.random.seed(0)
rn.seed(0)

session_conf = tf.ConfigProto(
    intra_op_parallelism_threads=1,
    inter_op_parallelism_threads=1
)

from keras import backend as K

tf.set_random_seed(0)
sess = tf.Session(graph=tf.get_default_graph(), config=session_conf)
K.set_session(sess)

from keras import datasets
import numpy as np
((train_x,train_y_org),(test_x,test_y)) = datasets.mnist.load_data()
train_x = (train_x/255).astype("float32").reshape(-1,28,28,1)
test_x  = (test_x /255).astype("float32").reshape(-1,28,28,1)
train_y = np.eye(10)[train_y_org]
test_y = np.eye(10)[test_y]

from tensorflow.keras.layers import *
from tensorflow.keras.models import *
from tensorflow.keras.optimizers import Adam
from tensorflow.keras.callbacks import ModelCheckpoint
from tensorflow.keras.losses import categorical_crossentropy

cp_cb = ModelCheckpoint(filepath = "model_{epoch:02d}.model")

model = Sequential()
model.add(Conv2D(filters=32,kernel_size=(3,3),input_shape=(28,28,1),padding="same"))
model.add(PReLU())
model.add(Dropout(0.15))
model.add(BatchNormalization())
model.add(Conv2D(filters=32,kernel_size=(3,3),padding="same"))
model.add(PReLU())
model.add(Dropout(0.15))
model.add(BatchNormalization())
model.add(MaxPool2D(pool_size=(2,2),padding="same")) # 28,28 -> 14,14
model.add(Conv2D(filters=64,kernel_size=(3,3),padding="same"))
model.add(PReLU())
model.add(Dropout(0.15))
model.add(BatchNormalization())
model.add(Conv2D(filters=64,kernel_size=(3,3),padding="same"))
model.add(PReLU())
model.add(Dropout(0.15))
model.add(BatchNormalization())
model.add(MaxPool2D(pool_size=(2,2),padding="same")) # 14,14 -> 7,7
model.add(Conv2D(filters=64,kernel_size=(3,3),padding="same"))
model.add(PReLU())
model.add(Dropout(0.15))
model.add(BatchNormalization())
model.add(Conv2D(filters=64,kernel_size=(3,3),padding="same"))
model.add(PReLU())
model.add(Dropout(0.15))
model.add(BatchNormalization())
model.add(MaxPool2D(pool_size=(2,2),padding="same")) # 7,7 -> 4,4
model.add(Conv2D(filters=64,kernel_size=(3,3),padding="same"))
model.add(PReLU())
model.add(Dropout(0.15))
model.add(BatchNormalization())
model.add(Conv2D(filters=64,kernel_size=(3,3),padding="same"))
model.add(PReLU())
model.add(Dropout(0.15))
model.add(BatchNormalization())
model.add(MaxPool2D(pool_size=(2,2),padding="same")) # 4,4 -> 2,2
model.add(Conv2D(filters=128,kernel_size=(2,2),padding="same"))
model.add(PReLU())
model.add(Dropout(0.15))
model.add(BatchNormalization())
model.add(MaxPool2D(pool_size=(2,2),padding="same")) # 2,2 -> 1,1
model.add(Flatten())
model.add(Dense(10,activation="softmax"))
model.summary()

model.compile(optimizer=Adam(lr=0.0001),metrics=['accuracy'],loss="categorical_crossentropy")

model_history = model.fit(train_x,train_y,batch_size=16,epochs=64,validation_data=(test_x,test_y),callbacks=[cp_cb])

結果は下記の通り

Train on 60000 samples, validate on 10000 samples
Epoch 1/64
60000/60000 [==============================] - 82s 1ms/step - loss: 0.8120 - acc: 0.7451 - val_loss: 0.4310 - val_acc: 0.8647
Epoch 2/64
60000/60000 [==============================] - 78s 1ms/step - loss: 0.1514 - acc: 0.9529 - val_loss: 0.0841 - val_acc: 0.9783
Epoch 3/64
60000/60000 [==============================] - 78s 1ms/step - loss: 0.0911 - acc: 0.9720 - val_loss: 0.0681 - val_acc: 0.9835
Epoch 4/64
60000/60000 [==============================] - 78s 1ms/step - loss: 0.0661 - acc: 0.9793 - val_loss: 0.0346 - val_acc: 0.9899
Epoch 5/64
60000/60000 [==============================] - 78s 1ms/step - loss: 0.0537 - acc: 0.9841 - val_loss: 0.0359 - val_acc: 0.9898
Epoch 6/64
60000/60000 [==============================] - 78s 1ms/step - loss: 0.0438 - acc: 0.9867 - val_loss: 0.0265 - val_acc: 0.9925
Epoch 7/64
60000/60000 [==============================] - 77s 1ms/step - loss: 0.0396 - acc: 0.9879 - val_loss: 0.0329 - val_acc: 0.9901
Epoch 8/64
60000/60000 [==============================] - 77s 1ms/step - loss: 0.0336 - acc: 0.9894 - val_loss: 0.0227 - val_acc: 0.9934
Epoch 9/64
60000/60000 [==============================] - 78s 1ms/step - loss: 0.0305 - acc: 0.9900 - val_loss: 0.0257 - val_acc: 0.9929
Epoch 10/64
60000/60000 [==============================] - 78s 1ms/step - loss: 0.0268 - acc: 0.9917 - val_loss: 0.0291 - val_acc: 0.9909
Epoch 11/64
60000/60000 [==============================] - 78s 1ms/step - loss: 0.0250 - acc: 0.9920 - val_loss: 0.0221 - val_acc: 0.9934
Epoch 12/64
60000/60000 [==============================] - 78s 1ms/step - loss: 0.0236 - acc: 0.9927 - val_loss: 0.0297 - val_acc: 0.9915
Epoch 13/64
60000/60000 [==============================] - 78s 1ms/step - loss: 0.0215 - acc: 0.9933 - val_loss: 0.0189 - val_acc: 0.9940
Epoch 14/64
60000/60000 [==============================] - 78s 1ms/step - loss: 0.0191 - acc: 0.9939 - val_loss: 0.0210 - val_acc: 0.9940
Epoch 15/64
60000/60000 [==============================] - 79s 1ms/step - loss: 0.0180 - acc: 0.9944 - val_loss: 0.0188 - val_acc: 0.9948
Epoch 16/64
60000/60000 [==============================] - 79s 1ms/step - loss: 0.0165 - acc: 0.9950 - val_loss: 0.0161 - val_acc: 0.9953
Epoch 17/64
60000/60000 [==============================] - 78s 1ms/step - loss: 0.0159 - acc: 0.9949 - val_loss: 0.0202 - val_acc: 0.9936
Epoch 18/64
60000/60000 [==============================] - 78s 1ms/step - loss: 0.0141 - acc: 0.9956 - val_loss: 0.0233 - val_acc: 0.9925
Epoch 19/64
60000/60000 [==============================] - 78s 1ms/step - loss: 0.0143 - acc: 0.9954 - val_loss: 0.0187 - val_acc: 0.9945
Epoch 20/64
60000/60000 [==============================] - 78s 1ms/step - loss: 0.0133 - acc: 0.9957 - val_loss: 0.0221 - val_acc: 0.9938
Epoch 21/64
60000/60000 [==============================] - 78s 1ms/step - loss: 0.0131 - acc: 0.9958 - val_loss: 0.0178 - val_acc: 0.9950
Epoch 22/64
60000/60000 [==============================] - 78s 1ms/step - loss: 0.0124 - acc: 0.9961 - val_loss: 0.0171 - val_acc: 0.9950
Epoch 23/64
60000/60000 [==============================] - 78s 1ms/step - loss: 0.0121 - acc: 0.9961 - val_loss: 0.0169 - val_acc: 0.9950
Epoch 24/64
60000/60000 [==============================] - 78s 1ms/step - loss: 0.0099 - acc: 0.9967 - val_loss: 0.0207 - val_acc: 0.9942
Epoch 25/64
60000/60000 [==============================] - 78s 1ms/step - loss: 0.0103 - acc: 0.9967 - val_loss: 0.0172 - val_acc: 0.9953
Epoch 26/64
60000/60000 [==============================] - 77s 1ms/step - loss: 0.0097 - acc: 0.9970 - val_loss: 0.0189 - val_acc: 0.9946
Epoch 27/64
60000/60000 [==============================] - 78s 1ms/step - loss: 0.0095 - acc: 0.9970 - val_loss: 0.0182 - val_acc: 0.9944
Epoch 28/64
60000/60000 [==============================] - 78s 1ms/step - loss: 0.0084 - acc: 0.9972 - val_loss: 0.0214 - val_acc: 0.9942
Epoch 29/64
60000/60000 [==============================] - 78s 1ms/step - loss: 0.0087 - acc: 0.9972 - val_loss: 0.0193 - val_acc: 0.9945
Epoch 30/64
60000/60000 [==============================] - 78s 1ms/step - loss: 0.0077 - acc: 0.9976 - val_loss: 0.0173 - val_acc: 0.9960
Epoch 31/64
60000/60000 [==============================] - 78s 1ms/step - loss: 0.0089 - acc: 0.9973 - val_loss: 0.0187 - val_acc: 0.9948
Epoch 32/64
60000/60000 [==============================] - 78s 1ms/step - loss: 0.0064 - acc: 0.9979 - val_loss: 0.0204 - val_acc: 0.9948
Epoch 33/64
60000/60000 [==============================] - 78s 1ms/step - loss: 0.0077 - acc: 0.9975 - val_loss: 0.0205 - val_acc: 0.9949
Epoch 34/64
60000/60000 [==============================] - 78s 1ms/step - loss: 0.0064 - acc: 0.9979 - val_loss: 0.0173 - val_acc: 0.9953
Epoch 35/64
60000/60000 [==============================] - 78s 1ms/step - loss: 0.0068 - acc: 0.9976 - val_loss: 0.0204 - val_acc: 0.9952
Epoch 36/64
60000/60000 [==============================] - 79s 1ms/step - loss: 0.0074 - acc: 0.9976 - val_loss: 0.0183 - val_acc: 0.9952
Epoch 37/64
60000/60000 [==============================] - 78s 1ms/step - loss: 0.0053 - acc: 0.9983 - val_loss: 0.0240 - val_acc: 0.9947
Epoch 38/64
60000/60000 [==============================] - 78s 1ms/step - loss: 0.0064 - acc: 0.9979 - val_loss: 0.0190 - val_acc: 0.9951
Epoch 39/64
60000/60000 [==============================] - 78s 1ms/step - loss: 0.0064 - acc: 0.9979 - val_loss: 0.0232 - val_acc: 0.9943
Epoch 40/64
60000/60000 [==============================] - 78s 1ms/step - loss: 0.0056 - acc: 0.9981 - val_loss: 0.0177 - val_acc: 0.9956
Epoch 41/64
60000/60000 [==============================] - 78s 1ms/step - loss: 0.0059 - acc: 0.9981 - val_loss: 0.0197 - val_acc: 0.9956
Epoch 42/64
60000/60000 [==============================] - 77s 1ms/step - loss: 0.0062 - acc: 0.9980 - val_loss: 0.0210 - val_acc: 0.9951
Epoch 43/64
60000/60000 [==============================] - 77s 1ms/step - loss: 0.0050 - acc: 0.9982 - val_loss: 0.0210 - val_acc: 0.9951
Epoch 44/64
60000/60000 [==============================] - 77s 1ms/step - loss: 0.0047 - acc: 0.9984 - val_loss: 0.0196 - val_acc: 0.9953
Epoch 45/64
60000/60000 [==============================] - 78s 1ms/step - loss: 0.0051 - acc: 0.9982 - val_loss: 0.0230 - val_acc: 0.9945
Epoch 46/64
60000/60000 [==============================] - 77s 1ms/step - loss: 0.0056 - acc: 0.9983 - val_loss: 0.0208 - val_acc: 0.9950
Epoch 47/64
60000/60000 [==============================] - 78s 1ms/step - loss: 0.0050 - acc: 0.9983 - val_loss: 0.0234 - val_acc: 0.9948
Epoch 48/64
60000/60000 [==============================] - 78s 1ms/step - loss: 0.0050 - acc: 0.9983 - val_loss: 0.0221 - val_acc: 0.9947
Epoch 49/64
60000/60000 [==============================] - 78s 1ms/step - loss: 0.0045 - acc: 0.9986 - val_loss: 0.0223 - val_acc: 0.9949
Epoch 50/64
60000/60000 [==============================] - 78s 1ms/step - loss: 0.0040 - acc: 0.9987 - val_loss: 0.0243 - val_acc: 0.9944
Epoch 51/64
60000/60000 [==============================] - 78s 1ms/step - loss: 0.0038 - acc: 0.9989 - val_loss: 0.0189 - val_acc: 0.9954
Epoch 52/64
60000/60000 [==============================] - 79s 1ms/step - loss: 0.0047 - acc: 0.9986 - val_loss: 0.0204 - val_acc: 0.9953
Epoch 53/64
60000/60000 [==============================] - 78s 1ms/step - loss: 0.0042 - acc: 0.9985 - val_loss: 0.0171 - val_acc: 0.9961
Epoch 54/64
60000/60000 [==============================] - 77s 1ms/step - loss: 0.0044 - acc: 0.9986 - val_loss: 0.0223 - val_acc: 0.9951
Epoch 55/64
60000/60000 [==============================] - 78s 1ms/step - loss: 0.0043 - acc: 0.9986 - val_loss: 0.0194 - val_acc: 0.9951
Epoch 56/64
60000/60000 [==============================] - 77s 1ms/step - loss: 0.0041 - acc: 0.9986 - val_loss: 0.0200 - val_acc: 0.9947
Epoch 57/64
60000/60000 [==============================] - 78s 1ms/step - loss: 0.0037 - acc: 0.9988 - val_loss: 0.0203 - val_acc: 0.9953
Epoch 58/64
60000/60000 [==============================] - 78s 1ms/step - loss: 0.0036 - acc: 0.9989 - val_loss: 0.0182 - val_acc: 0.9955
Epoch 59/64
60000/60000 [==============================] - 77s 1ms/step - loss: 0.0041 - acc: 0.9988 - val_loss: 0.0188 - val_acc: 0.9957
Epoch 60/64
60000/60000 [==============================] - 77s 1ms/step - loss: 0.0041 - acc: 0.9985 - val_loss: 0.0240 - val_acc: 0.9941
Epoch 61/64
60000/60000 [==============================] - 79s 1ms/step - loss: 0.0037 - acc: 0.9989 - val_loss: 0.0214 - val_acc: 0.9947
Epoch 62/64
60000/60000 [==============================] - 79s 1ms/step - loss: 0.0030 - acc: 0.9990 - val_loss: 0.0236 - val_acc: 0.9948
Epoch 63/64
60000/60000 [==============================] - 78s 1ms/step - loss: 0.0037 - acc: 0.9989 - val_loss: 0.0239 - val_acc: 0.9948
Epoch 64/64
60000/60000 [==============================] - 78s 1ms/step - loss: 0.0034 - acc: 0.9990 - val_loss: 0.0204 - val_acc: 0.9952

epoch53でval_accが99.61%を記録しました。

本当にこのモデルで99.61%を記録するか確認しておきましょう。

callbackにモデルを保存する関数を入れておいたので、 各epoch毎にその時の重みとバイアスを保存してくれています。

load_modelで呼び出して確認です。

model = load_model("./model_53.model")
model.evaluate(test_x,test_y)
10000/10000 [==============================] - 2s 187us/step
[0.017076379576059116, 0.9961]

ちゃんとval_accが0.9961出してくれますね。

た だ し 今 回 は こ の モ デ ル は 使 い ま せ ん 。

理由としては、trainデータのaccがもうすでに100%だったからです。

なので恣意的にepoch30のval_acc99.6%のモデルを使います。

model = load_model("./model_30.model")

データの取捨選択

さて、このモデル、現状trainとtestに対してどれくらいの能力を持っているのか、 今一度確認します。

# 訓練データ
print(np.sum(np.argmax(model.predict(train_x),axis=1) == train_y_org))
# テストデータ
print(np.sum(np.argmax(model.predict(train_x),axis=1) == np.argmax(test_y,axis=1)))

結果は以下の通り

59976
9960

trainが60000,testが10000なので、 trainは24個間違い、testは40個間違えてますね。

さて、ここでtrainの24個に注目します。

もう合ってるものを学習してあまり意味はないし、むしろ過学習するだけで悪影響を及ぼすと考えられます。

なので間違えているものだけを、学習率を1/10に下げてちょろちょろと重みとバイアスを更新すると少なくとも学習データに対しては適合します。

それがテストデータに対してどうなるのか、が今回のお話。

なのでこんなコードをこの後に実行しました。

model.compile(optimizer=Adam(lr=0.00001),metrics=['accuracy'],loss="categorical_crossentropy")
model_history_2 = []
for i in range(32):
    choice = np.invert((np.argmax(model.predict(train_x),axis=1) == train_y_org))
    model_history_2.append(model.fit(train_x[choice],train_y[choice],batch_size=16,epochs=1,validation_data=(test_x,test_y)))

肝は4行目でtrainデータの中で間違えたものだけを取り出して、 それを5行目で学習するときに突っ込むところですね。

さて、その結果はこんな感じです。

Train on 24 samples, validate on 10000 samples
Epoch 1/1
24/24 [==============================] - 7s 280ms/step - loss: 2.9693 - acc: 0.3750 - val_loss: 0.0173 - val_acc: 0.9960
Train on 23 samples, validate on 10000 samples
Epoch 1/1
23/23 [==============================] - 2s 104ms/step - loss: 2.1650 - acc: 0.3913 - val_loss: 0.0173 - val_acc: 0.9960
Train on 21 samples, validate on 10000 samples
Epoch 1/1
21/21 [==============================] - 2s 113ms/step - loss: 2.7324 - acc: 0.3333 - val_loss: 0.0173 - val_acc: 0.9960
Train on 21 samples, validate on 10000 samples
Epoch 1/1
21/21 [==============================] - 2s 112ms/step - loss: 2.9146 - acc: 0.2381 - val_loss: 0.0173 - val_acc: 0.9960
Train on 20 samples, validate on 10000 samples
Epoch 1/1
20/20 [==============================] - 2s 119ms/step - loss: 3.5161 - acc: 0.2500 - val_loss: 0.0174 - val_acc: 0.9962
Train on 17 samples, validate on 10000 samples
Epoch 1/1
17/17 [==============================] - 2s 139ms/step - loss: 3.3470 - acc: 0.2941 - val_loss: 0.0174 - val_acc: 0.9961
Train on 17 samples, validate on 10000 samples
Epoch 1/1
17/17 [==============================] - 2s 139ms/step - loss: 2.3149 - acc: 0.4706 - val_loss: 0.0175 - val_acc: 0.9958
Train on 16 samples, validate on 10000 samples
Epoch 1/1
16/16 [==============================] - 2s 147ms/step - loss: 2.7394 - acc: 0.3750 - val_loss: 0.0175 - val_acc: 0.9959
Train on 16 samples, validate on 10000 samples
Epoch 1/1
16/16 [==============================] - 2s 146ms/step - loss: 2.2038 - acc: 0.3125 - val_loss: 0.0175 - val_acc: 0.9960
Train on 16 samples, validate on 10000 samples
Epoch 1/1
16/16 [==============================] - 2s 146ms/step - loss: 1.8772 - acc: 0.3750 - val_loss: 0.0175 - val_acc: 0.9960
Train on 15 samples, validate on 10000 samples
Epoch 1/1
15/15 [==============================] - 2s 158ms/step - loss: 2.2117 - acc: 0.3333 - val_loss: 0.0175 - val_acc: 0.9959
Train on 15 samples, validate on 10000 samples
Epoch 1/1
15/15 [==============================] - 2s 158ms/step - loss: 1.7083 - acc: 0.4000 - val_loss: 0.0175 - val_acc: 0.9959
Train on 15 samples, validate on 10000 samples
Epoch 1/1
15/15 [==============================] - 2s 157ms/step - loss: 2.1227 - acc: 0.4000 - val_loss: 0.0175 - val_acc: 0.9959
Train on 14 samples, validate on 10000 samples
Epoch 1/1
14/14 [==============================] - 2s 171ms/step - loss: 2.7587 - acc: 0.4286 - val_loss: 0.0175 - val_acc: 0.9960
Train on 14 samples, validate on 10000 samples
Epoch 1/1
14/14 [==============================] - 2s 169ms/step - loss: 3.1257 - acc: 0.2857 - val_loss: 0.0175 - val_acc: 0.9959
Train on 14 samples, validate on 10000 samples
Epoch 1/1
14/14 [==============================] - 2s 166ms/step - loss: 2.8811 - acc: 0.2857 - val_loss: 0.0176 - val_acc: 0.9959
Train on 14 samples, validate on 10000 samples
Epoch 1/1
14/14 [==============================] - 2s 166ms/step - loss: 2.2550 - acc: 0.3571 - val_loss: 0.0176 - val_acc: 0.9960
Train on 13 samples, validate on 10000 samples
Epoch 1/1
13/13 [==============================] - 2s 181ms/step - loss: 2.1959 - acc: 0.4615 - val_loss: 0.0176 - val_acc: 0.9960
Train on 12 samples, validate on 10000 samples
Epoch 1/1
12/12 [==============================] - 2s 195ms/step - loss: 3.4894 - acc: 0.0833 - val_loss: 0.0176 - val_acc: 0.9960
Train on 12 samples, validate on 10000 samples
Epoch 1/1
12/12 [==============================] - 2s 198ms/step - loss: 3.0016 - acc: 0.3333 - val_loss: 0.0176 - val_acc: 0.9960
Train on 12 samples, validate on 10000 samples
Epoch 1/1
12/12 [==============================] - 2s 199ms/step - loss: 4.0224 - acc: 0.1667 - val_loss: 0.0176 - val_acc: 0.9960
Train on 12 samples, validate on 10000 samples
Epoch 1/1
12/12 [==============================] - 2s 197ms/step - loss: 3.4493 - acc: 0.3333 - val_loss: 0.0176 - val_acc: 0.9960
Train on 11 samples, validate on 10000 samples
Epoch 1/1
11/11 [==============================] - 2s 218ms/step - loss: 3.6480 - acc: 0.2727 - val_loss: 0.0176 - val_acc: 0.9960
Train on 9 samples, validate on 10000 samples
Epoch 1/1
9/9 [==============================] - 2s 266ms/step - loss: 2.7602 - acc: 0.3333 - val_loss: 0.0176 - val_acc: 0.9960
Train on 9 samples, validate on 10000 samples
Epoch 1/1
9/9 [==============================] - 2s 264ms/step - loss: 3.2277 - acc: 0.3333 - val_loss: 0.0176 - val_acc: 0.9960
Train on 9 samples, validate on 10000 samples
Epoch 1/1
9/9 [==============================] - 2s 258ms/step - loss: 1.9044 - acc: 0.4444 - val_loss: 0.0177 - val_acc: 0.9960
Train on 9 samples, validate on 10000 samples
Epoch 1/1
9/9 [==============================] - 2s 257ms/step - loss: 3.7250 - acc: 0.1111 - val_loss: 0.0177 - val_acc: 0.9960
Train on 9 samples, validate on 10000 samples
Epoch 1/1
9/9 [==============================] - 2s 256ms/step - loss: 3.6916 - acc: 0.2222 - val_loss: 0.0177 - val_acc: 0.9959
Train on 10 samples, validate on 10000 samples
Epoch 1/1
10/10 [==============================] - 2s 234ms/step - loss: 3.0128 - acc: 0.4000 - val_loss: 0.0177 - val_acc: 0.9959
Train on 10 samples, validate on 10000 samples
Epoch 1/1
10/10 [==============================] - 2s 231ms/step - loss: 2.5596 - acc: 0.2000 - val_loss: 0.0177 - val_acc: 0.9959
Train on 10 samples, validate on 10000 samples
Epoch 1/1
10/10 [==============================] - 2s 233ms/step - loss: 3.6156 - acc: 0.2000 - val_loss: 0.0177 - val_acc: 0.9959

5ループ目に99.62%を達成してくれてます。その後は過学習したのかどんどんval_lossもval_accも落ちていきます。

ここで言いたいのは53epoch全量回し切ったのに対し、

30epoch程度のモデルに対して20個程度のデータで5回回すだけでval_accが超えたことですね。

これは計算量の効率がだいぶよいのではないでしょうか。

そんなお話。

現場で使える! TensorFlow開発入門 Kerasによる深層学習モデル構築手法 (AI & TECHNOLOGY)

現場で使える! TensorFlow開発入門 Kerasによる深層学習モデル構築手法 (AI & TECHNOLOGY)