Ashigaruコンピューター道

ソフトウェアの話とか、キャリアの話とか

Ruby on rails で動くCMS Refinerycmsをセットアップする


趣味で作っているキンドルセールまとめサイトにブログコンテンツを載せたくなったので、いろいろ調べてRuby on rails で動く Refinerycmsを使用することにした。そのセットアップのメモ

環境構築

サービスがrails 4.2.xにしか対応していないので、その環境構築。現在のメイン環境ruby2.3.1 rails 5.xと共存させたいのでruby 2.7.xを入れてそこにいろいろ入れる。



# ruby 2.2.7 のインストール
rbenv install 2.2.7
rbenv rehash
rbenv global 2.2.7

# 動作に必要なgem install
gem install bundle
gem install refinerycms

# railsのプロジェクト作成
refinerycms blog_test
cd blog_test

# このフォルダは 2.2.7で動かしたいのでversion指定
touch .ruby-version; echo "2.2.7" > .ruby-version

# 開発用サーバ動かす
bundle exec rails s -b 0.0.0.0


ここまでやるとアクセスできるようになる。

この後本番環境で、既存のrailsプロジェクトと共存させたりしたいのでいろいろ設定が必要そうだが、それはまた今度

Pros And Cons

  • rails 4.x系でしか動かない(2017/07/06)
    • ただしrails 5.x系への対応PRは出ている
  • ドキュメントが古い
  • スタイル適応の仕組みがないのでデザイン変えたい時、CSS頑張るしかない。
    • これ自分的には相当面倒なんだが、他の人はどうなんやろ?
  • Rails wayから外れていないので、Rails知っている人には非常にとっつきやすい
  • 未だにメンテされている(活発ではなさそう)

Ruby on Rails Active Recordのソースコードリーディング

調べた問題

ActiveRecordでCompanyモデルに紐づくUserという物があったときに、関連のあるUserを作成するときにbuildを使って以下のようにする。その際、まだDBには保存されていないUserが次の検索ででくるか気になったので実験しつつ、ActiveRecordのソースを調べてみた。


Company.users.build(name: 'xxxxx')


調査

railsソースコードリーディングについてここ参考にさせてもらった。AZS

上を参考にしつつ、railsのコードを落としてコードリーディング用のプロジェクトを作成した後、モデルを作成して、DBにテーブルを作る。

rails g model Company name:string
rails g model User company_id:integer name:string

bundle exec rails db:migrate RAILS_ENV=development
== 20170617020543 CreateUsers: migrating ======================================
-- create_table(:users)
-> 0.0027s
== 20170617020543 CreateUsers: migrated (0.0028s) =============================

== 20170617020644 CreateCompanies: migrating ==================================
-- create_table(:companies)
-> 0.0010s
== 20170617020644 CreateCompanies: migrated (0.0015s) =========================

そしてCompanyにCompanyとUserの関連を持たせるのと、コードを追うためのメソッドを追加。


class Company < ApplicationRecord

has_many :users

def self.build_users
binding.pry
com = Company.find_or_create_by(name: 'test_company')
com.users.build(name: 'test_user')
com.users.where(name: 'test_company')
end
end

実行して追いかけてみる。buildはcollection_proxy.rbのbuildメソッドが呼ばれているもよう。


From: /home/ishioka/repos/rails/activerecord/lib/active_record/associations/collection_proxy.rb @ line 316 ActiveRecord::Associations::CollectionProxy#build:

315: def build(attributes = {}, &block)
=> 316: @association.build(attributes, &block)
317: end

 @associationの実態は以下のようActiveRecord::Associations::HasManyAssociationというクラスのインスタンスでフィールドにCompanyとUserに関する情報をいろいろ持っている。


