読書記録 ゼロから作るDeep Learning 4 強化学習編
0. この投稿の概要
- ゼロから作るディープラーニングの新作、強化学習編を読んで学んだことをまとめます。ご存知の方が多いと思いますが、このシリーズは可能な限りライブラリを使わずにディープラーニングを実装しながら理解を深めていく構成になっています。
- 本書のサポートページにコードは公開されているため、この投稿には特にコードを載せていません。トピックの箇条書きになって読みづらいと思いますが、購入の際の参考になれば幸いです。
1. 各章の概要
1章 バンディット問題
2章 マルコフ決定過程
3章 ベルマン方程式
- 決定論的ではない場合、状態価値関数を手計算で求めるのが困難になる。この場合現在の状態の価値関数と次に取りうる状態の価値関数の関係性を示すベルマン方程式を用いる。
- 特に最適方策を考える場合は方策は決定論的になるためベルマン方程式は簡略化される。
- ベルマン方程式により、価値の連立方程式が得られる。それを解ければ行動価値を最大にするような方策を得ることができる。
- 本章で行動価値関数(Q関数)が導入される。これは状態価値関数の条件に行動aが追加される。この行動は方策には関係なく任意に決められる。このQ関数を最大化するようにaを選ぶと最適方策が得られる。この場合環境のモデルに依存しないで方策を求められるので、後ほど環境のモデルが未知の場合はQ関数を主に使っていくことになる。
4章 動的計画法
- ベルマン方程式で得られた連立方程式を解く方法は状態が増えると途端に解くのが困難になる。本章では3×4のグリッドワールドの問題に取り組む。
- 動的計画法(DP)はベルマン方程式の価値関数を推定値に置き換える。適当な初期値を与えて反復計算することで、真値に近づく反復方策評価。
- 価値の評価と方策の改善を繰り返して最適方策を得る方策反復法(Policy Iteration)。方策の改善ではその時点での各状態の価値関数を最大にするように決定論的な方策を選ぶ。
- 評価と改善をそれぞれ最大限行うことを繰り返す方策反復法に対して、それぞれ最小限だけ行うのが価値反復法(Value Iteration)。状態1つ毎に評価と改善を行う。評価と改善で現れる式はほとんど同じであることから、価値反復法は価値の更新式の1つにまとめられて実装もシンプルになっている。
5章 モンテカルロ法
- DPでは環境のモデル(状態遷移関数、報酬関数)が既知であることが前提だった。モンテカルロ法はエージェントが環境とやり取りを繰り返して得た経験から価値関数を推定する。
- 方策評価はエージェントの各ステップ毎に状態、行動、報酬を記録しておき、エピソードが終わったらそれを使って価値関数を計算する。
- 方策制御はε-greedy法を用いる。完全なgreedyでは行動が固定されて同じ経験しか得られないため。また、方策が更新されていくため価値関数は指数移動平均を用いて得る。
- これまでは評価と改善の対象となるターゲット方策と、エージェントが実際に行動を起こす際に使われる挙動方策を区別しないでいた。挙動方策に探索をさせ、ターゲット方策に活用させる手法を方策オフ型という。この時異なる分布からサンプルしたデータを使うため、重点サンプリングを用いる。
6章 TD法
- モンテカルロ法はエピソードの終わりにたどり着かないと更新できないという欠点がある。TD法はサンプリングしたデータを元に価値関数を更新するが、エピソードの終わりをまたずにnステップ先の情報を使って逐次更新する。
- 方策制御はSARSAが使われる。方策を決める際は環境モデルが必要でないQ関数を更新していく。1ステップ先の情報を用いるため、更新に必要なのはSt,At,Rt,St+1,At+1でありこれがSARSAの名前の由来になっている。方策オン型の場合は探索を行うためにε-greedy法を用いる。
- 方策オフ型の場合は重点サンプリングを組み合わせるが、結果が不安定になりやすいという弱点がある。これを解決するのがQ学習。SARSAでは方策からサンプリングされたQ関数をターゲットに更新していたのに対し、Q学習ではQ関数の最大値をターゲットにする(方策に依存しない)ので、重点サンプリングによる補正が不要。
- エージェントの実装方法は分布モデルとサンプルモデルがある。分布モデルでは行動の確率分布を明示的に保持するが、サンプルモデルでは分布を保持せずに行動を選択する場合はQ関数を最大化する行動をサンプリングする。
7章 ニューラルネットワークとQ学習
- 状態と行動のサイズが大きくなるとQ関数をテーブルとして保持してそれを更新することが困難になる。これを解決するためにQ関数をディープラーニングで近似する。
- グリッドワールドの問題をNNベースのQ学習で定式化。状態はone-hotベクトルで表し、NNは状態を入力にして行動の候補の数だけQ学習を出力する。
- Q学習における更新のターゲット(Rt+γmaxQ(St+1,a))を教師データとする回帰問題と考えることができる。
8章 DQN
- 取り組む問題はOpenAI Gymのカートポール。DQNでは学習を安定させるために経験再生とターゲットネットワークという技術が使われる。
- Q学習ではエージェントの経験を使い学習を行うが、経験は前後のデータに強い相関があり学習データに偏りが生まれる。経験再生では経験を保存しておいて、そこからランダムに学習用データをサンプルすることで偏りを防ぐ。
- DQNにおける正解ラベルであるQ学習のターゲットはQ関数の更新に伴い変動する。これでは学習が安定しないため、ターゲットの値を固定するために重みを更新するネットワークとは別にラベルを生成するためのターゲットネットワークを用意する。
- DQNの拡張:
- Double DQN: 学習のターゲットを2つのネットワークのQ関数を元に設定する。
- 優先度付き経験再生: ターゲットとQ関数の差の大きさを元にサンプルする経験に優先度をつける。
- DuelingDQN: Q関数をアドバンテージ関数+価値関数Vに分けてそれぞれを別のネットワークで学習する。
9章 方策勾配法
- 価値関数を経由しないで方策を直接表す方策ベースの手法も存在する。報酬の総和の期待値を目的関数とし、ニューラルネットワークでモデル化した方策を勾配上昇法で最適化するのが方策勾配法。
- 方策勾配法の改善:
- REINFORCE: 勾配の計算時に、報酬の総和ではなくその時刻以降の報酬Gtだけを考える。それ以前の時刻の報酬は関係ないので。
- ベースライン: 報酬Gtから予測値(ベースライン)を引いたものを使う。ベースラインには価値関数が使われることが多い。これにより、例えばカートポールが絶対に倒れるようなタイミングによる学習をスキップできる。
- Actor-Critic: ベースラインとしてニューラルネットワークでモデル化した価値関数を用いる。価値ベースかつ方策ベースの手法。
2. 感想
(AWS CDK)LambdaのLayerでコードを再利用する
0. この投稿の概要
- AWS Lambdaの異なるFunction間でコードを再利用をするための仕組みであるLayerをAWS CDKでdeployします。
- 元となるLambda Functionは以前の投稿で実装した図書館アラートシステムです。aburaku.hatenablog.com
1. Layerとは?
2. CDKによるdeploy
- コードはgithubに上げています。
layer = _lambda.LayerVersion( self, "Packages", code=_lambda.Code.from_asset(LAYER_PATH), description="requests module", ) library_alert_lambda = _lambda.Function( self, "LibraryAlert", code=_lambda.Code.from_asset(API_PATH), handler="library_alert.lambda_handler", memory_size=512, timeout=core.Duration.seconds(120), role=lambda_role, layers=[layer], # <- 追加 **common_params, )
- CDKのLayerVesionの必須引数はcodeだけです。
pip install requests -t ./packages/python
のようにtargetオプションをつけてパッケージを指定した場所にインストールをして、その場所をcodeに指定します。 - この際気をつけるのはpythonというフォルダの下に配置することです。こうしておくとパスが通るのでLambda側で特にパスを追加しなくてもimportが可能になります。
参考:Lambda レイヤーの作成と共有 - AWS Lambda
- Function側の変更はlayers引数に追加したいlayerをリストで渡してやるだけです。
3. 結果
- 動作自体は元のコードと変化はしませんが、GUIでLayerを追加したFunctionを見に行くと、確かに追加されていることが確認できます。
4. 参考
既読のリンクを非表示にするchrome拡張機能を作る
0. この投稿の概要
1. 機能の概要
自分が実装した拡張機能は以下のような構成になっています。今回初めて拡張機能を作ったので、私の構成は最適ではないと思いますが参考まで。
- google chromeの拡張機能アイコンをクリックすることで機能が動きます。
- まずbackground.jsがクリックイベントに反応して実行され、chromeの閲覧履歴から対象のURLを取得してlocal storageに保存、その後content.jsを実行します。*1
- content.jsでアクティブなタブのhtmlから特定のclassを持つリンクを抽出し、それが閲覧履歴に含まれる場合はdisplayプロパティをnoneにすることで非表示にします。
2. 実装例
フォルダ構成
- 以下の通りになっています。必要なコードは全部載せているので、コピーをして同様の構成を作り、chromeで読み込めば機能を使えます。読み込み方についてはchrome拡張機能のチュートリアルを参考にしてください。
- アイコン画像は何でもいいですが、私は同ページからダウンロードした画像をそのまま使っています。
.
├── background.js
├── content.js
├── images
│ ├── get_started128.png
│ ├── get_started16.png
│ ├── get_started32.png
│ └── get_started48.png
└── manifest.json
manifest.json
- 拡張機能の設定ファイルみたいなもので、必須のファイルです。拡張機能の名前や呼び出すスクリプト、アイコン画像などを設定します。
- 特に重要なのはpermissionです。ここで拡張機能がアクセスできる資源を指定しています。今回は閲覧履歴を取得するためのhistory、閲覧履歴を保存するためのstorage、webページを操作するためのactiveTab、スクリプトをページに追加するためのscriptingを許可しています。
{ "name": "Hide read link", "description": "Hide links from web page if you have already read them.", "version": "1.0", "manifest_version": 3, "background": { "service_worker": "background.js" }, "permissions": ["storage", "activeTab", "scripting", "history"], "action": { "default_title": "Click", "default_icon": { "16": "/images/get_started16.png", "32": "/images/get_started32.png", "48": "/images/get_started48.png", "128": "/images/get_started128.png" } }, "icons": { "16": "/images/get_started16.png", "32": "/images/get_started32.png", "48": "/images/get_started48.png", "128": "/images/get_started128.png" } }
background.js
- イベントに反応して実行されるスクリプトです。
let microseconds3Days = 1000 * 60 * 60 * 24 * 3; var threeDaysAgo = (new Date).getTime() - microseconds3Days; // Execute when the icon is clicked. chrome.action.onClicked.addListener((tab) => { storeHistory(tab); }); function storeHistory(tab) { chrome.history.search({ "text": "https://pairs.lv/user/profile/", "startTime": threeDaysAgo, "maxResults": 10000, }, function(historyItems) { let historyList = []; for (let i = 0; i < historyItems.length; ++i) { let url = historyItems[i].url; let url_split = url.split("/"); // Store only profile ID historyList.push(url_split[url_split.length-1]); }; chrome.storage.local.set({ historyList }); chrome.scripting.executeScript({ target: {tabId: tab.id}, files: ['content.js'], }); }); };
- 拡張機能のアイコンをクリックするとchrome.action.onClicked.addListenerで指定した関数が実行されます。
- 今回は3日分のペアーズのプロフィール閲覧履歴をchrome.history.searchで取得しています。
- URLは表示される場所によって変わるようなので、プロフィール固有と思われるIDをURLから抽出しています。そのリストをchrome.storage.local.setで保管しています。
- 最後にchrome.scripting.executeScriptでcontent.jsをwebページに埋め込みます。
content.js
- chrome.storage.local.getで保存しておいた履歴データを読み込む意外は普通のjavascriptコードです。対象リンクのクラスはchromeのデベロッパーツールで確認しました。
function deleteReadLinks() { chrome.storage.local.get("historyList", ({ historyList }) => { let historySet = new Set(historyList) let profiles = document.getElementsByClassName("css-opde7s"); for (let i = 0; i < profiles.length; ++i) { if ("href" in profiles[i]) { let url_split = profiles[i].href.split("/"); if (historySet.has(url_split[url_split.length-1])) { profiles[i].style.display = "none"; } } }; }); } deleteReadLinks();
3. 参考
- Getting started - Chrome Developers: Chrome公式ページのチュートリアル。拡張機能開発が初めての場合は取りあえずなぞってみると良いと思います。拡張機能に関する書籍やwebページは多くないので、基本的に公式ページを参考にすることになるかと思います。
図書館APIで新刊の入荷をいち早く知る(AWS CDKで実装)
0. 簡単なまとめ
1. やりたいこと
- 図書館に自分が読みたい新刊が入荷されたことをいち早く知り予約することで、待たずに新刊を読めるようになりたい。
- 定期的に自動で入荷状況を調べる必要がある。金を使わないために図書館を使っているので、当然このシステムを稼働させるのに金を使いたくない。
2. 実現方法
- 無料のレンタルサーバーは色々あるようだが、今回は勉強のためにAWSを使った。大体の構成は下の図の通り。自分しか使わないシステムなので、ほぼほぼ無料で動かせるはず。
- 図書館の入荷状況を調べるために図書館のwebサイトをクローリングすると逮捕されるかもしれないので、株式会社カーリルが提供する図書館APIを使わせてもらった。
3. 仕様と実装
- コードは下記リンクに置いてます。今回はAWS CDKなる、コードでAWSのリソースの準備を行えるツールを使って実装しました。いわゆるInfrastructure as Codeというやつですね。言語はPython3です。
- 主要コンポーネントの役割
- その他のコンポーネント
- Lambdaがメールアドレスやアプリケーションキーのような機密情報にアクセスする必要がある。コードに埋め込みたくないので、SSMのParameter Storeを利用した。
- Lambdaの定期実行で異常が発生していないかウォッチするためにCloudWatch Logsを利用。Lambda実行時のエラーを検知して、Amazon SNSで登録しておいたメールアドレスに通知する。
- デフォルトの設定ではURLを知っていたら誰でもAPIにアクセスできてしまうので、API使用量とAPIキーを使ってアクセスを制限する。
4. 使い方
- 環境構築
- AWSの登録など
- AWSアカウント取得、シークレットキー作成、CDKのインストールが必要です。このページが参考になります。
- googleのアプリパスワード設定
- Lambdaからgmailを送信するのにアプリパスワードが必要なので作成してください。
- 図書館APIのアプリケーションキー登録
- 利用にはアプリケーションキーの登録が必要なので、このページから登録してください。
- 環境変数の設定
- cloneしたディレクトリ内に.envファイルを作成し、下記の通りgmailアドレス、googleアプリパスワード、図書館APIのアプリケーションキーを設定してください。
-
EMAIL = "your mail address@gmail.com" GOOGLE_PASS = "your password" APP_KEY = "your application key"
- cdk deployの実行
python3 -m pip install -r requirements.txt
で必要なモジュールをインストールして、cdk deploy
を実行するとAWSのサービスが立ち上がります。- 環境を削除したい場合は
cdk destroy
を実行してください。
- エンドポイントとAPIキーの登録
- cdk deployが完了後に
LibraryAlert.LibraryAlertApiEndpointXXX = https://XXXX.amazonaws.com/prod/
という表示がコンソールに現れます。このURLがエンドポイントです。 - APIキーはAmazon API GatewayのGUI上で確認します。作成されたAPI(LibraryAlertApi)の下記の場所でAPIキーを表示できます。
- gui/config.jsというファイルを作成して、以下のようにエンドポイントとAPIキーを記載します。*1
const config = {
endpoint: "your endpoint URL",
apikey: "your API key",
}
- cdk deployが完了後に
- AWSの登録など
- 図書館と本の登録
- index.htmlをブラウザで開き、まずは図書館の登録をします。図書館のIDはカーリルの図書館マップで利用したい図書館のページから取得します。このIDは市町村内の図書館で共通なようなので、例えば"Tokyo_Machida"を指定すると町田の市立図書館全ての状況をチェックします。
このIDをページ右上のフォームに書き、"図書館の追加"ボタンを押して更新をすると、下の表に追加した図書館が表示されます。
- 次に本の登録をします。読みたい本のISBN-10を左上のフォームに書き、"本の追加"ボタンを押して更新すると、下の表に追加した本が表示されます。ISBN-10はamazonで確認できます。*2
- 以降は登録した図書館と本の組み合わせ全てが表に追加されて監視対象になります。図書館や本を削除したい場合は、図書館IDやISBNをそれぞれのフォームに入れて削除ボタンを押してページを更新してください。
- 1時間毎に起動するLambda functionで入荷状況が調べられます。もし新しく入荷があった場合は、「新刊入荷情報」というタイトルのメールが届きます。メール内容に図書館の予約ページへのリンクがあります。
- index.htmlをブラウザで開き、まずは図書館の登録をします。図書館のIDはカーリルの図書館マップで利用したい図書館のページから取得します。このIDは市町村内の図書館で共通なようなので、例えば"Tokyo_Machida"を指定すると町田の市立図書館全ての状況をチェックします。
5. 参考
- コードで学ぶAWS入門: CDKを使ってサーバレスなアーキを構築する際に多いに参考にさせてもらいました。これでCDKの大枠を掴んで、あとはCDKの公式リファレンスで使いたい機能を探せば大体何とかなりそう。
↓書籍化しているので広告貼っておきます。
-
vue.jsを使ってaxiosを学ぶ | アールエフェクト: フロントエンドの知識がほとんどないので、Vue3を使ってAPIを呼ぶために参考にさせてもらいました。
-
無料で読みたい!図書館APIで新書が図書館に登録されたタイミングでLINE通知する(その1) - Qiita: 同じことをGASで実現している人がいた。どう見てもこっちの方が簡単。GASは軽い処理を自動実行するならLambdaよりも楽そうなので、別の機会に使ってみたい。