賃貸物件データをスクレイピングして散布図を出力してみた話
Hello CRISP-DM 1日目。 統計の基本は一応学びなおしたつもりなのと、 機は熟した感があり、Hotなところを突いてみる。 多変量解析と機械学習 多変量解析は、統計学の1分野で、人間が説明できるモデルを作ることが目的。 モデルができれば予測できるんじゃなかろうか、とも思うけども、 対比されちゃってるけど、統計学なんて凄まじい歴史がある訳で、 予測のための新しいツールとして機械学習があるという話。 モデルを説明することを放棄して、高性能な予測なり分類なりをしましょうと。 説明できないモデルを使って責任ある決定とか出来んのか、というのもあり。 完全に理解することを放棄するという位置付けでもない様子。 ルールベースと機械学習 予測器としての側面で、ルールベースとの比較がある。 密度関数が100%理解可能(予測値が出現する規則が100%理解可能)なのであれば、 いっそのこと全てif文で書けば良いわけで、わざわざ難しくする必要はなく..。 ルールベースの予測器を作ってみて結構上手く行った経験があって、 これはこれで何が起きても説明できるし、これで行けるならこれで良いかも。 Windows95の時代からある。 家賃予測してみる 家賃の予測モデル?を作るのを目的にCRISP-DMというプロセスを回す話。 ドメイン領域から目的変数と説明変数を取り出して、モデルを作って評価して、 正しくなさそうなら元に戻ってやり直す、という普通っぽいやり方にCRISP-DMという名前が付いてる。 データマイニングのプロセスで、特別に何か特別なツールを使わなければならない、ということはなし。 kaggleのTitanic..だと確かにリアル感がないのと、 結構、家賃だけでも知らないデータが埋もれてるものだな、と関心したり。 スクレイピングしてみる suumoから地元の賃貸物件情報を取得して表示してみた。 スクレイピングするためのコードはこちらを参考にしてみた。 同時接続数が1なので4200件くらいの取得に8時間くらいかかってしまうけど、 待てば良い話なので、寝る前にスタートして起きたら確認。 ログだとかセンサーだとかWebだとか、いろいろなデータソースがあるけれども、 取ってくるためにはかなり泥臭い感じになるし、他に方法がないのかも。 なるべくコードを書かずに済ませたい、けども、致し方ないのかも。 Octparseで取ろうとしたらWindows専用でMacのは無いと言われ..。 散布図を作る pandas(panel datas)というPython製のライブラリと、 seabornというPython製の描画ライブラリだけで簡単に散布図が出来る。 散布図出しただけで気分が良いな。 データが規則正しく並んでるのが気持ちが良いのか。 現実世界に存在する賃貸物件の情報が綺麗に2軸のグラフに収まってる。 もうこれだけで目的の半分くらいは達成したんじゃなかろうか..。 データの次数は5なので5x5の散布図ができる。 散布図作成時にある列に関して色分けできる機能があって、 試しに階数で色分けしてみた。微妙..。 suumoで期待した通りの書式になってなってないデータは0を入れてるので、 下限の0に張り付いてるデータが存在する。 1Kとか2LDKとか、間取りの区別はしていないから同時に出る。 家賃と占有面積は関係があるということがわかる様子。 広い方が高いよ、は言える様子。 他の因子に引きずられず、築浅であろうが駅近であろうが、広さは正義らしい。 家賃と築年数は、全体的に右肩下がりだけども、 占有面積ほど関係があるとは言えない様子。 現実的には、築浅だとしても駅から遠ければお安い、という話があると思い..。 全体的に右肩下がりなので、そうであっても古ければ概ね安いという。 もっと意外なのは、家賃と最寄駅からの時間。 駅近であることが一番影響してそうに思ってたけどもそんなことはなかった。 確かに、駅近であっても古くて狭いのは安いからな。 占有面積と築年数ほど、家賃に影響を与えないらしい。 思い込みというのは怖いもの..。 多変量解析なら、どれの係数が高いかはここで当たりがつくけれども、 ランダムフォレストで家賃予測するとすると、どうなるか、次回に続く。
上下左右に黒帯を出さずにYouTube動画(16:9)のIFRAMEのアスペクト比を変える
YouTubeの動画は16:9なので、16:9以外のアスペクト比にすると、 動画が内接するように上下左右に黒帯が出る。 アスペクト比を変えた矩形にクリッピングして、 それをレスポンシブにする例がなかったので自作してみた。 どういうことかというと、下のイラストみたいな感じ。 大きい方の矩形がYouTubePlayerのIFRAME。 内接する小さい方が作りたいアスペクト比の矩形。 明るい方だけを抜き出してレスポンシブにします。 (動画の余った部分は欠けます) コード達 16:9(56.25%)を53.88%に切り抜いてレスポンシブ化するHTML,CSS,JS。 Chrome,Safari(PC)とFirefoxで確認済み。 HTMLは以下。 <div class=\"youtube_wrapper\"> <iframe class=\"youtube_content\" id=\"youtube_content\" frameborder=\"0\" allow=\"accelerometer; autoplay; encrypted-media;\" src=\"https://www.youtube.com/embed/{video-id}\"></iframe> </div> CSSは以下。 .youtube_wrapper { position:relative; width:100%; overflow:hidden; margin:auto; margin-bottom:8px; } .youtube_wrapper:before { content:\"\"; display:block; padding-top: 53.88%; } .youtube_content { position:absolute; top:50%; left:50%; margin:auto; transform: translate(-50%,-50%); width:100%; } IFRAMEの最初の高さだけCSS化できなかった...。 最初の高さを設定するJSは以下。jQueryにするなんなりしてください。 window.onload = function(){ var elem = document.getElementById(\'youtube_content\'); if (elem) { elem.style.height = elem.clientWidth * 0.5625 + \'px\'; } };
共役事前確率分布と逆ガンマ分布再考
[mathjax] 今回から、atomにmathjax-wrapperを入れてatomで数式入り文章を書いてみる。 数式を書くと瞬時にプレビューができて、10倍は速くなった気がする。1時間くらいで書いてみる。 母集団の(sigma)、(mu)が既知なのなら、共役事前分布は正規分布で良いのだから、 一体何をやりたいのか...という記事になってることに1日経って気づいた...。 チラシの裏程度なので気にしない。 何故、事前分布と事後分布を同じ確率分布に揃えるかというと、計算済みの事後分布を次の事前分布として 使うことができるから。事後分布の計算を繰り返していくと理論上は精度が上がっていく。 事前分布と事後分布を同じ分布にするために、逆ガンマ分布を事前分布として採用する。 そうすることで、左辺の事後分布と、右辺の尤度x共役事前分布が共に逆ガンマ分布となる。 (母平均が既知で分散が未知、という条件がつく。) 正規分布の共役事前確率分布として逆ガンマ分布を使う、というところまでは良かったのだけども、 逆ガンマ分布のパラメタが巷には2種類なのと4種類なのがあってもやもや。 ガンマ分布をさらに一般化した一般化ガンマ分布が4パラメータなのだそうな。 そもそもガンマ分布はカイ二乗分布と指数分布の一般化なので、どこまで一般化するのか。 ひとまず触れてはいけないところに触れてしまったようなので、2パラメータで出直してみる。 合ってるんだか間違ってるんだかも不明なのだけども、結論としては、細かいことはどうでもよくて、 事後分布と尤度x共役事前分布の関係を脳裏に焼き付けるためのプロセス。 何周かしないと真理にはたどり着けない...。 ガンマ分布と逆ガンマ分布 ガンマ分布の確率密度関数は以下。(alpha)は形状パラメータ,(beta)はスケールパラメータ。 (alpha) を固定して (beta)を動かすとカイ二乗分布。 (beta) を固定して (alpha)を動かすと指数分布。 Excelで (alpha) と (beta)をグリグリ動かしてみると意味がわかる。面白い。 良くできてるなー。 $$ begin{eqnarray} f(x|alpha,beta) = frac{1}{beta^alpha Gamma(alpha)} x^{alpha-1} expleft(-frac{x}{beta}right) end{eqnarray} $$ そして、(alpha) を大きくしていくと形状が正規分布に近づいていく。 累積確率分布関数は以下。 $$ begin{eqnarray} F(x|alpha,beta) = frac{1}{beta^alphaGamma(alpha)} int_0^xt^{alpha-1} exp (e^{-frac{t}{beta}})dt end{eqnarray} $$ 対して逆ガンマ分布の確率密度関数は以下。ガンマ分布で(x\'=frac{1}{x}) とすると出てくる。 $$ begin{eqnarray} f(x|alpha,beta)=frac{beta^{alpha}}{ Gamma(alpha)} x^{-(alpha+1)} expleft(-frac{beta}{x}right) end{eqnarray} $$ The shorthand for the distribution, X~inverted gamma(α,β), or IG(α, β), means that a random variable X has this distribution with positive parameters α and β. 再びベイズ統計へ ベイズの定理を使って出てくる事後確率分布と事前確率分布の関係は以下の通りだった。 $$ begin{eqnarray} p(mu,sigma^2|boldsymbol{x})&=&frac{p(boldsymbol{x}|mu,sigma^2)p(mu,sigma^2)}{p(boldsymbol{x})} \\ &propto& p(boldsymbol{x}|mu,sigma^2)p(mu,sigma^2) end{eqnarray} $$ 尤度は周辺確率の積として表されるけれども、母集団の平均が既知で分散が未知の場合の話をするので、 確率変数(X)を分散にもつ正規分布を考える。(y)は標本平均。(mu)は既知の母平均。 $$ begin{eqnarray} p(x | mu, X) &=& N(x|mu,X) \\ &=& frac{1}{sqrt{2pi x}}expleft(-frac{(y-mu)^2}{2x}right) end{eqnarray} $$ 正規分布である尤度に逆ガンマ分布をかけて式変形していく。 比例の関係を見たいので定数をのぞいてシンプルにするところがポイント。 $$ begin{eqnarray} p(mu,sigma^2|boldsymbol{x}) &propto& frac{1}{sqrt{2pi x}}expleft(-frac{(y-mu)^2}{2x}right) frac{beta_0^alpha}{Gamma(alpha_0)} left(frac{1}{x}right)^{alpha_0+1} expleft(frac{-beta_0}{x}right) \\ &propto & frac{1}{x} exp left( - frac{(y-mu)^2}{2x}right) x^{-alpha_0-2} exp left( -frac{beta_0}{x} right) \\ &propto & x^{-alpha_0-2} exp left( -frac{1}{x}(beta_0 + frac{1}{2}(y-mu)^2 right) end{eqnarray} $$ 右辺の確率分布が逆ガンマ分布であるためには、定数のぞいた箇所、つまり(x)の肩と(exp)の肩が 逆ガンマ分布のそれと同じでなければならないから、 $$ begin{eqnarray} -(alpha_0-2) &=& alpha +1 \\ alpha &=& alpha_0+frac{1}{2} \\ end{eqnarray} $$ また、 $$ begin{eqnarray} beta &=& beta_0 + frac{1}{2} (y-mu)^2 end{eqnarray} $$ これで、左辺の逆ガンマ分布の (alpha)、(beta)が作れる。 更新するたびに (alpha)、(beta)が増加していくのがわかる。
ミドルウエア
この記事は自分の勉強のために書いています。 ソースは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に移植するのは簡単なのかも。