[1] pry(#<User::ActiveRecord_Associations_CollectionProxy>)> @association
User Load (0.2ms) SELECT "users". FROM "users" WHERE "users"."company_id" = ? [["company_id", 1]]
=> #<ActiveRecord::Associations::HasManyAssociation:0x007f752027be50
@association_scope=nil,
@inversed=false,
@loaded=false,
@owner=#<Company:0x007f753ec2a668 id: 1, name: "test_company", created_at: Sat, 17 Jun 2017 02:30:35 UTC +00:00, updated_at: Sat, 17 Jun 2017 02:30:35 UTC +00:00>,
@proxy=[],
@reflection=
#<ActiveRecord::Reflection::HasManyReflection:0x007f753f044988
@active_record=Company(id: integer, name: string, created_at: datetime, updated_at: datetime),
@active_record_primary_key="id",
@association_scope_cache=
{true=>
#<ActiveRecord::StatementCache:0x007f753efcb060
@bind_map=
#<ActiveRecord::StatementCache::BindMap:0x007f753efcb5d8
@bound_attributes=
[#<ActiveRecord::Relation::QueryAttribute:0x007f753efc5b10
@name="company_id",
@original_attribute=nil,
@type=#<ActiveModel::Type::Integer:0x007f753efa4a00 @limit=nil, @precision=nil, @range=-2147483648...2147483648, @scale=nil>,
@value=#<ActiveRecord::StatementCache::Substitute:0x007f753efc6880>,
@value_before_type_cast=#<ActiveRecord::StatementCache::Substitute:0x007f753efc6880>>],
@indexes=[0]>,
@query_builder=#<ActiveRecord::StatementCache::Query:0x007f753efcb088 @sql="SELECT "users".
FROM "users" WHERE "users"."company_id" = ?">>},
@automatic_inverse_of=false,
@class_name="User",
@constructable=true,
@foreign_key="company_id",
@foreign_type="users_type",
@klass=User(id: integer, company_id: integer, name: string, created_at: datetime, updated_at: datetime),
@name=:users,
@options={},
@plural_name="users",
@scope=nil,
@scope_lock=#<Thread::Mutex:0x007f753f044618>,
@type=nil>,
@stale_state=nil,
@target=[]>

Userのインスタンス化自体は、このパターンだとObjectのnewが使われていた。


From: /home/ishioka/repos/rails/activerecord/lib/active_record/inheritance.rb @ line 65 ActiveRecord::Inheritance::ClassMethods#new:

48: def new(args, &block)
49: if abstract_class? || self == Base
50: raise NotImplementedError, "#{self} is an abstract class and cannot be instantiated."
51: end
52:
53: attrs = args.first
54: if has_attribute?(inheritance_column)
55: subclass = subclass_from_attributes(attrs)
56:
57: if subclass.nil? && base_class == self
58: subclass = subclass_from_attributes(column_defaults)
59: end
60: end
61:
62: if subclass && subclass != self
63: subclass.new(
args, &block)
64: else
=> 65: super
66: end
67: end

[9] pry(User)> self.parent
=> Object


こので戻ってきたメソッドないでは、Userがインスタンス化されている。


From: /home/ishioka/repos/rails/activerecord/lib/active_record/associations/collection_association.rb @ line 281 ActiveRecord::Associations::CollectionAssociation#add_to_target:

277: def add_to_target(record, skip_callbacks = false, &block)
278: if association_scope.distinct_value
279: index = @target.index(record)
280: end
=> 281: replace_on_target(record, index, skip_callbacks, &block)
282: end

[11] pry(#<ActiveRecord::Associations::HasManyAssociation>)> record
=> #<User:0x007f753e614440 id: nil, company_id: 1, name: "test_user", created_at: nil, updated_at: nil>


before_addとかafter_addはここで呼ばれているのか、へー


From: /home/ishioka/repos/rails/activerecord/lib/active_record/associations/collection_association.rb @ line 441 ActiveRecord::Associations::CollectionAssociation#replace_on_target:

440: def replace_on_target(record, index, skip_callbacks)
=> 441: callback(:before_add, record) unless skip_callbacks
442:
443: set_inverse_instance(record)
444:
445: @was_loaded = true
446:
447: yield(record) if block_given?
448:
449: if index
450: target[index] = record
451: elsif @
was_loaded || !loaded?
452: target << record
453: end
454:
455: callback(:after_add, record) unless skip_callbacks
456:
457: record
458: ensure
459: @was_loaded = nil
460: end

targetというArrayにこのレコードを入れているが、最終的にこれがCompanyと関連をもって保存されるのかな?


From: /home/ishioka/repos/rails/activerecord/lib/active_record/associations/collection_association.rb @ line 452 ActiveRecord::Associations::CollectionAssociation#replace_on_target:

440: def replace_on_target(record, index, skip_callbacks)
441: callback(:before_add, record) unless skip_callbacks
442:
443: set_inverse_instance(record)
444:
445: @
was_loaded = true
446:
447: yield(record) if block_given?
448:
449: if index
450: target[index] = record
451: elsif @was_loaded || !loaded?
=> 452: target << record
453: end
454:
455: callback(:after_add, record) unless skip_callbacks
456:
457: record
458: ensure
459: @
was_loaded = nil
460: end

[17] pry(#<ActiveRecord::Associations::HasManyAssociation>)> target
=> []
[19] pry(#<ActiveRecord::Associations::HasManyAssociation>)> target.class
=> Array

この次はもう呼び出し元へ戻ってきて、usersの中身は入ってた。


From: /home/ishioka/repos/CodeReading/app/models/company.rb @ line 9 Company.build_users:

5: def self.build_users
6: binding.pry
7: com = Company.find_or_create_by(name: 'test_company')
8: com.users.build(name: 'test_user')
=> 9: com.users.where(name: 'test_company')
10: end

[24] pry(Company)> com.users
=> [#<User:0x007f753e614440 id: nil, company_id: 1, name: "test_user", created_at: nil, updated_at: nil>]

whereの実態はココらへん

From: /home/ishioka/repos/rails/activerecord/lib/active_record/relation/query_methods.rb @ line 600 ActiveRecord::QueryMethods#where:

599: def where(opts = :chain, rest)
=> 600: if :chain == opts
601: WhereChain.new(spawn)
602: elsif opts.blank?
603: self
604: else
605: spawn.where!(opts,
rest)
606: end
607: end



From: /home/ishioka/repos/rails/activerecord/lib/active_record/relation/query_methods.rb @ line 610 ActiveRecord::QueryMethods#where!:

609: def where!(opts, rest) # :nodoc:
=> 610: opts = sanitize_forbidden_attributes(opts)
611: references!(PredicateBuilder.references(opts)) if Hash === opts
612: self.where_clause += where_clause_factory.build(opts, rest)
613: self
614: end

その後 self.build_users へ戻ってきてしまったので、com.users.where(name: 'test_company')の段階では検索の条件などを組み立てるだけで、実際の値の検索はしていないようなので(遅延評価ってやつかな?)ちょっとコードを変えて再実行。

以下のように最後にwhereで組み立てたusersをカウントすることによって、どこの値をみてるかわかるはず。


class Company < ApplicationRecord

has_many :users

def self.build_users
binding.pry
com = Company.find_or_create_by(name: 'test_company')
com.users.build(name: 'test_user')
users = com.users.where(name: 'test_company')
_count_users = users.count
end
end
~



追ってくとこんなところがあって、SQLを組み立ててるのでDBに取りに行く模様


From: /home/ishioka/repos/rails/activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb @ line 34 ActiveRecord::ConnectionAdapters::DatabaseStatements#select_all:

31: def select_all(arel, name = nil, binds = [], preparable: nil)
32: arel, binds = binds_from_relation arel, binds
33: sql = to_sql(arel, binds)
=> 34: if !prepared_statements || (arel.is_a?(String) && preparable.nil?)
35: preparable = false
36: else
37: preparable = visitor.preparable
38: end
39: if prepared_statements && preparable
40: select_prepared(sql, name, binds)
41: else
42: select(sql, name, binds)
43: end
44: end

[2] pry(#<ActiveRecord::ConnectionAdapters::SQLite3Adapter>)> sql
=> "SELECT COUNT(
) FROM "users" WHERE "users"."company_id" = ? AND "users"."name" = ?"


結局SQLが発行されてDBの値が検索されてそれが検索される。


From: /home/ishioka/repos/CodeReading/app/models/company.rb @ line 10 Company.build_users:

5: def self.build_users
6: binding.pry
7: com = Company.find_or_create_by(name: 'test_company')
8: com.users.build(name: 'test_user')
9: users = com.users.where(name: 'test_company')
=> 10: _count_users = users.count
11: end

[1] pry(Company)> n
(0.8ms) SELECT COUNT(*) FROM "users" WHERE "users"."company_id" = ? AND "users"."name" = ? [["company_id", 1], ["name", "test_company"]]

From: /home/ishioka/.rbenv/versions/2.3.1/lib/ruby/gems/2.3.0/gems/pry-0.10.4/lib/pry/pry_instance.rb @ line 356 Pry#evaluate_ruby:

351: def evaluate_ruby(code)
352: inject_sticky_locals!
353: exec_hook :before_eval, code, self
354:
355: result = current_binding.eval(code, Pry.eval_path, Pry.current_line)
=> 356: set_last_result(result, code)
357: ensure
358: update_input_history(code)
359: exec_hook :after_eval, result, self
360: end

[1] pry(#<Pry>)> c
=> 0


結論

whereでは検索条件が作成されるだけであって、まだ検索はされない。
実際にその値が必要になった場合に、「DBから」検索されるので、紐付いているオブジェクトは検索結果には入ってこない。

最後に

ActiveRecordのコードを少し追っかけてみたが、結構複雑で呼び出し順や、各オブジェクトの関係などすぐには理解できなそう。
ただすごい参考になりそう(rubyの使い方、設計思想など)なので、issueでも拾ってコードリーディングしてみるかな。

Jupiter Notebook + Keras(Tensor Flow)でチュートリアルをしてみる4 MINIST

Kerasチュートリアルの第4弾、今回はConvolutional Neural Networkここも参考になった)を組んで精度を上げてみる。ここページを参考にしている。

まず必要なライブラリをインポートする。
いろいろ見た事ないものがあるので、調べてみると、
Flattenは2次元のデータを1次元のベクターにするのに使うらしい。
Conv2Dは2次元配列の畳み込み演算するのに使う。
MaxPooling2Dは、画像の圧縮をするのに使う。

import numpy
from keras.datasets import mnist
from keras.models import Sequential
from keras.layers import Dense
from keras.layers import Dropout
from keras.layers import Flatten
from keras.layers.convolutional import Conv2D
from keras.layers.convolutional import MaxPooling2D
from keras.utils import np_utils
from keras import backend as K
K.set_image_dim_ordering('th')


そしてお馴染みのランダムにシードの設定

# fix random seed for reproducibility
seed = 7
numpy.random.seed(seed)


第1層がConv2Dになるのだが、そこに読み込める形にデータを整形する。
KerasのConv2Dに入力するためには、[画像数][pixles(グレースケールは1)][width][height]の4次元配列にする必要があるのでshapeを使う。

# load data
(X_train, y_train), (X_test, y_test) = mnist.load_data()
# reshape to be [samples][pixels][width][height]
X_train = X_train.reshape(X_train.shape[0], 1, 28, 28).astype('float32')
X_test = X_test.reshape(X_test.shape[0], 1, 28, 28).astype('float32')



前回もやった0 - 255を0 - 1にNormalizeと教師データの整形を今回もやる。

# normalize inputs from 0-255 to 0-1
X_train = X_train / 255
X_test = X_test / 255
# one hot encode outputs
y_train = np_utils.to_categorical(y_train)
y_test = np_utils.to_categorical(y_test)
num_classes = y_test.shape[1]



モデルの定義をする。
1層目はConv2Dで32フィルター、フィルタのサイズ5 x 5、input_shapeは[1(グレースケール)][width][height]で指定。

2層目はMaxPooling2Dを使って画像を圧縮。slidesは指定がなければ、pool_sizeと同じになるようなので、2 x 2の領域が1つのPixcelにされるので14 x 14 の配列になるはず。

3層目はDropoutを使って20%のneuronsを伝播させない。これを入れるとover fittingを避けられるらしい。

4層目は2次元配列を1次元のベクターに変更

5層目は全結合のレイヤーで128 output

6層目は0 - 9の10のoutputでsoft max関数で確立に変換している。

# create model
model = Sequential()
model.add(Conv2D(32, (5, 5), input_shape=(1, 28, 28), activation='relu'))
model.add(MaxPooling2D(pool_size=(2, 2)))
model.add(Dropout(0.2))
model.add(Flatten())
model.add(Dense(128, activation='relu'))
model.add(Dense(num_classes, activation='softmax'))
# Compile model
model.compile(loss='categorical_crossentropy', optimizer='adam', metrics=['accuracy'])


そしてモデルの学習。前回と同じように200サンプルごとにパラメタのアップデートして、10回繰り返し。

# Fit the model
model.fit(X_train, y_train, validation_data=(X_test, y_test), epochs=10, batch_size=200, verbose=2)
# Final evaluation of the model
scores = model.evaluate(X_test, y_test, verbose=0)
print("Baseline Error: %.2f%%" % (100-scores[1]*100))



最終出力は以下。おお、エラー率が1%以下になっている。99%は判別できるという事。素晴らしい。


Train on 60000 samples, validate on 10000 samples
Epoch 1/10
172s - loss: 0.2310 - acc: 0.9347 - val_loss: 0.0825 - val_acc: 0.9743
Epoch 2/10
170s - loss: 0.0736 - acc: 0.9780 - val_loss: 0.0467 - val_acc: 0.9841
Epoch 3/10
175s - loss: 0.0531 - acc: 0.9839 - val_loss: 0.0432 - val_acc: 0.9856
Epoch 4/10
165s - loss: 0.0401 - acc: 0.9878 - val_loss: 0.0406 - val_acc: 0.9869
Epoch 5/10
179s - loss: 0.0337 - acc: 0.9893 - val_loss: 0.0346 - val_acc: 0.9886
Epoch 6/10
156s - loss: 0.0275 - acc: 0.9916 - val_loss: 0.0309 - val_acc: 0.9893
Epoch 7/10
163s - loss: 0.0232 - acc: 0.9927 - val_loss: 0.0359 - val_acc: 0.9877
Epoch 8/10
158s - loss: 0.0204 - acc: 0.9937 - val_loss: 0.0324 - val_acc: 0.9887
Epoch 9/10
164s - loss: 0.0166 - acc: 0.9947 - val_loss: 0.0300 - val_acc: 0.9901
Epoch 10/10
178s - loss: 0.0143 - acc: 0.9958 - val_loss: 0.0310 - val_acc: 0.9905
Baseline Error: 0.95%


ちなみに今回構築したモデルを可視化すると以下のようになっていた。







Jupiter Notebook + Keras(Tensor Flow)でチュートリアルをしてみる3 MINIST

Kerasチュートリアル第3弾。

機械学習のモデルを評価する時によく使われる、MINIST(手書き文字の認識をする)問題のコードを追っていく。
解説している記事をみながら、コードを追ってみる。

使う画像のサイズは28 x 28ピクセル
60000イメージはモデル構築のために、10000イメージはテストのために使う。

いいモデルはエラー率1%以下になるらしい。

では実際にコードを見ていく。

実際の文字イメージ画像群(データセット)をダウンロードする必要があるが、Kerasでは便利なスクリプトが用意されているらしい。(~/.keras/datasets/mnist.pkl.gz にダウンロードされる)

以下はダウンロードしてプロットするスクリプト

# Plot ad hoc mnist instances
from keras.datasets import mnist
import matplotlib.pyplot as plt
# load (downloaded if needed) the MNIST dataset
(X_train, y_train), (X_test, y_test) = mnist.load_data()
# plot 4 images as gray scale
plt.subplot(221)
plt.imshow(X_train[0], cmap=plt.get_cmap('gray'))
plt.subplot(222)
plt.imshow(X_train[1], cmap=plt.get_cmap('gray'))
plt.subplot(223)
plt.imshow(X_train[2], cmap=plt.get_cmap('gray'))
plt.subplot(224)
plt.imshow(X_train[3], cmap=plt.get_cmap('gray'))
# show the plot
plt.show()


ふむ、Jupiter Notebook上でも綺麗に表示された。

ではデータはダウンロードされたので、今回使うライブラリたちを読み込む。
Dropoutというのは、過学習を避けるためにランダムに伝播をさせずに、ネットワークをFatにさせないテクニックらしい。
詳しい論文

keras.unilsはその名の通り、kerasの便利クラス。モデルのシリアライズしたりするメソッドがある。https://keras.io/utils/


import numpy
from keras.datasets import mnist
from keras.models import Sequential
from keras.layers import Dense
from keras.layers import Dropout
from keras.utils import np_utils



前回と同じように、再現性を持たせるためにseedをセット。

# fix random seed for reproducibility
seed = 7
numpy.random.seed(seed)



でMINISTのデータを読み込む

# load data
(X_train, y_train), (X_test, y_test) = mnist.load_data()


読み込んだトレーニングセットは3次元の配列(白黒の場合)だが、モデルに食わせるためには、Vector(1次元の配列)にする必要があるらしい。28 x 28の画像だから784要素の配列する。

# flatten 2828 images to a 784 vector for each image
# print(type(X_train))
num_pixels = X_train.shape[1]
X_train.shape[2] # 28 * 28
X_train = X_train.reshape(X_train.shape[0], num_pixels).astype('float32') # 60000 , 784
# 784要素のArrayが60000個ある2次元配列になった。
# print(X_train.shape)
X_test = X_test.reshape(X_test.shape[0], num_pixels).astype('float32')


グレースケールは0 to 255で表しているが、これを255で割って0 to 1のスケールにする。

# normalize inputs from 0-255 to 0-1
X_train = X_train / 255
X_test = X_test / 255


教師データのラベルは0,1,2,3と正解の数値で表されているが、これを
0だったら [1,0,0,0,0,0,0,0,0,0]
5だったら [0,0,0,0,1,0,0,0,0,0]
と表すように変換する。

# one hot encode outputs
y_train = np_utils.to_categorical(y_train)
y_test = np_utils.to_categorical(y_test)
num_classes = y_test.shape[1]


ついにモデルの定義をする。
1層目は入力784、出力784
2層目は入力784、出力10、活性化関数にsoftmaxを使っている。ざっくり言うと出力を確率に変えてくれるらしい。

損失関数はcategorical_crossentropy = Logarithmic loss


# create model
model = Sequential()
model.add(Dense(num_pixels, input_dim=num_pixels,
kernel_initializer='normal', activation='relu'))
model.add(Dense(num_classes, kernel_initializer='normal', activation='softmax'))
# Compile model
model.compile(loss='categorical_crossentropy',
optimizer='adam', metrics=['accuracy'])


データを与えて、モデルを最適化する
200イメージごとにモデルのパラメータをアップデートして、20回繰り返す。

最後にテストデータでテストしたスコアをプリント!

# Fit the model
model.fit(X_train, y_train, validation_data=(X_test, y_test), epochs=20, batch_size=200, verbose=2)
# Final evaluation of the model
scores = model.evaluate(X_test, y_test, verbose=0)
print("Baseline Error: %.2f%%" % (100-scores[1]*100))




Baseline Error: 1.61%
が最終出力。つまり10000のイメージでテストしてみて、1.61%はこのモデルでの予想とはずれたが、98.4%は正解になったということ

こんなシンプルな2層のネットワークでも98.4%正解になると驚きだった。まあ人間がやれば99.9%ぐらい成功できるだろうけど。

ただこのチュートリアルはシンプル 編なようなので、次週もっと正解率を高めるモデル構築を見ていく。

たぶん使ってないDropoutを使うんだろうな。





Jupiter Notebook + Keras(Tensor Flow)でチュートリアルをしてみる2

Jupiter Notebook + Keras(Tensor Flow)でチュートリアルをしてみる1の続き。

前回は環境構築して、簡単なチュートリアルを試してみた。今回はそのコードをこまかく見ていく。

まず必要なライブラリ群のインポート

from keras.models import Sequential
from keras.layers import Dense
import numpy
from IPython.display import SVG
from keras.utils.vis_utils import model_to_dot

SVG, model_to_dotはモデルの可視化に使う。その他はKerasのプロジェクトではだいたいimportしないといけないはず。

次にランダムな数を生成した時に再現性を持たせるためにseedを設定。

# fix random seed for reproducibility
numpy.random.seed(7)

学習するデータを読み込みする。このチュートリアルでは糖尿病患者のデータをcsvから読み込み。X, Yの変数に読み込む、Xには全行の0から7の8列の2次元配列のデータ、Yには全行、8列目の1次元配列のデータが入る。
どうやらXには予想のためのデータ、Yにはその結果、0(陰性)、0(陽性)が入っているよう。

# load pima indians dataset
dataset = numpy.loadtxt("../tutorial/1/pima-indians-diabetes.csv", delimiter=",")
# split into input (X) and output (Y) variables
X = dataset[:,0:8]
Y = dataset[:,8]



次はモデルを定義していく。

KerasではSequentialというモデルを作り、そこにNWの層を追加していく形が基本らしい。
1層目は通常の全結合NWレイヤーで、入力が8 個、出力が12個の物を積んでる。 activationは活性化関数をどうするかというところ、ここではrelu(ランプ関数)が指定されている。現在は一般的にランプ関数を指定した方が精度がいいとされているらしい。

2層目は入力12個で、出力が8個

3層目は入力8個で、出力1個、activationにsigmoid(シグモイド関数)を使って出力を0 or 1 の整数にしている。


# create model
model = Sequential()
model.add(Dense(12, input_dim=8, activation='relu'))
model.add(Dense(8, activation='relu'))
model.add(Dense(1, activation='sigmoid'))

モデルの定義が終わったあと、モデルをコンパイルする。
引数は3つとり、optimizer(最適化手法)、loss(損失関数)、metrics(評価指標のリスト)が必要、詳しくはここ参照

最適化手法はadam
損失関数はbinary_crossentropy = logloss
metricsは一般的にはaccuracyを使うらしい。


# Compile model
model.compile(loss='binary_crossentropy', optimizer='adam', metrics=['accuracy'])


コンパイル後、実際に学習を始める。
XはテストデータのNumpy配列、Yは教師データのNumpy配列を与える。
epoches(反復回数)とbatch(何サンプルでgradient disent の更新をするか)も指定する。

model.fit(X, Y, epochs=50, batch_size=10)

最後にモデルの評価を行う。
XはテストデータのNumpy配列、Yは教師データのNumpy配列を与える。

evaluate後モデルの評価スコアが帰ってくる。

# evaluate the model
scores = model.evaluate(X, Y)
print("n%s: %.2f%%" % (model.metrics_names[1], scores[1]*100))



非常に簡単に使えるので、ぜんぜんTensorFlowより敷居が低そう。ただ機械学習ニューラルネットワークの基礎はしっている必要があるので、CourseraのMachine larning(とくに)week4 week5あたりをやっておくといいと思う。


Oauth 2.0 Mac Token

Oauth 2.0のMAC Tokenについて調べたのでまとめる


  • Message Authentification Codeの略
  • メッセージに署名をつけて、認証しようよということ
  • Bearer Tokenと MAC Tokenがある
  • Bearer Tokenの方がシンプルで見かけることが多い
  • Bearer か MACかはクライアントは決めれない。サーバが決める。
  • MAC Tokenでトークンが渡される時のレスポンス


HTTP/1.1 200 OK
Content-Type: application/json
Cache-Control: no-store
{
"access_token":"SlAV32hkKG",
"token_type":"mac",
"expires_in":3600,
"refresh_token":"8xLOxBtZp8",
"mac_key":"adijq39jdlaska9asud",
"mac_algorithm":"hmac-sha-256"
}

  • MAC Tokenではリソースへのアクセス時、以下情報をヘッダに付加する必要あり。

Authorization: MAC id="h480djs93hd8",  ts="1336363200",  nonce="dj83hs9s",  mac="bhCQXTVyfj5cmA9uKkPFx1zeOXM="

  • idはトークン取得時にもらった、Mac Key Identifireかaccess_token。
  • tsはクライアントで付け加えるタイムスタンプ 
  • nonceはクライアント側で生成し、付け加えるユニークな文字列、id, ts, nonceの組み合わせがユニークにならないといけない
  • macは以下方法で生成

ts + nonce + http method + uri + host + port + ext or Authorized(optional) の文字列を生成して、mac_algorithmで指定されているアルゴリズムでhashの生成したもの

参考

http://stackoverflow.com/questions/31209525/oauth-2-0-bearer-tokens-vs-mac-tokens-why-not-using-mac-tokens

https://dzone.com/articles/oauth-20-bearer-token-profile

https://tools.ietf.org/html/draft-hammer-oauth-v2-mac-token-05

https://www.slideshare.net/ritou/oauth-20-mac-authentication 日本語

Jupiter Notebook + Keras(Tensor Flow)でチュートリアルをしてみる1

Tensorflowを昔やってみたのだが、挫折したのでもっと直感的と言われているKerasを試してみる。

インストール

pyenv

Pyhonデータ分析ライブラリたちが入る
Anaconda https://docs.continuum.io/anaconda/install-macos
pyenv使ってるのでこちらを参考にした
http://qiita.com/takehilo/items/1ffe5b266ed6a64690c8

jun-mac:keras jun-ishioka$ pyenv install -v anaconda3-4.2.0
jun-mac:keras jun-ishioka$ pyenv global anaconda3-4.2.0
jun-mac:keras jun-ishioka$ python --version
Python 3.5.2 :: Anaconda 4.2.0 (x86_64) 
source /Users/jun-ishioka/.pyenv/versions/anaconda3-4.2.0/bin/activate 
Tensorflow, KerasはAnacondaのパッケージ管理出あるcondaでインストール

Google機械学習ライブラリ
Tensorflow  https://www.tensorflow.org/install/install_mac

conda install -c conda-forge tensorflow

Tensorflowのラッパー、より直感的に操作できる
Keras https://keras.io/#installation

conda install -c conda-forge keras

もろもろインストール後、Jupiter Notebookという対話しきのPytyon GUIを起動(これはAnacondaにデフォルトで含まれているみたい)

jupyter notebook

ブラウザが立ち上がりファイラーが表示される。
 適当なフォルダを作って、NewからPython [conda root]を選択。
 そうすると、ノートブックが出来上がる。

このセルの中にはマークダウンでドキュメントを書いたり、コードを書いて実行したりできる。

ここのチュートリアルをやってみた。
http://machinelearningmastery.com/tutorial-first-neural-network-python-keras/
実行したいときはCell -> Run Cells
 無事実行完了、Jupyter Notebook素敵すぎる。コードの管理とかかなり楽になりそう。

Kerasのコードの内容まで踏み込みたいのだが、環境構築に手こずってしまったので今日はここまで。