じぶんメモ

プログラミングのメモ、日常のメモとか。

【ゼロから作る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テーブル
idID
titleタイトル
body本文
created_at作成日時
updated_at更新日時


  • eventsテーブル
idID
titleタイトル
body内容
created_at作成日時
updated_at更新日時


  • commentsテーブル
idID
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テーブル

idID
created_at作成日時
updated_at更新日時

chat_messagesテーブル

idID
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で使用できる。

導入

  • npmでも公式サイトでjsとcssを落とすでもOK。

  • 落としてきたら必要なcssとjsをインクルードしておく。

<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");
});

細かい使い方は公式を参照。

SweetAlert

モーダルのサンプル

なにかとモーダルが使われているサービスが多く、気になったので調べた。
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;
}

こちらの記事を参考にさせていただきました。

syncer.jp

Railsでgemなしでログイン機能を実装

はじめに

Railsでは便利なログイン機能を実装してくれるdeviseやsorceryといったgemが存在します。 導入するだけでsign_insign_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_outcookieの中身の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を取得します。 取得できなかった場合はログインしていない、と判断します。

最後に

メソッドの切り出しはケースバイケースなので、参考程度に、と考えて下さい。 重要なのは以下の点だと思います。

  • ログイン時はuserモデルにhas_secure_passwordを宣言することで使用できるauthenticate使って検証をかけること。
  • ログイン後はremember_tokenをcookieに、暗号化したremember_tokenをDBにセットしておいて、DBとcookieのremember_tokenが一致しているかでログインしているかを判定する。