ミドルウエア
この記事は自分の勉強のために書いています。 ソースはLaravel5.7の公式ドキュメントです。 新しいことは何もないので通常はそちらを参照してください。 [clink implicit=\"false\" url=\"https://readouble.com/laravel/5.7/ja/middleware.html\" imgurl=\"http://laravel.jp/assets/img/header.jpg\" title=\"Laravel 5.7 ミドルウェア\" excerpt=\"ミドルウェア\"] [arst_toc tag=\"h4\"] アプリケーションに送られてきたリクエストを途中でフィルタするのがミドルウェア。 認証とかCORSとかCSRFとか、デフォルトでミドルウェアが用意されているけれども、 ミドルウェアを自作することができる。 ミドルウェアの定義 新しいミドルウェアの作成 ミドルウェアを作成するartisanコマンドは以下の通り。 こうすると、app/Http/Middlewareの中にCheckCostクラスが作られる。 $ php artisan make:middleware CheckCost; CheckCostクラスの中でhandle()メソッドを実装する。 $requestを$nextに流す際に$closureを評価するように動作し、 結果として$requestと$nextの間のフィルタ処理を$closureに書くことができる。 <?php namespace AppHttpMiddleware; use Closure; class CheckCost { /** * 送信されたきたリクエストをフィルタする * * @param IlluminateHttpRequest $request * @param Closure $next * @return mixed */ public function handle($request, $closure $next) { if ($request->value < 100) { return redirect('hoge'); } return $next($request); } } ミドルウェアを複数使用するとき、 前の段のミドルウェアを通過した後、次の段のミドルウェアを通過する。 Before Middleware/ After Middleware 公式だと仰々しい名前が付いているのだけれども、 $nextを呼んだ後に処理をするか、呼ぶ前に処理をするか、の違いを表現できる。 つまり、リクエストを完了する前の処理なのか、後の処理なのか。 <?php namespace AppHttpMiddleware; use Closure; class CheckCost { /** * Before Middleware * リクエストを評価する前に処理する * * @param IlluminateHttpRequest $request * @param Closure $next * @return mixed */ public function handle($request, $closure $next) { //アクションを実行 return $next($request); } } namespace AppHttpMiddleware; use Closure; class CheckCost { /** * After Middleware * リクエストを評価した後に処理する * * @param IlluminateHttpRequest $request * @param Closure $next * @return mixed */ public function handle($request, $closure $next) { $response = $next($request); return $response; } } 登録 グローバルミドルウェア 要は全てのHTTPリクエストについてミドルウェアを通すやり方。 App/Http/Kernel.phpに書く。Kernelクラスにある$middlewareという配列に グローバルミドルウェアにしたいやつを並べていく。 <?php namespace AppHttp; use IlluminateFoundationHttpKernel as HttpKernel; class Kernel extends HttpKernel { protected $middleware = [ ... ] } 特定のルートのみにミドルウェアを設定 特定のルートに限定してミドルウェアを設定する場合もApp/Http/Kernel.phpに書く。 まず、$routeMiddlewareにミドルウェアの短縮キーを書く <?php $routeMiddleware = [ \'auth\' => AppHttpMiddlewareAuthenticate::class, \'auth.basic\' => IlluminateAuthMiddlewareAuthenticateWithBasicAuth::class, ... ]; そして、routes.phpでそれぞれのルートに定義した短縮キーを指定する。 短縮キーではなく完全なクラス名を書いても良いみたい。 ミドルウェアを複数書くと、その順番に通すことになる。 Route::get(\'admin/profile\', function() { })->middleware(\'auth\'); Route::get2(\'admin/profile2\', function() { })->middleware(\'CheckCost::class\'); Route::get3(\'admin/profile3\', function() { })->middleware(\'auth\',\'guest\'); ミドルウェアグループ 複数のミドルウェアをグループ化して、そのグループのミドルウェアを一気に当てることができる。 App/Http/Kernel.phpに書く。Kernelクラスにある$middlewareGroupという配列に グループ名をキー、ミドルウェアの配列を値として書いていく。以下のように。 <?php $middlewareGroup = [ \'web\' => [ \'auth\' => AppHttpMiddlewareAuthenticate::class, \'auth.basic\' => IlluminateAuthMiddlewareAuthenticateWithBasicAuth::class, ... ], ... ]; routes.phpの中では、それぞれのルートに定義したグループ名を与える。 例えば、以下だと\'/\'に対して\'web\'ミドルウェアグループを与えている。 つまり\'/\'に\'web\'ミドルウェアグループに定義したミドルウェアが順番にあたる。 Route::get(\'/\',function(){ })->middleware(\'web\'); routes/web.php に書いたルートには自動的に\'web\'ミドルウェアグループがあたる。 ミドルウェアパラメタ ミドルウェアパラメタの書き方 ミドルウェア実行時に引数を与えることができる。 書き方は以下の通り。handle()をオーバーライドする際に、引数として受けるパラメタを追加する。 例えば、パラメタ$paramを受け取るミドルウェアは以下の通り。 クエリパラメタにある$valが$paramである場合に限り処理を書いている。 <?php namespace AppHttpMiddleware; use Closure; class CheckCost { /** * 送信されたきたリクエストをフィルタする * * @param IlluminateHttpRequest $request * @param Closure $next * @return mixed */ public function handle($request, $closure $next, $param) { if ($request->val == $param) { return redirect(\'hoge\'); } return $next($request); } } ミドルウェア指定時に以下のようにパラメタを渡す。 T.B.D. 終了処理ミドルウェア T.B.D. サンプル 実行時間を計測するミドルウェアの試作 こちらを参考に試しに作ってみた。 https://qiita.com/niisan-tokyo/items/663300f8df1c6c89f0ae $ php artisan make:middleware TimerMiddleware Middleware created successfully. TimerMiddleware本体はこちら。 <?php namespace AppHttpMiddleware; use Closure; class TimerMiddleware { /** * Handle an incoming request. * * @param IlluminateHttpRequest $request * @param Closure $next * @return mixed */ public function handle($request, Closure $next) { $before = microtime(true); $res = $next($request); Log::debug(mictotime(true) - $before); return $res; } } ミドルウェアの登録は、app/Http/Kernel.phpの$routeMiddlewareに追加。 protected $routeMiddleware = [ .. AppHttpMiddlewareTimerMiddleware::class, ]; ルートの定義はこちら。 Route::get(\'timer\',function(){ })->middleware(\'timer\'); /timer にアクセスすると、storages/logs/laravel.logにログが残る。 ... [2019-01-29 16:05:07] local.DEBUG: 0.019489049911499
ルーティング
この記事は自分の勉強のために書いています。 ソースはLaravel5.7の公式ドキュメントです。 新しいことは何もないので通常はそちらを参照してください。 [clink implicit=\"false\" url=\"https://readouble.com/laravel/5.7/ja/routing.html\" imgurl=\"http://laravel.jp/assets/img/header.jpg\" title=\"Laravel 5.7 ルーティング\" excerpt=\"基本的なルーティング\"] [arst_toc tag=\"h4\"] 基本的なルーティング routes.phpの書き方 HTTP動詞に対応させたroutes.phpの書き方。RESTfulAPIを書く場合は必要なんだと思う。 anyを使うと全ての動詞に対応する。matchを使うと対応する動詞を選べる。 No 動詞 文法 1 GET Route::get($url,$callback); 2 POST Route::post($url,$callback); 3 PUT Route::put($url,$callback); 4 PATCH Route::patch($url,$callback); 5 DELETE Route::delete($url,$callback); 6 OPTIONS Route::options($url,$callback); 7 全て Route::any($url,$callback); 8 選んだ動詞 Route::match($array_of_verb,$url,$callback); リダイレクトルート 単純にリダイレクトをかける場合は、わざわざコントローラを通さなくて良い。 (コントローラを通してた...)。 通常302で、301もルーティングだけで対応可能。 No HTTP Status Code 文法 1 302 Route::redirect(\'here\',\'there\'); or Route::redirect(\'here\',\'there\',302); 2 302 Route::redirect(\'here\',\'there\',301); or Route::permanentRedirect(\'hear\',\'there\'); Viewルート ルートから直接ビューを返す場合はコントローラを介さなくてもよい。 No文法 1Route::view($url,$view_name); 2Route::view($url,$view_name,$array_of_parameters); ex, Route::view($url,$view,[\'param1\'=>100,\'param2\'=>200]); CSRF保護 webルートに定義したPOST,PUT,DELETEへのルートへ送信するフォームは 一緒にCSRFトークンを送る必要がある(CSRFは別にまとめる予定)。 ルートパラメータ URLの動作に必要なパラメタをクエリ文字列ではなくURLに含める書き方。 必須パラメータとしておけば省略は不可となり存在チェックを省略できる。 必須パラメータ Curly bracketsでパラメタ名を囲んで定義する。 受けるクロージャはそのパラメタ名をもつ引数を使用する。 Route::get(\'application/{application_id}\', function($application_id) { return $application_id; }); 任意パラメータ Curly brackets内のパラメタ名を\"?\"で終わらすと任意パラメータになる。 受けるクロージャでは必ずデフォルト引数を設定する。 Route::get(\'user/{name?}\',function($name = null) { return $name; }); 正規表現制約 ルートパラメタのフォーマットを正規表現で制約できる(そんなことできるんだ...)。 書き方は以下の通り。URLとクロージャは触らない。 Route::get(\'user/{name}\', function($name) { return $name; })->where(\'name\',\'[A-Za-z]+\'); グローバル制約 RouteServiceProviderのbootメソッドに全てのルートパラメタを制約するルールを書ける。 例えば以下のように書いておくと、全てのルートの\"id\"という名前のクエリパラメタが数値に制約される。 /** * */ public function boot() { Route::pattern(\'id\',\'[0-9]+\'); parent::boot(); } /** * routes.php */ Route::get(\'user/{id}\',function($id) { return $id; // $idは数値に制約 }); 名前付きルート 定義したルートに名前を付けておくことで、そのルートのURLを名前から引くことができる。 名前付きルート 例えばuser/{id}に対するルートにmypageという名前を付けるには以下。 Route::get(\'user/{id}\',function($id) { return $id; })->name(\'mypage\'); 名前付きルートへのURL取得 このルートへのURLはroute()により取得できる。 ルートが必須/任意パラメータを必要とする場合は第2引数で値を与える。 redirect()->route(\'mypage\',[\'id\'=>100]); ルートグループ 複数のルートで共通の属性(=ミドルウェアと名前空間)をまとめて書ける。 グループはネストすることができる。ミドルウェアと名前空間は別途記載。 ミドルウェアによるグループ化 複数のルートで共通のミドルウェアを外出しする書き方は以下。 (もちろん個別に書くこともできる) Route::middleware([\'first\',\'second\'])->group(function() { Route::get(\'/\',function(){ // firstミドルウェア、secondミドルウェアを順番に使用 }); Route::get(\'/hoge/{id}\',function($id){ // firstミドルウェア、secondミドルウェアを順番に使用 }); }); 名前空間によるグループ化 複数のルートで同じPHP名前空間を使用する書き方は以下。 こうすることで、各コントローラのファイルの先頭に名前空間を書かなくてもよくなる。 Route::namespace(\'Namespace1\')->group(function() { // AppHttpControllersNamespace1 名前空間下のコントローラ }); })/ <?php namespace App/Http/Controllers/Namespace1; // <- これが不要になる サブドメインによるグループ化 複数のルートが同じサブドメイン配下にあるような配置にする書き方は以下。(こんなことできるのか!)。 Route::domain(\'{subdomainname}.myapp.test\')->group(function() { Route::get(\'user/{id}\',function($id) { // subdomainname.myapp.test/user/100 }); }); ルートプレフィックスによるグループ化 複数のルートが同じ文字列からスタートするように配置する書き方は以下。 Route::prefix(\'sameprefix\')->group(function() { Route::get(\'user/{id}\',function($id) { // /sameprefix/user/100 }); Route::get(\'group/{id}\',function($id) { // /sameprefix/group/200 }); }); ルート名プレフィックスによるグループ化 複数のルートのルート名が同じ文字列からスタートするように配置する書き方は以下。 Route::name(\'sameprefix\')->group(function() { Route::get(\'user/{id}\',function($id) { // \'sameprefix.user\' というルート名 })->name(\'user\'); Route::get(\'group/{id}\',function($id) { // \'sameprefix.group\' というルート名 }); }); モデル結合ルート ルートで与えた情報(多くの場合でモデルID)をコントローラに渡して コントローラの中でモデルインスタンスを引くという使い方がほとんどなので、 ルートに含めたモデルIDからモデルインスタンスを解決してコントローラでそれを使うことができる。 当然、クロージャを直接書いても同じ。 暗黙のモデル結合ルート どうやってルートパラメータとモデルインスタンスを紐づけるかというと、 なんと、PHPのタイプヒントを使って紐づける。 以下、$userがAppUserクラスであるとタイプヒントされている。 ルートパラメータにuserがあるから、クロージャの中ではAppUserのインスタンスを $userを使って引いたオブジェクトを使用できる。 わざわざ$userを使ってAppUser::find($user)と書いたりエラーケースを書くのを省略できる。 AppUser::find($user)が見つからなかったらルートは404を返す。すごいー。 Route::get(\'users/{user}\',function(AppUser $user) { // AppUser $user を使用できる。 }); キー名がカスタマイズされたモデル結合ルート モデルの主キーがidではないときimplicitにidからModelを引くことが出来ない。 モデル側でgetRouteKeyName()をオーバーライドして主キーを返せば良い。 ルートパラメータを新しい主キーであるものとしてインスタンスを探してくれる。 Route::get(\'users/{user}\',function(AppUser $user) { // AppUser.userid が $user であるインスタンスを使用できる。 }); /** * モデルの中 * */ public function getRouteKeyName() { return \'userid\'; } 明示的なモデル結合ルート ルート名とタイプヒントを使ってimplicitにモデル結合する方法とは別に、 RouteServiceProviderクラスのboot()メソッドの中でexplicitにモデル結合を定義できる。 タイプヒントがなくても良いというのならexplicitに書く意味もあると思うのだけど、 タイプヒントが必要なのであれば、implicitな記法に対して冗長な気がするんだけども...。 /** * RouteServiceProvider * */ public function boot() { parent::boot(); Route::model(\'user\',AppUser::class); } /** * routes.php * */ Route::get(\'user/{user}\',function(AppUser $user) { }); 依存解決ロジックのカスタマイズ 「ルートパラメータをモデルID、タイプヒントをモデルクラスとして、モデルIDを主キー(または別のキー) を使って検索する」という単純なロジックとは異なるロジックを定義する方法が用意されている。 例えばモデルを主キーとは関係ないフィールドで検索したいときは以下みたいに書く。 /** * RouteServiceProvider * */ public function boot() { parent::boot(); Route::bind(\'user\', function($value) { return AppUser::where(\'name\',$value)->first() ?? abort(404); }); } モデル側でresoluveRoutingBind()をオーバーライドすることでも実現可能。 /** * モデル * */ public function resolveRoutingBind($value) { return $this->where(\'name\',$value)->first() ?? abort(404); } その他 フォールバックルート フォールバックルートを書いておくと、routes.phpに書いたどのルートにも当たらなかったときに使われる。 通常404になるところを受ける。書く場合は最後に配置しないといけない。 Route::fallback(function(){ // do something when 404. });
MAP推定
[mathjax] 真の(mu)、(sigma)があるものとしてデータ(boldsymbol{x}=(x_1,x_2,cdots,x_n))が最も起こりやすくなるように (mu)、(sigma)を計算するのが最尤推定だった。 最尤推定においては、(mu_{ML})、(sigma_{ML})が先にあって、その結果として(boldsymbol{x})があると考えている。 逆に、データ(boldsymbol{x}=(x_1,x_2,cdots,x_n))があって、 その結果として(mu)、(sigma)が確率的な分布をもって存在すると考える。 (mu)、(sigma)はデータ(boldsymbol{x}=(x_1,x_2,cdots,x_n))次第で確率的に決まる。 この分布を事後確率分布と呼んでいて、 事後確率分布を最大にする(mu_{MAP})、(sigma_{MAP})を求めるのが最大事後確率推定(MAP推定)。 まずは、事後確率分布と事前確率分布の関係をまとめてみて、 最大化すべき事後確率分布とは何なのか、詳細に追ってみる。 最尤推定 (N)個のデータ(boldsymbol{x}=(x_1,x_2,cdots,x_n))が観測されたとして、 観測値にはガウスノイズが含まれる(=観測値は正規分布に従う)。 (boldsymbol{x})が互いに独立であるならば、同時確率は周辺確率、つまり各々の確率の積で表せた。 (boldsymbol{x})は既知のデータ、統計量である(mu)、(sigma)は変数であるから、 同時確率は(mu)、(sigma)を変数とする関数として表せた。 begin{eqnarray} p(x | mu, sigma^2) = prod_{n=1}^{N}N(x_n|mu,sigma^2) end{eqnarray} 既にある観測値(boldsymbol{x})を最も高い確率で実現するための統計量(mu)、(sigma)が 一つ存在するという仮定のもと(boldsymbol{x})を推定した。 (p(x|mu,sigma^2))は、(mu)、(sigma)がとある値であるときに、 (boldsymbol{x})が発生する確率として捉えることができる(条件付き確率)。 事後確率分布 逆に、(boldsymbol{x})が得られたときに(mu,sigma^2)が得られる確率(p(mu,sigma^2|x))を考える。 (boldsymbol{x})自体が確率的に生起するデータであり、(mu,sigma^2)が(boldsymbol{x})次第でブレる。 そんな状況。 ベイズの定理を使って因果を入れ替えると以下の通り。 begin{eqnarray} p(mu, sigma^2 | x) = frac{p(boldsymbol{x|}mu,sigma^2)p(mu,sigma^2)}{p(boldsymbol{x})} end{eqnarray} 分子の(p(boldsymbol{x|}mu,sigma^2))は尤度。(p(mu,sigma^2))は事前分布。 データ(boldsymbol{x})が事前分布(p(mu,sigma^2))に従うと仮定したもので、 最初からそれがわかっていれば苦労しない訳だから、(p(mu,sigma^2))はわからない。 (p(boldsymbol{x}))はデータ(boldsymbol{x})が発生する確率。 観測することから得られる確率。(mu,sigma^2)に対して定数。 この(p(mu, sigma^2 | x))を最大化する(mu, sigma^2)を求めようというのが、 最大事後確率推定。 (p(mu, sigma^2 | x))の式において、分母の(p(boldsymbol{x}))は(mu, sigma^2)に対して定数だから、 (p(mu, sigma^2 | x))を最大化する(mu, sigma^2)は、分母を払った(p(boldsymbol{x|}mu,sigma^2)p(mu,sigma^2))も最大化する。 だから、代わりに(p(boldsymbol{x|}mu,sigma^2)p(mu,sigma^2))の最大化を考える。 begin{eqnarray} p(mu, sigma^2) p(x | mu, sigma^2) &=& N(x_n|mu,sigma^2) prod_{n=1}^{N}N(x_n|mu,sigma^2) \\ &=& N(x_n|mu,sigma^2) p(x | mu_{ML}, sigma^2_{ML}) end{eqnarray} 尤度関数(p(boldsymbol{x|}mu,sigma^2))の最尤解(mu_{ML})と(sigma^2_{ML})の求め方は前回記事。 [clink url=\"https://ikuty.com/2019/01/23/maximum_likelihood_estimation-2/\"] データさえあれば標本平均、標本分散を計算して終了。 begin{eqnarray} mu_{ML} &=& frac{1}{N}sum_{i=1}^{N}x_i \\ sigma^2_{ML} &=& frac{1}{N} sum_{i=1}^N (x_i -mu_{ML})^2 end{eqnarray} 尤度(prod_{n=1}^{N}N(x_n|mu,sigma^2))は計算により値が決まる。 begin{eqnarray} prod_{n=1}^{N}N(x_n|mu,sigma^2) &=& prod_{j=1}^{n} frac{1}{sqrt{2pi} sigma_{ML}} exp left( - frac{1}{2} left( frac{x_j -mu_{ML}}{sigma_{ML}} right)^2 right) end{eqnarray} 共役事前確率分布 さて... 事後確率と事前確率の関係をわかりやすく書くと以下の通り。 だが、事前確率(p(mu,sigma^2|x))は分からない。 begin{eqnarray} p(mu,sigma^2|x) = frac{p(x|mu,sigma^2)}{p(x)} p(mu,sigma^2) end{eqnarray} 事後確率分布(p(mu,sigma^2|x))が事前確率分布(p(mu,sigma^2))と同じ確率分布であれば、 事前確率分布と尤度の計算から事後確率分布を求められる。 そういう操作をするために、事後確率分布と同じ形の事前確率分布を仮に作っておいて、 まず事後確率分布と仮の事前確率分布を結ぶ式を立てる。 仮に立てた事前確率分布を、共役事前確率分布と言うらしい。 いきなり事前確率分布を(mu)、(sigma)だけをもつ別の確率分布に置き換えることはできないから、 確率分布を制御する超パラメータ(alpha,beta,gamma,cdots)を使って表現する。 正規分布の共役事前分布は逆ガンマ分布を使う。 逆ガンマ分布は4個の超パラメータ(alpha,beta,gamma,delta)を持つ。 begin{eqnarray} p(mu,sigma^2) = frac{sqrt{gamma}}{sigma sqrt{2pi}} frac{beta^alpha}{Gamma[alpha]} left( frac{1}{sigma^2} right)^{alpha+1} exp left( - frac{2beta + gamma (delta - mu)^2 }{2sigma^2} right) end{eqnarray} なので、ベイズの式は超パラメータ(alpha,beta,gamma,delta)を使って以下のように書ける。 左辺の事後確率分布と右辺の共役事前確率分布は同じ分布であって、 右辺には定数(1/p(x))と尤度( prod_{n=1}^{N}N(x_n|mu,sigma^2))をかけた形になっている。 begin{eqnarray} p(mu,sigma^2|x) &=& frac{p(x|mu,sigma^2)}{p(x)} p(mu,sigma^2) \\ &=& frac{1}{p(x)} prod_{n=1}^{N}N(x_n|mu,sigma^2) frac{sqrt{gamma}}{sigma sqrt{2pi}} frac{beta^alpha}{Gamma[alpha]} left( frac{1}{sigma^2} right)^{alpha+1} exp left( - frac{2beta + gamma (delta - mu)^2 }{2sigma^2} right) end{eqnarray} 左辺の事後確率を最大化する(mu,sigma)を見つけるわけだけども、 それは、尤度と共役事前確率で表された右辺を最大化する(mu,sigma)を見つけるのと同じ。 対数をとると積の部分が和になって計算しやすいから対数をとる。 やり方は(mu)、(sigma)で偏微分したものが0とする。 begin{eqnarray} frac{partial}{partial mu} p(mu,sigma^2|x) &=& frac{partial}{partial mu} frac{p(x|mu,sigma^2)}{p(x)} p(mu,sigma^2) \\ &=& frac{partial}{partial mu} frac{1}{p(x)} prod_{n=1}^{N}N(x_n|mu,sigma^2) frac{sqrt{gamma}}{sigma sqrt{2pi}} frac{beta^alpha}{Gamma[alpha]} left( frac{1}{sigma^2} right)^{alpha+1} exp left( - frac{2beta + gamma (delta - mu)^2 }{2sigma^2} right) \\ &=& 0 end{eqnarray} ちょっと計算しようとすると目眩がするけど、超パラメータ(alpha,beta,gamma,delta)はそのまま残して、 (mu,sigma)を(alpha,beta,gamma,delta)を含む式で表す。 begin{eqnarray} mu_{MAP} &=& frac{sum_{i=1}^{N}x_i + gamma delta}{N+gamma} \\ &=& frac{Nbar{x} + gamma delta}{N+gamma} end{eqnarray} 同様に、 begin{eqnarray} frac{partial}{partial sigma^2} p(mu,sigma^2|x) &=& frac{partial}{partial sigma^2} frac{p(x|mu,sigma^2)}{p(x)} p(mu,sigma^2) \\ &=& frac{partial}{partial sigma^2} frac{1}{p(x)} prod_{n=1}^{N}N(x_n|mu,sigma^2) frac{sqrt{gamma}}{sigma sqrt{2pi}} frac{beta^alpha}{Gamma[alpha]} left( frac{1}{sigma^2} right)^{alpha+1} exp left( - frac{2beta + gamma (delta - mu)^2 }{2sigma^2} right) \\ &=& 0 end{eqnarray} 計算できないけど begin{eqnarray} sigma^2_{MAP} &=& frac{sum_{i=1}^{N} (x_i-mu)^2 + 2beta + gamma (delta-mu)^2}{N+3+2alpha} \\ &=& frac{Nsigma^2 + 2beta + gamma (delta-mu)^2}{N+3+2alpha} end{eqnarray} 最尤推定による解と並べてみる。 平均、分散ともに、最尤解の分母、分子をパラメータ(alpha,beta,gamma,delta)で調整したものになっている。 begin{eqnarray} mu_{ML} &=& bar{x} \\ mu_{MAP} &=& frac{Nbar{x}+gamma delta}{N+delta} \\ sigma_{ML}^2 &=& bar{sigma^2} \\ sigma_{MAP}^2 &=& frac{Nsigma^2 + 2beta + gamma (delta-mu)^2}{N+3+2alpha} end{eqnarray} パターン認識と機械学習 上posted with amazlet at 19.01.22C.M. ビショップ 丸善出版 売り上げランキング: 21,190Amazon.co.jpで詳細を見る 統計学入門 (基礎統計学Ⅰ)posted with amazlet at 19.01.22東京大学出版会 売り上げランキング: 3,133Amazon.co.jpで詳細を見る [youtube id=QnqlP4O0TXs]
最尤推定
[mathjax] これは単なる趣味の記事です。 たいして数学が得意でもなかった偽理系出身のおっさんの懐古趣味。 PRMLは省略が多くて難しいです。 省略を紐解いても出てくるのが統計学入門レベルです。 鬼ですね。 ただ、厳密さを求めない簡単な説明をしてくれているところを先に読むと、 割と簡単に紐解けます。 \"互いに独立であること\"の復習 少し前に、\"違いに独立\"の定義を詳しめに読んだことがあった。 2つの確率変数(X)、(Y)があったとき、 (X=x)であり同時に(Y=y)である確率(P(X=x,Y=y)=f(x,y))について、 確率を定義できる空間(2次元ユークリッド空間(S)...)において、 (X,Y)が同時に起こる特別な事象(A)((S)の部分集合)を定義した。 begin{eqnarray} iint_S f(x,y)dydx = 1, f(x,y) geq 0 \\ P((X,Y) in A) = iint_A f(x,y)dydx end{eqnarray} 2変数の片方について積分した分布を周辺確率分布(marginal probability distribution)とよんだ。 begin{eqnarray} g(x) &=& sum_y f(x,y) \\ h(x) &=& sum_x f(x,y) end{eqnarray} (g(x),h(x))は、(f(x,y))において、片方の変数を固定して足し算したもの。 では、(g(x),h(x))から(f(x,y))を作れるかというと、 (g(x),h(x))が\"違いに独立\"である場合に限り、(g(x)とh(x))の積と(f(x,y))が等しくなった。 [clink url=\"https://ikuty.com/2018/10/17/statistical-independence/\"] 最尤推定 (N)個のデータ(boldsymbol{x}=begin{pmatrix}x_1 \\ x_2 \\ cdots \\ x_n end{pmatrix})が、平均(mu)、標準偏差(sigma)の正規分布(N(mu,sigma))から独立に生成されるとする。 また、(boldsymbol{x})は互いに独立であるとする。 各々のデータが発生する確率(P(x_i|mu,sigma)=Nleft(x_n | mu, sigma^2right))を 同時確率(P(boldsymbol{x}|mu,sigma))の周辺確率と考えることができる。 つまり以下の通り。 begin{eqnarray} p(boldsymbol{x} | mu, sigma^2 ) = prod_{n=1}^N Nleft(x_n | mu, sigma^2right) end{eqnarray} ここで、(boldsymbol{x})は既に与えられている訳なので、 (p(boldsymbol{x} | mu, sigma^2 ) )の変数部分は(mu)、(sigma)となる。 つまり、(p(boldsymbol{x} | mu, sigma^2 ) )は(mu)、(sigma)の関数。 (p(boldsymbol{x} | mu, sigma^2 ))を最大化する(mu)、(sigma)を求めようというのが、 最尤推定の考え方。 (p(boldsymbol{x} | mu, sigma^2 ) )の最大値を与える(mu)、(sigma)は、 両辺に対数をとった後でも最大値を与えるから、 計算しやすくするために両辺に対数をとる。 対数をとることで、右辺の積が対数の和になる。 高校数学で暗記したやつの連発。 begin{eqnarray} log p(boldsymbol{x} | mu, sigma^2 ) &=& log prod_{n=1}^N Nleft(x_n | mu, sigma^2right) \\ &=& -frac{1}{2sigma^2} sum_{n=1}^N left( x_n -mu right)^2 - frac{N}{2} log sigma^2 - frac{N}{2} log left( 2pi right) end{eqnarray} (mu)に関して最大化するためには、 (sigma)を定数として固定して(mu)で偏微分して、それが0とする。 右辺は第2項,第3項がゼロとなり、第1項の微分が残る。 begin{eqnarray} frac{partial }{partial mu} log p(boldsymbol{x} | mu, sigma^2 ) &=& frac{1}{sigma^2} sum_{n=1}^{N} left(x_n-muright) \\ &=& 0 end{eqnarray} これ、すなわち以下。 begin{eqnarray} frac{1}{sigma^2} left( sum_{n=1}^{N} x_n - Nmu right) &=& 0 \\ sum_{n=1}^{N} x_n &=& Nmu \\ mu = frac{1}{N} sum_{n=1}^{N} x_n end{eqnarray} なんと、(mu)は標本平均のときに最大化する!。 (sigma)に関して最大化するには、 (mu)を定数として固定して(sigma)で偏微分して、それが0とする。 これも高校数学で暗記したやつを連射する。 begin{eqnarray} frac{partial }{partial sigma} log p(boldsymbol{x} | mu, sigma^2 ) &=& left( -frac{1}{2sigma^2} sum_{n=1}^N left( x_n -mu right)^2 right)\' - left( frac{N}{2} log sigma^2 right)\' \\ &=& frac{1}{sigma^3} sum_{n=1}^N left( x_n -mu right)^2 - frac{N}{sigma} \\ &=& 0 end{eqnarray} これ、すなわち以下。 begin{eqnarray} frac{N}{sigma} = frac{1}{sigma^3} sum_{n=1}^N left( x_n -mu right)^2 \\ sigma^2 = frac{1}{N} sum_{n=1}^N left( x_n -mu right)^2 \\ end{eqnarray} なんと、(sigma^2)は標本分散のときに最大化する。 なぜ、(mu)、(sigma)それぞれ独立に偏微分して求めた値を使って、 もう片方を計算して良いのか、については時間がないから省略。 標本分散は不偏分散ではないことに起因して、 最尤推定により求められた分散は、不偏分散から過小に評価される。 [clink url=\"https://ikuty.com/2018/10/27/unbiased_variance/\"] PRMLでは、多項式曲線のフィッティングに最尤推定を使う内容になっていて、 最尤解が不偏でないことがわかりやすくなっている。 次回は、最大事後確率推定(maximum posterior)について。 以下を最大化する。 begin{eqnarray} pi(mu) L(mu) = frac{1}{sqrt{2pi}sigma_m} exp left( -frac{1}{2} left(frac{mu}{sigma_m}^2 right)right) prod_{j=1}^{n} frac{1}{sqrt{2pi} sigma_v} exp left( - frac{1}{2} left( frac{x_j -mu}{sigma_v} right)^2 right) end{eqnarray} パターン認識と機械学習 上posted with amazlet at 19.01.22C.M. ビショップ 丸善出版 売り上げランキング: 21,190Amazon.co.jpで詳細を見る 統計学入門 (基礎統計学Ⅰ)posted with amazlet at 19.01.22東京大学出版会 売り上げランキング: 3,133Amazon.co.jpで詳細を見る
標本調査に必要なサンプル数の下限を与える2次関数
[mathjax] 2項分布に従う母集団の母平均を推測するために有意水準を設定して95%信頼区間を求めてみた。 母平均のあたりがついていない状況だとやりにくい。 [clink url=\"https://ikuty.com/2019/01/11/sampling/\"] (hat{p})がどんな値であっても下限は(hat{p})の関数で抑えられると思ったので、 気になって(hat{p})を変数のまま残すとどうなるかやってみた。 begin{eqnarray} 1.96sqrt{frac{hat{p}(1-hat{p})}{n}} le 0.05 \\ frac{1.96}{0.05}sqrt{hat{p}(1-hat{p})} le sqrt{n} \\ 39.2^2 hat{p}(1-hat{p}) le n end{eqnarray} 左辺を(f(hat{p}))と置くと (f(hat{p}))は下に凸の2次関数であって、 (frac{d}{dhat{p}}f(hat{p})=0)の時に最大となる。というか(hat{p}=0.5)。 (hat{p}=0.5)であるとすると、これはアンケートを取るときのサンプル数を求める式と同じで、 非常に有名な以下の定数が出てくる。 begin{eqnarray} 1537 * 0.5 (1-0.5) le n \\ 384 le n end{eqnarray} (hat{p})がどんな値であっても、サンプル数を400とれば、 有意水準=5%の95%信頼区間を得られる。 だから、アンケートの(n)数はだいたい400で、となる。 さらに、有意水準を10%にとれば、(n)の下限は100で抑えられる。 なるはやのアンケートなら100、ちゃんとやるには400、というやつがこれ。
標本調査に必要なサンプル数を素人が求めてみる。
[mathjax] ちょっと不思議な計算をしてみる。 仮定に仮定を積み重ねた素人の統計。 成功か失敗かを応答する認証装置があったとする。 1回の試行における成功確率(p)は試行によらず一定でありベルヌーイ試行である。 (n)回の独立な試行を繰り返したとき、成功数(k)を確率変数とする離散確率変数に従う。 二項分布の確率密度関数は以下の通り。 begin{eqnarray} P(X=k)= {}_n C_k p^k (1-p)^{n-k} end{eqnarray} 期待値、分散は、 begin{eqnarray} E(X) &=& np \\ V(X) &=& np(1-p) end{eqnarray} (z)得点(偏差値,つまり平均からの誤差が標準偏差何個分か?)は、 begin{eqnarray} z &=& frac{X-E(X)}{sigma} \\ &=& frac{X-E(X)}{sqrt{V(X)}} \\ &=& frac{X-np}{sqrt{np(1-p)}} end{eqnarray} であり、(z)は標準正規分布に従う。 これを標本比率(hat{p}=frac{X}{n})を使うように式変形する。 begin{eqnarray} z &=& frac{frac{1}{n}}{frac{1}{n}} frac{X-np}{sqrt{np(1-p)}} \\ &=& frac{frac{X}{n}-p}{sqrt{frac{p(1-p)}{n}}} \\ &=& frac{hat{p}-p}{sqrt{frac{p(1-p)}{n}}} end{eqnarray} (n)が十分に大きいとき、(z)は標準正規分布(N(0,1))に従う。 従って、(Z)の95%信頼区間は以下である。 begin{eqnarray} -1.96 le Z le 1.96 end{eqnarray} なので、 begin{eqnarray} -1.96 le frac{hat{p}-p}{sqrt{frac{p(1-p)}{n}}} le 1.96 end{eqnarray} (hat{p})は(p)の一致推定量であるから、(n)が大なるとき(hat{p}=p)とすることができる。 begin{eqnarray} -1.96 le frac{hat{p}-p}{sqrt{frac{hat{p}(1-hat{p})}{n}}} le 1.96 \\ end{eqnarray} (p)について解くと(p)の95%信頼区間が求まる。 begin{eqnarray} hat{p}-1.96 sqrt{frac{hat{p}(1-hat{p})}{n}} le p le hat{p}+1.96 sqrt{frac{hat{p}(1-hat{p})}{n}} end{eqnarray} 上記のにおいて、標準誤差(1.96sqrt{frac{hat{p}(1-hat{p})}{n}})が小さければ小さいほど、 95%信頼区間の幅が狭くなる。この幅が5%以内であることを言うためには以下である必要がある。 (有意水準=5%) begin{eqnarray} 1.96sqrt{frac{hat{p}(1-hat{p})}{n}} le 0.05 end{eqnarray} 観測された(hat{p})が(0.9)であったとして(n)について解くと、 begin{eqnarray} 1.96sqrt{frac{0.9(1-0.9)}{n}} le 0.05 \\ frac{1.96}{0.05} sqrt{0.09} le sqrt{n} \\ 11.76 le sqrt{n} \\ 138.2 le n end{eqnarray} 139回試行すれば、100回中95回は(p)は以下の95%信頼区間に収まる。 つまり95%信頼区間は以下となる。 begin{eqnarray} hat{p}-1.96 sqrt{frac{hat{p}(1-hat{p})}{n}} &le& p le hat{p}+1.96 sqrt{frac{hat{p}(1-hat{p})}{n}} \\ 0.9-1.96 frac{sqrt{0.09}}{sqrt{139}} &le& p le 0.9 + 1.96 frac{sqrt{0.09}}{sqrt{139}} \\ 0.9-1.96 frac{0.3}{11.78} &le& p le 0.9+1.96 frac{0.3}{11.78} \\ 0.85 &le& p le 0.95 end{eqnarray} (n)を下げたい場合は有意水準を下げれば良い。 統計的に有意水準=10%まで許容されることがある。 有意水準が10%であるとすると、(n)は35以上であれば良いことになる。 begin{eqnarray} 1.96sqrt{frac{0.9(1-0.9)}{n}} le 0.1 \\ frac{1.96}{0.1} sqrt{0.09} le sqrt{n} \\ 5.88 le sqrt{n} \\ 34.6 le n end{eqnarray} 信頼区間と有意水準の式において(p)を標本から取ってきたけど、 アンケートにおいてYes/Noを答える場合、(p)は標本における最大値(つまり0.5)を 設定して(n)を求める。 つまり、(p)として利用するのは標本比率ではないのかな?と。 このあたり、(hat{p})を変数として残すとどういうことがわかった。 [clink url=\"https://ikuty.com/2019/01/13/sampling_with2/\"]
とりあえずチャットアプリを作ってみる
Kotlin製のChat UI Libraryを写経しながらAndroidアプリを作る練習。 もちろんサーバ側は無いので、文字列決め打ちで出してるだけ。 昔作ったJava製のものと基本的なことは変わっていない様子。 https://github.com/bassaer/ChatMessageView.git null安全性の絶対的な安心感。これに尽きる。。 自分のような素人が書いてもぬるぽで落ちないというのは奇跡!。 Swiftとそっくりという噂なので、 もしかするとKotlinからSwiftに移植するのは簡単なのかも。
一覧から詳細の起動_一覧側(MainActivity)
一覧から詳細を起動する一覧側(MainActivity.kt)の実装。 ListViewのsetOnItemClickListener()の書き方。 setOnItemClickListener()の引数はAdapterView.OnItemClickListenerオブジェクトを取る。 OnItemClickListenerオブジェクトは1つの仮想関数(Single Abstract Method)を持っていて、 それを実装したものを渡すことになる。 本来、OnItemClicListenerを派生したオブジェクトでSAMを実装したものを渡す、という とても長い記述になるのだけれども、仮想関数が1個だけならば代わりにラムダ式を1個渡せば良い。 (SAM変換)。 ActivityからIntentを取得してIntentからActivityを起動する、という書き方。 Intentを取得するときにパラメタ(今回の場合はArticleインスタンス)が渡る。 package com.example.ikuty.myapplication import android.support.v7.app.AppCompatActivity import android.os.Bundle import android.widget.ListView import com.example.ikuty.myapplication.model.Article class MainActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) val listAdapter = ArticleListAdapter(applicationContext) val dummyArticle1 = dummyArticle(\"ikuty.com記事1\",\"ikuty1\") val dummyArticle2 = dummyArticle(\"ikuty.com記事2\",\"ikuty2\") listAdapter.articles = listOf(dummyArticle1,dummyArticle2) val listView: ListView = findViewById(R.id.list_view) as ListView listView.adapter = listAdapter listView.setOnItemClickListener { adapterView,view, position, id -> val article = listAdapter.articles[position] ArticleActivity.intent(this, article).let { startActivity(it) } } } }
一覧から詳細の起動 – ParcelableなインスタンスをIntentに付与してActivityを起動する
記事一覧画面でアイテムを触るとそのアイテムの詳細画面が開くというのをやりたい。 記事詳細画面のController(Activity)を書く。 Intent まずIntent。Activityを起動する際にパラメタの役割を果たす。 Intentには「意図」「目的」の意味がある。Activityを起動する意図とか目的とか。 ベースとなるIntentに付加的な情報を付与(putExtra)することで「意図」「目的」となる。 Intentには基本型の他にカスタムオブジェクトを付与することができる。 ただし、カスタムオブジェクトはParcelableインターフェースを実装している必要がある。 あるActivityから別のActivityを起動する際のテクニックとして、 起動される側のActivityが持つintent()メソッドを使ってIntentを作成し(つまりパラメタを渡し)、 作成したIntent経由でActivityを起動する。 companionオブジェクトにfun intent(context: Context, article: Article): Intentを作る。 このintent()を経由すれば起動に必要なパラメタ(今回はarticle: Article)を付与したIntentを取得できる。 intent()内ではputExtra()を使ってarticleとIntentを紐づけている。 onCreate(savedInstanceState: Bundle?) onCreate()が発火したときにはIntentにParcalableなArticleインスタンスが付与済み。 Activityが持つintentからgetParcelableExtra()によりArticleインスタンスを取得して、 ビューにそのArticleインスタンスを表示する。 package com.example.ikuty.myapplication import android.content.Context import android.content.Intent import android.os.Bundle import android.support.v7.app.AppCompatActivity import android.webkit.WebView import com.example.ikuty.myapplication.model.Article import com.example.ikuty.myapplication.view.ArticleView class ArticleActivity: AppCompatActivity() { companion object { private const val ARTICLE_EXTRA: String = \"article\" fun intent(context: Context,article: Article): Intent = Intent(context, ArticleActivity::class.java) .putExtra(ARTICLE_EXTRA, article) } override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_article) val articleView = findViewById(R.id.article_view) as ArticleView val webView = findViewById(R.id.web_view) as WebView val article: Article = intent.getParcelableExtra(ARTICLE_EXTRA) articleView.setArticle(article) webView.loadUrl(article.url) } }
リストビューを表示するためにListViewAdapterを実装する
ListViewAdapter リストビューを表示するためにListViewAdapterを実装する。 BaseAdapterを継承して必要なメソッドを実装する。 実装が必要なのは以下。 fun getCount(): Int fun getItem(position: Int): Any? fun getItemId(position: Int): Long fun getView(position:Int,convertView: View?,parent: ViewGroup?): View fun getCount(): Int 名前の通り、リストビューが保持するアイテムの個数を返す。 ListViewAdapterが保持するList<Article>のサイズを返すようにする。 fun getItem(position: Int): Any? 名前の通り、指定した位置のアイテムを返す。 保持するList<Article>の要素を返すようにする。 fun getItemId(position: Int): Long 指定した位置のアイテムに関するアプリケーション固有のIDを返す。 ListViewの外から「位置->固有ID」を取得できる。 アイテムを取得してアイテムからIDを取るんじゃダメなのか...。 Get the row id associated with the specified position in the list. fun getView(position:Int,convertView: View?,parent: ViewGroup?): View 指定した位置のアイテムを表示するためのビューを取得する。 convertViewには、画面から表示しきれなくなったViewが来る。 既に画面に表示されているならばnullが来る。 画面から表示しきれなくなったView(convertView)を使い回すことで負荷削減する。 Get a View that displays the data at the specified position in the data set. convertViewをArticleViewにダウンキャストした結果をViewとする。 ダウンキャストがnullならばエルビス演算子(?:)の右辺が評価され、新しいArticleViewが作られる。 View = ((convertView as? ArticleView) ?: ArticleView(context)).apply { setArticle(articles[position]) package com.example.ikuty.myapplication import android.content.Context import android.view.View import android.view.ViewGroup import android.widget.BaseAdapter import com.example.ikuty.myapplication.model.Article import com.example.ikuty.myapplication.view.ArticleView class ArticleListAdapter(private val context: Context): BaseAdapter() { var articles: List = emptyList() override fun getCount(): Int = articles.size override fun getItem(position: Int): Any? = articles[position] override fun getItemId(position: Int): Long = 0 override fun getView(position: Int, convertView: View?, parent: ViewGroup?): View = ((convertView as? ArticleView) ?: ArticleView(context)).apply { setArticle(articles[position]) } }
						