【ゼロから作るDeep Learning】要点まとめ 第4章, 第5章
4章 - ニューラルネットワークの精度を高めるためには
損失関数
- ニューラルネットワークで出力された結果に対して、損失関数を使ってニューラルネットワークの精度の悪さを求める。
- 損失関数の引数は、ニューラルネットワークの出力結果と、正解ラベル(教師データ)
- 二乗和誤差
- 交差エントロピー誤差
- たくさんのデータの中から無作為に幾つか抽出して損失関数に入れ込むのをミニバッチという。
- 損失関数の結果が最小になるようなパラメータを探すことで、精度を高めることができる。
勾配法
- 微分 = ある関数でのパラメータの変化の様子
- 偏微分 = 引数が複数ある関数での1つの引数の微分のことを示す
- 勾配 = 偏微分をベクトル(一次元配列)で示したもの。勾配が示す方向は、各場所において関数の値を最も減らす方向
- 損失関数の結果を最小にするには、勾配法(学習率を使って決められたステップ数、重みパラメータの初期値から勾配を引いて更新していく。)を使用する。
- 上記のような学習を最急降下法(SGD)という
- 勾配法で使用する学習率はハイパーパラメータといい、唯一人間が与える必要があるもの(重みパラメータは勾配法を使用して自動的に更新される想定)
5章 - 誤差逆伝播法
順伝播(Affine)で解いた結果から、逆の方向から各隠れ層の微分を求めることで、 重みパラメータの勾配を高速に求めることができる。
誤差逆伝播法を使用するには以下の組み合わせが必要。 - 分類問題では出力層でソフトマックス関数、損失関数に交差エントロピー誤差を使用する。 - 回帰問題では出力層で恒等関数、損失関数に二乗和誤差を使用する。
【ゼロから作るDeep Learning】要点まとめ 第3章ニューラルネットワーク
最近オライリー社から出版されている「ゼロから作るDeep Learning」を読み始めた。
数学の知識がない僕には結構難しかったので、備忘録として要点をまとめていこうと思う。
1,2章に関してはpythonの話や、ANDゲート等の基本情報的な内容が多いので、ここでは省略する。
3章 - ニューラルネットワークについて
Affine空間
- 入力値xと重みパラメータ、バイアスを使って行列の計算をする。 例)x1, x2、一層目の隠れ層が3つのノードからなるとき。 入力値が2つで出力が3つを求められるので、重みは3×2の配列で必要。
x1w11 + x2w12 + b = a1 x1w21 + x2w22 + b = a2 x1w31 + x2w32 + b = a3
↑のような行列の計算をAffineという。
- 一層目に出力された値を活性化関数を使って更に計算する(シグモイド関数、RELU)
- 最終層への出力時は、活性化関数に、恒等関数(回帰問題)、ソフトマックス関数(分類問題)を使用する。
活性化関数
Affineの結果から次のノードに渡すべき値を出力する関数。
- シグモイド関数
- RELU(出力の値が0以上ならそのまま値を、0未満なら0を出力する)
出力層の活性化関数
- ソフトマックス関数: 出力される値の合計が1になるため、確率を求めることができる。分類問題に使用される。基本的にAffineの結果が一番大きい値を使用するので、学習フェーズ以外では使用されない。
- 恒等関数: 入力された値をそのまま返却する。回帰問題に使用される。
出力層のノードの個数
期待する候補の数だけ必要。 例えば与えられた数値が何なのかを判定する場合は0~9のうちのどれかということになるので、 出力層は10個になる。
バッチ処理
画像のような二次元配列のデータを使う場合に、纏めて複数のデータをニューラルネットワークに投入して結果を得る方法のこと。
例) 10個の画像データ。1データが28 * 28の784ピクセル隠れ層は2つ、それぞれ50, 100のノードを保つ場合。
x = 100 × 784 w1 = 784 × 50(隠れ層1への重み付け) w2 = 50 × 100(隠れ層2への重み付け) w3 = 100 × 10(出力層への重み付け)
railsでポリモーフィック関連でインターフェースっぽく振る舞わせる
railsにもインターフェースはないのかな?と思い調べてみると、
ポリモーフィック関連というものを使うそうです。
例として、Article、Eventという二つのモデルを用意し、
その両方ともにCommentモデルをhas_manyの関係で保持させたいとします。
テーブル定義
- articlesテーブル
id | ID |
---|---|
title | タイトル |
body | 本文 |
created_at | 作成日時 |
updated_at | 更新日時 |
- eventsテーブル
id | ID |
---|---|
title | タイトル |
body | 内容 |
created_at | 作成日時 |
updated_at | 更新日時 |
- commentsテーブル
id | ID |
---|---|
commentable_id | 関連元レコードID |
commentable_type | 関連元レコード種別 |
name | 発言者名 |
body | 内容 |
created_at | 作成日時 |
updated_at | 更新日時 |
commentable_id
は、どのレコードに対するコメントなのか、
commentable_type
は、commentable_id
に対してどのテーブルへの関連なのかを示します。
モデル定義
class Article < ApplicationRecord has_many :comments, as: :commentable end
class Event < ApplicationRecord has_many :comments, as: :commentable end
class Comment < ApplicationRecord belongs_to :commentable, polymorphic: true, inverse_of: :comments end
has_manyに as: :commentable
とすることで、ポリモーフィック関連でのアソシエーションを生成します。
commentモデル側では belongs_to :commentable, polymorphic: true
とすることで、
article、eventのどちらのモデルからも参照される・することを定義しています。
インターフェース部分を別ファイルに切り出すと、以下のような感じ
class Article < ApplicationRecord include Commentable end
class Event < ApplicationRecord include Commentable end
module Commentable extend ActiveSupport::Concern included do has_many :comments, as: :commentable end end
class Comment < ApplicationRecord belongs_to :commentable, polymorphic: true, inverse_of: :comments end
has_manyな関連テーブルのレコードのカラムでソート
チャットとかchat(チャットルーム) has_many chat_messages(チャットのメッセージ)みたいなテーブル構造になると思うが、 この時、チャットルームをメッセージが来た順に並び変えたいとかあると思う。 メッセージの投稿日時がmaxのもので比較してソートする必要があるが、書き方がわからなかったので調べた。
テーブル構造はこんな感じ
chatsテーブル
id | ID |
---|---|
created_at | 作成日時 |
updated_at | 更新日時 |
chat_messagesテーブル
id | ID |
---|---|
chat_id | チャットID |
message | 本文 |
created_at | 作成日時 |
updated_at | 更新日時 |
select chats.*, max_created_at from chats left outer join ( select max(chat_messages.created_at) max_created_at , chat_id from chat_messages group by chat_id ) cm on chats.id = cm.chat_id order by max_created_at desc;
Sweet Alertで簡単に綺麗なアラート画面を作る
普通にjavascriptでalert()を使うと味気ないアラートウィンドウしか出ないが、sweet alertを使うと簡単に綺麗なアラート画面を出せる。
jQuery依存でなくプレーンなjavascriptで使用できる。
導入
<link rel="stylesheet" type="text/css" href="dist/sweetalert.css"> <link rel="stylesheet" type="text/css" href="themes/twitter.css">
- あとはjavascriptでswal()と呼び出すだけで綺麗なアラートが出る。
swal({ title: "データを消しますか?", text: "消されたデータは元に戻すことができません。", type: "warning", showCancelButton: true, confirmButtonColor: "#DD6B55", confirmButtonText: "OK", cancelButtonText: "キャンセル", closeOnConfirm: false }, function(){ swal("削除しました!", "データは正常に削除されました。", "success"); });
細かい使い方は公式を参照。
モーダルのサンプル
なにかとモーダルが使われているサービスが多く、気になったので調べた。
z-indexを使ってモーダルと、モーダルの後ろの画面を隠すためのオーバーレイを前面にするのがポイント。
あとはボタン押下でdisplay: noneを解除してあげればOK。
<html lang="ja"> <head> <title>test</title> <link href="test.css" rel="stylesheet" type="text/css" /> <script src="https://ajax.googleapis.com/ajax/libs/jquery/3.2.1/jquery.min.js"></script> </head> <body> <!-- モーダルのオープン --> <a id="modal-open" class="button-link">クリックするとモーダルウィンドウを開きます。</a> <!-- モーダルのボディ --> <div id="modal-content"> <p>「閉じる」か「背景」をクリックするとモーダルウィンドウを終了します。</p> <a id="modal-close" class="button-link">閉じる</a> </div> <!-- モーダルを強調させるためのオーバーレイ --> <div id="modal-overlay"></div> </body> <script type="text/javascript"> // モーダル表示 $("#modal-open").click( function(){ $(this).blur(); if ($('#modal-overlay').css('display') != 'none') { return; } $("#modal-overlay, #modal-content").fadeIn("fast"); centeringModalSyncer(); } ); // モーダル非表示 $("#modal-overlay,#modal-close").unbind().click(function(){ $("#modal-content,#modal-overlay").fadeOut("fast"); }); //センタリングをする関数 function centeringModalSyncer(){ var w = $(window).width(); var h = $(window).innerHeight(); var cw = $("#modal-content").outerWidth(true); var ch = $("#modal-content").outerHeight(true); var pxleft = ((w - cw)/2); var pxtop = ((h - ch)/2); $("#modal-content").css({"left": pxleft + "px"}); $("#modal-content").css({"top": pxtop + "px"}); } </script> </html>
#modal-overlay{ z-index:1; display:none; position:fixed; top:0; left:0; width:100%; height:120%; background-color:rgba(0,0,0,0.3); } #modal-content{ z-index:2; display:none; position:fixed; width:50%; margin:1.5em auto 0; padding:10px 20px; border:2px solid #aaa; background:#fff; } .button-link{ color:#00f; text-decoration:underline; } .button-link:hover{ cursor:pointer; color:#f00; }
こちらの記事を参考にさせていただきました。
Railsでgemなしでログイン機能を実装
はじめに
Railsでは便利なログイン機能を実装してくれるdeviseやsorceryといったgemが存在します。
導入するだけでsign_in
やsign_out
といったログインに必要なメソッドを自動的に生成してくれますが、
カスタマイズをする場合には、内部のソースを解読する必要があったりします。
Rails4ではhas_secure_password
という便利なメソッドが導入され、ログイン機能くらいならgemなしでも簡単に実装できるのでまとめてみました。
ログインの仕様を考える
よくあるログイン画面では、Emailとパスワードを入力させ、 その組み合わせが正しいかでチェックをします。 今回も同様にEmaiとパスワードを持つUserモデルを例にとります。
またユーザの登録時に、パスワードと確認用のパスワードを入力させ、 内容が一致すれば登録させる、というバリデーションをとります。
ユーザ登録機能の実装
Modelの実装
まずテーブルの作成ですが、mailとpassword_digestを用意します。 password_digestには、暗号化されたパスワードが登録されます。 password_digestはhas_secure_passwordに使用される名前なので、他の名前にはしないで下さい。 remember_tokenはログイン時に発行するトークンを保持し、cookieにも同様の値を保持させ、 ログインしているかどうかを判別するのに使用します。
create_table "users", force: :cascade do |t| t.string "name", limit: 191, null: false t.string "mail", limit: 191, null: false t.string "password_digest", limit: 191, null: false t.string "remember_token", limit: 191 t.datetime "created_at", null: false t.datetime "updated_at", null: false end
続いてモデルの作成です。
class User < ActiveRecord::Base has_secure_password validations: true validates :mail, presence: true, uniqueness: true end
has_secure_password
を宣言することで、password
, password_confirmation
をUserモデルのプロパティとして使用することができます(DB上での管理ではなくメモリ上で値を保持できるようになる)。
引数にオプションとしてvalidations: true
が与えられているが、trueを渡すことによって、
以下のバリデーションがUserモデルに追加されます。
- userの新規登録時にpasswordの必須入力
- passwordとpassword_confirmationの内容が合致すること
また、それとは別にメールアドレスを必須入力かつユニークになるようバリデーションを設定しています。
Viewの実装
ログインユーザの作成画面を作ります。
= form_for @user, url: users_path do |f| = form_error(@user) .form-group = form.label :name = form.text_field :name .form-group = form.label :mail = form.text_field :mail .form-group = form.label :password = form.text_field :password .form-group = form.label :password_confirmation = form.text_field :password_confirmation = f.submit '登録'
名前・メール・パスワード・パスワード確認の入力欄を設けます。
Controllerの実装
class UsersController < ApplicationController def new @user = User.new end def create @user = User.new(user_params) if @user.save redirect_to login_path else render 'new' end end private def user_params params.require(:user).permit(:name, :mail, :password, :password_confirmation) end end
入力されたパラメータを使用して登録をします。 この時、以下の検証が行われ、エラーがなければユーザが登録されます。
- アドレスが未入力の場合はエラー
- 入力されたアドレスが他のユーザに使用されている場合はエラー
- パスワードが未入力の場合はエラー
- パスワードとパスワード確認の内容が一致していない場合はエラー
入力されたパスワードは、password_digest
に暗号化されて登録されます。
ログイン機能の実装
ユーザ登録が済んだら、ログイン機能の作成を行います。
ルーティング設定
Rails.application.routes.draw do # ログイン / ログアウト get 'login', to: 'sessions#new' post 'login', to: 'sessions#create' delete 'logout', to: 'sessions#destroy' end
メール・パスワードを入力させるsessions#new
、
入力された情報を検証し、cookieにログイン情報を格納するsessions#create
、
ログアウトさせるsessions#destroy
をとります。
viewの作成
= form_for :session, url: login_path do |f| .form-group = f.text_field :mail .form-group = f.password_field :password = f.submit 'ログイン'
アドレスとパスワードを入力させます。
controllerの実装
class SessionsController < ApplicationController before_action :require_sign_in!, [:destroy] before_action :set_user, only: [:create] def new end def create if @user.authenticate(session_params[:password]) sign_in(@user) redirect_to root_path else flash.now[:danger] = t('.flash.invalid_password') render 'new' end end def destroy sign_out redirect_to login_path end private def set_user @user = User.find_by!(mail: session_params[:mail]) rescue flash.now[:danger] = t('.flash.invalid_mail') render action: 'new' end # 許可するパラメータ def session_params params.require(:session).permit(:mail, :password) end end
一つずつ解説します。
1.newでログイン画面を表示
これは特に何もしてないので飛ばします。
2.ログイン画面で入力された値をcreateアクションで検証
before_action :set_user, only: [:create] def create if @user.authenticate(session_params[:password]) sign_in(@user) redirect_to root_path else flash.now[:danger] = t('.flash.invalid_password') render 'new' end end
before_actionでメールアドレスからユーザの情報を取得し、
authenticate
メソッドでパスワードの検証を行っています。
authenticate
メソッドは、モデルでhas_secure_password
を宣言していると自動的に使用できるようになり、
入力されたパスワードを暗号化し、DBに登録されているpassword_digestと一致するか検証します。
検証が通れば、application_controllerで実装しているsign_in
メソッドを呼び出し、
remember_tokenを作成し、userモデルとcookieにセットし、ログイン後の画面に遷移します。
remember_tokenは、後々ログインしているかどうかの検証に使用します。
Userモデルにnew_remember_token
を実装しておきます。
これでログインは完了です。
def sign_in(user) remember_token = User.new_remember_token cookies.permanent[:user_remember_token] = remember_token user.update!(remember_token: User.encrypt(remember_token)) @current_user = user end
def self.new_remember_token SecureRandom.urlsafe_base64 end def encrypt(token) Digest::SHA256.hexdigest(token.to_s) end
3.ログアウトの処理
def destroy sign_out redirect_to login_path end
sign_out
でcookieの中身のremember_tokenを削除します。
def sign_out cookies.delete(:user_remember_token) end
ログインしていなかったらログイン画面に遷移させる。
ログインを実装していても、これをしていないと意味がありません。 ログアウトアクションは、ログインをしていないと使用できないと思いますのでこれを実装します。
skip_before_action :require_sign_in!, only: [:new, :create]
ログインしていなかったらログイン画面に遷移させるrequire_sign_in!
を実装します。
ログイン前のnew, createアクションでは実行させないようにしときましょう。
require_sign_in!
の中身はapplication_controllerに実装します。
長くなるので、最終的なapplication_controllerの中身を以下に記します。
class ApplicationController < ActionController::Base before_action :current_user before_action :require_sign_in! helper_method :signed_in? protect_from_forgery with: :exception def current_user remember_token = User.encrypt(cookies[:user_remember_token]) @current_user ||= User.find_by(remember_token: remember_token) end def sign_in(user) remember_token = User.new_remember_token cookies.permanent[:user_remember_token] = remember_token user.update!(remember_token: User.encrypt(remember_token)) @current_user = user end def sign_out @current_user = nil cookies.delete(:user_remember_token) end def signed_in? @current_user.present? end private def require_sign_in! redirect_to login_path unless signed_in? end end
ログインしているかを判定するsigned_in?
メソッドを別に切り出しておきます。
条件としては@current_user
がセットされているか。
これをするために、before_action
で@current_user
をセットするcurrent_user
メソッドを定義します。
def current_user remember_token = User.encrypt(cookies[:user_remember_token]) @current_user ||= User.find_by(remember_token: remember_token) end
cookieからトークンを取得後暗号化し、cookieと同じトークンを持ったuserを取得します。 取得できなかった場合はログインしていない、と判断します。
最後に
メソッドの切り出しはケースバイケースなので、参考程度に、と考えて下さい。 重要なのは以下の点だと思います。