rails環境設定のメモ

ruby on railsの環境のお話

プロジェクト作成の時にフリーズする
betaというプロジェクトのプラグインも依存パッケージの検査のとこでかたまる

そもそもプラグインが導入できなくておわこん

railsのnewコマンドに対応してない

rubyに対応してない

ということでwindowsの無料IDEは全滅。

emacs環境設定のメモ

  • .emacsファイルにemacs lispで書きこむ
  • とは言っても.elファイルが用意されていることが多いので、パスを通してそれを呼び込むだけのことが多い
  • .emacsは、elispというフォルダとともにhome directoryにおく
  • elispフォルダにemacs設定ファイルを全部ぶち込んでおくと便利
  • emacsIDE化することができる(F2)
  • haskellにはhaskell-modeがある。
  • rubyにはruby-modeが用意されている。ただしバイナリインストーラには付属していないので、ソースコードを落として抜き取る。
  • ruby-modeの.elファイルでは削られたものもあるので、ロードエラーになることもある。

読んでるhaskellのページのメモ

  • 「やさしい Haskell 入門 (バージョン98)」(全然やさしくない)

http://www.sampou.org/haskell/tutorial-j/

http://www.sampou.org/haskell/a-a-monads/html/

http://d.hatena.ne.jp/kazu-yamamoto/20080208/1202456329

yahoo finance(US)から株価情報を自動取得する

金融工学入門の課題ためのコード

  • 銘柄をリストで与えると、営業日ベースの収益率(クローズ)をCSV形式で保存

rubyでyahoo financeのAPIを叩く(てかhtmlパースしてるだけかもだけど)ライブラリ見つけたからそれを使った。
http://www.transparentech.com/opensource/yahoofinance

>gem install yahoofinance

  • finance.rb

require "rubygems"
require "yahoofinance"
#require "fastercsv"


def get_bond_data(bond_set,from)
data = {}

bond_set.each do |meigara|
his = YahooFinance::get_historical_quotes( meigara,
Date.parse( from ),
Date.today() )

data[meigara] = Hash.new()
for i in 1..his.size-1
return_rate = 100*his[i][4].to_f/his[i-1][4].to_f-100
date = his[i][0]
data[meigara][date]=return_rate
end
end
p data
data
end

def to_csv(bond_data,file_name)
CSV.open(file_name,"w") do |writer|
dates = bond_data[bond_data.keys.first].keys.sort
meigaras = bond_data.keys.sort
writer << ["return_rates"]+meigaras
dates.each do |date|
return_rates = []
meigaras.each do |meigara|
if bond_data[meigara].key?(date)
return_rates << bond_data[meigara][date]
else
return_rates << "na"
end
end
writer << [date]+return_rates
end
end
end

  • main.rb

require "./finance.rb"

bond_set = ["YHOO","GOOG","EBAY","AAPL","AMZN","DTV","MSFT","ORLY","SNDK","ORCL"]
bond_data = get_bond_data(bond_set,'2005-09-09')
to_csv(bond_data,"fa.csv")

こんな感じで出力される(一部)
(例にある銘柄はすべてナスダック)

return_rates,AAPL,AMZN,DTV,EBAY,GOOG,MSFT,ORCL,ORLY,SNDK,YHOO
2005-09-09,-0.17509727626459437,-3.4831460674157313,-0.8119925046845822,-0.8217770929635435,-3.4383676632014044,-0.11273957158962844,-1.5567086730911797,-5.621003029283074,-1.3469693190321976,-1.327042170451179
2005-09-12,1.1412829594647746,1.2975187798770804,0.8186397984886753,1.7241379310344769,-0.6224332648870643,0.4909365558912384,-1.099706744868044,0.9857239972807577,-5.980300187617246,-1.1370262390670547

Naive Bayes実装してみた

Naive Bayesくらい作ったことないと、ということで実装してみた。

にあるように、最初にやるには不向き。理由は、

  • 解釈とモデル設計が難しい(その変わり理論学ぶモチベになる)
  • ゼロ頻度問題やらスムージングやらが面倒

class NaiveBayes < Classifier
include Math
attr_accessor :data_set, :times, :prior, :likelihood,:evidence,:posterior, :times_i,:sum,:times_c,:option
def initialize(*options)
options = options[0]
if options.is_a?(Hash)
if options.key?(:teaching_data)
@teaching_data= options[:teaching_data]
else
raise "error : teaching_data must be given"
end
if options.key?(:data_set)
@data_set = options[:data_set]
else
@data_set = Marshal::load(Marshal::dump(@teaching_data))
end
if options.key?(:option)
@option=options[:option]
else
@option="normal"
end
end

# calculating possibilities
case @option
when "normal"
normal_bayes
when "complement"
complement_bayes
else
raise "error: invalid option"
end
self
end

def normal_bayes
set_times!
set_words!
set_likelihood!
set_prior!
test
classify_all!(@data_set)
end

def complement_bayes
set_times!
set_words!
set_likelihood!
set_prior!
test
classify_all!(@data_set)
end

def test
puts "sum:#{@sum}"
puts "times_c:#{@times_c}"
puts "times_i:#{@times_i}"
puts "words:#{@words}"
["kbc","geil","programmer"].each do |category|
puts "lh;#{category}:#{@likelihood[category]}"
puts "times_c;#{category}:#{times_c[category]}"
end
end

# segging @times and @times_c and @times_i and @sum
def set_times!
@sum =0
@times=Hash.new()
@times_i = Hash.new()
@times_c = Hash.new()

@teaching_data.dictionary.each_key do |incident_name|
@times[incident_name]=Hash.new()
end
@teaching_data.data_set.each do |single_data|
single_data.features.each_key do |incident|
if @times[incident].key?(single_data.classification)
@times[incident][single_data.classification] += single_data.features[incident]
else
@times[incident][single_data.classification]=1
end
end
end
@times.each_key do |incident|
# init num of incident (1 not 0 not to set the possibility 0 )
@times_i[incident]=0
end

@teaching_data.class_dic.each_key do |category|
@times_c[category] = 0
end
@times.each_key do |incident|
@times[incident].each_key do |category|
# updating the total num of incident
@times_i[incident] += @times[incident][category] if @times[incident][category] >0
@times_c[category] += @times[incident][category] if @times[incident][category] >0
@sum += @times[incident][category]
end
end
end

def classify_all!(data_set)
data_set.data_set.each do |single_data|
classify!(single_data)
end
data_set
end

# classify single_data by the size of @posterior[obj][cat] (=P(cat|doc))
# befor the classification , set @posterior

def classify!(single_data)
case @option
when "normal"
normal_posterior!(single_data)
when "complement"
complement_posterior!(single_data)
end
result = nil
max = -1000000000000000
@posterior[single_data].each_key do |category|
if @posterior[single_data][category] > max
result = category
max = @posterior[single_data][category]
end
end
puts "class:#{result}"
single_data.classification = result
result
end

def complement_posterior!(single_data)
@posterior = Hash.new()
@posterior[single_data]=Hash.new()
@teaching_data.class_dic.each_key do |category|
likelihood = 0.0
single_data.features.each_key do |incident|
if @likelihood[category].key?(incident)
likelihood = likelihood + log(@likelihood[category][incident])*single_data.features[incident] #if single_data.features[incident]>0
else
likelihood = likelihood + log(c_laplace_smoothing(category))*single_data.features[incident]
end
end
@posterior[single_data][category]= log(@prior[category])-likelihood
puts "#{category}:#{@posterior[single_data][category]}"
end
end

def normal_posterior!(single_data)
@posterior = Hash.new()
@posterior[single_data]=Hash.new()
@teaching_data.class_dic.each_key do |category|
likelihood = 0.0
single_data.features.each_key do |incident|
if @likelihood[category].key?(incident)
likelihood = likelihood + log(@likelihood[category][incident])*single_data.features[incident] #if single_data.features[incident]>0
else
likelihood = likelihood + log(n_laplace_smoothing(category))*single_data.features[incident]
end
end
@posterior[single_data][category]= log(@prior[category])+likelihood
puts "#{category}:#{@posterior[single_data][category]}"
end
end

def set_words!
@words = @times_i.keys.size
end

def c_laplace_smoothing(category)
(@sum+1.0)/(@sum-@times_c[category]+@words)
#@times_i[incident]
end

def n_laplace_smoothing(category)
(1.0)/(@times_c[category]+@words)
#@times_i[incident]
end

# setting @likelihood[cat][inc] (= P(inc|cat) )

def set_likelihood!
case @option
when "normal"
n_set_likelihood!
when "complement"
c_set_likelihood!
end
end

def c_set_likelihood!
@likelihood = Hash.new()
@times.each_key do |incident|
@times[incident].each_key do |category|
@likelihood[category]=Hash.new()
end
end
@times.each_key do |incident|
@times[incident].each_key do |category|
# set P(inc|cat)= (incident in the category (= cat and inc) )/ times of cat occuered
@likelihood[category][incident] = ((@times_i[incident]-@times[incident][category]+1)*(1.0)/(@sum-@times_c[category]+@words))
end
end
end

def n_set_likelihood!
@likelihood = Hash.new()
@times.each_key do |incident|
@times[incident].each_key do |category|
@likelihood[category]=Hash.new()
end
end
@times.each_key do |incident|
@times[incident].each_key do |category|
# set P(inc|cat)= (incident in the category (= cat and inc) )/ times of cat occuered
@likelihood[category][incident] = ((@times[incident][category]+1)*(1.0)/(@times_c[category]+@words))
end
end
end

# setting @prior[cat](=P(cat))
def set_prior!
@prior = Hash.new()
# num of samples
sum = 0
@teaching_data.class_dic.each_key do |category|
sum += @teaching_data.class_dic[category]
end
@teaching_data.class_dic.each_key do |category|
# set P(cat) = num of the samples of the given cat / total num of samples
@prior[category]=(@teaching_data.class_dic[category]+1)*(1.0)/(sum+@teaching_data.class_dic.keys.size)
end
end

end

リバーシのAI

授業であつかっているリバーシのAI

1.リバーシのゲームの実装

  • Boardクラスは二次元Arrayでリバーシ盤を表現して、Stoneがそこにうまっている。
  • Stoneは:outside,:blankと白黒の4種類のステートをとる
  • reverse関数は
  • 関数は引数を破壊しないように設計してある(Marshal::load(Marshal::dump(hoge))を使う)

class Board 
attr_accessor :board,:next_turn,:steps, :value
def initialize
# creating the board
@board=Array.new()
for i in 0..9
@board[i]=Array.new(9,nil)
for j in 0..9
@board[i][j]=Stone.new(:blank)
end
end
# initialize the board state
@board[4][4]=Stone.new(:black)
@board[4][5]=Stone.new(:white)
@board[5][5]=Stone.new(:black)
@board[5][4]=Stone.new(:white)
# define the periferal field

for i in 0..9
@board[0][i]=Stone.new(:outside)
@board[i][0]=Stone.new(:outside)
@board[9][i]=Stone.new(:outside)
@board[i][9]=Stone.new(:outside)
end
@next_turn = :black
@steps = Array.new()
@steps[0] = "init"
end

def get(row,alph_column)
@board[row][alphabet_to_number(alph_column)]
end

def possible?(row,column,stone)
if reverse(row,column,stone)
true
else
false
end
end

def options
result = BoardSet.new()
for i in 1..8
for j in 1..8
new_board = reverse(i,j)
if new_board
result = result.add(new_board)
end
end
end
if result.null?
result = BoardSet.new()
@next_turn = opposite(@next_turn)
@steps << {:row=>nil,:column => nil,:stone => @next_turn}
result.add(self)
result
else
result
end
end

def opposite(turn)
case turn
when :black
:white
when :white
:black
end
end

def reverse(row,column)
if stone?(row,column)
return nil
end
result = Marshal::load(Marshal::dump(self))
stone = Stone.new(result.next_turn)
all_reversed = 0
# if not stone (:blank or :outside)
arrows = Array.new()
for i in -1..1
for j in -1..1
arrows<<[i,j]
end
end
arrows.delete([0,0])
i = 1
arrows.each do |arrow|
i = 1
step = 0
while @board[row+arrow[0]*i][column+arrow[1]*i].state == stone.reversed_state
# add to step_count
i += 1
step = i
if @board[row+arrow[0]*i][column+arrow[1]*i].state == :outside or @board[row+arrow[0]*i][column+arrow[1]*i].state == :blank
step = 0
end
end
all_reversed += step
## reverse the result board
unless step ==0
for k in 0..step
result.board[row+arrow[0]*k][column+arrow[1]*k]=Stone.new(stone.state)
end
end
end
# setting the turn
result.steps << {:row=>row,:column => column,:stone => @next_turn}
result.next_turn = opposite(result.next_turn)
if all_reversed != 0 #and next_turn==stone.state
result
else
nil
end

end

def show
for i in 0..9
unless i==0 or i==9
for j in 0..9
case @board[i][j].state
when :white
print "○"
when :black
print "●"
when :blank
print "・"
when :outside
print i
end
end
else
print "#abcdefgh#"
end
puts
end
puts "[Property]-------------"
puts "| next turn: #{@next_turn}"
puts "| last stone : #{@steps.last[:stone]}(#{@steps.last[:row]},#{number_to_alphabet(@steps.last[:column])})"
puts "| black : #{count[:black]}, white: #{count[:white]}"
puts "-----------------------"
puts "======================="
end

private
def alphabet_to_number(alphabet)
mapping = {"a"=>1,"b"=>2,"c"=>3,"d"=>4,"e"=>5,"f"=>6,"g"=>7,"h"=>8}
mapping[alphabet]
end

def number_to_alphabet(number)
mapping = [nil,"a","b","c","d","e","f","g","h"]
mapping[number]
end

def stone?(row,column)
if @board[row][column].state == :blank or @board[row][column].state == :outside
false
else # :black or :white
true
end
end

def count
result = {:white => 0,:black => 0, :blank => 0}
for i in 1..8
for j in 1..8
case @board[i][j].state
when :white
result[:white] +=1
when :black
result[:black] += 1
when :blank
result[:blank] += 1
end
end
end
result
end


def simple
count[@next_turn]-count[opposite(@next_turn)]
end

def param
array=Array.new(0,9)
for l in 0..9
array[l]=Array.new(0,9)
end
layer = [10,-10,5,0]
for i in 0..3
for j in (1+i)..(8-i)
for k in (1+i)..(8-i)
array[j][k] = layer[i]
end
end
end
array

end

def eval_func1
result = 0
for i in 1..8
for j in 1..8
case @board[i][j].state
when @next_turn
result += param[i][j]
when opposite(@next_turn)
result += param[i][j]*(-1)
end
end
end
result
end

end

class Stone
attr_reader :state
def initialize(state)
case state
when :white
@state = :white
when :black
@state = :black
when :blank
@state = :blank
when :outside
@state = :outside
else
raise "error: invalid color"
end
end

def stone?
case @state
when :white
true
when :black
true
when :outside
false
when :blank
false
end
end

def reversed_state
if @state == :white
:black
elsif @state == :black
:white
else
nil
end
end


end

class BoardSet < Array
def initialize()
super
end

def add(board)
result = Marshal::load(Marshal::dump(self))
if board.is_a?(Board)
result << board
elsif board.is_a?(BoardSet)
result = result + board
end
result
end

def null?
case size
when 0
true
else
false
end
end
end


2.リバーシを解くAIの実装

  • 方針
    • リバーシのゲーム木を3〜5回程度展開する
    • 深さ優先探索&ミニマックス(ネガマックス)法で探索
    • 効率化のためにalpha-beta cuttingを行う
    • 評価関数はボードに割り当てられた数*ストーンの有無の和とする

class Tree
attr_accessor :children,:parent, :data , :value
def initialize(hash)
if hash.is_a?(Hash)
# input data
if hash.key?(:data)
@data = hash[:data]
else
@data = nil
end
# input value
if hash.key?(:value)
@value = hash[:value]
else
@value = nil
end

end
if hash.is_a?(Hash)
# input root
if hash.key?(:parent)
@parent = hash[:parent]
else
@parent = :root
end
# input children
@children = Array.new()
@children << hash[:children] if hash.key?(:children)
end
end

def add!(node)
#copy_node = Marshall::load(Marshall::dump(node))
node.parent = self
@children << node
self
end

def evaluate!(function)
@value = @data.send(function)
@data.value = @value
@value
end

def evaluate_all!(function)
self.evaluate!(function)
unless children == []
children.each do |child|
child.evaluate_all!(function)
end
end
self
end
end

class GameTree < Tree
end

class ReversiTree < GameTree
attr_reader :depth, :function , :min, :max
def initialize(option)
super(option)
if option.is_a?(Hash)
if option.key?(:depth)
@depth = option[:depth]
else
@depth = 3
end
if option.key?(:function)
@function = option[:function]
else
raise "fucntion is undefined"
end
end
end

def best!
alphabeta!(self,@depth,-10000000000000,10000000000000)
@children.min_by{|child| child.value}.data
end

def show(level)
(level-1).times{print " | "}
print " +-"
printf("[%2d]\n",@value.to_s) unless @value == nil
printf("[%2s]\n","na") if @value == nil
if @children==[]
else
@children.each do |child|
child.show(level+1)
end
(level).times{print " | "}
print "\n"
end
end

def reverse(turn)
case turn
when :black
:white
when :white
:black
end
end
# open children
def open!
@data.options.each do |option|
@children << ReversiTree.new({:data=>option,:function =>@function})
end
self
end

def alphabeta!(node,depth,alpha,beta)
if depth == 0
node.evaluate!(@function)
#node.data.show
#puts "#{node.evaluate!(@function)}"
return node.value
else
node.open!
node.children.each do |child|
alpha = [alpha,(-1)*alphabeta!(child,depth-1,beta*(-1),alpha*(-1))].max
if alpha >= beta
node.value = alpha
return alpha
end
end
node.value = alpha
return alpha
end
end
end


3.ゲームで遊ぶ部分の実装

  • 多相性を使って基本クラスから継承させて3つのモードをつくればよかったと後悔

 class ReversiGame
attr_reader :boards, :mode, :turn,:cpu_func
def initialize
@boards = Array.new()
@boards << Board.new()
@cpu_func = Array.new()
mainloop
end

def mainloop
mode_select
turn_select

case @mode
when 1
cpu_select(1)
mode1
when 2
cpu_select(1)
cpu_select(2)
mode2
when 3
mode3
end

end

def cpu_select(num)
puts "cpu#{num} select"
case gets.chomp
when "1"
@cpu_func[num] = "eval_func1"
else
puts "error : invalid func"
cpu_select(num)
end
end

def cpu_choice(cpu_func)
cpu = ReversiTree.new({:function => cpu_func,:depth=>3,:data => @boards.last})
result = cpu.best!
result.show
result
end

def mode1
begin
case @boards.last.next_turn
when @turn
puts "<HUMAN>"
puts "cmd input"
cmd = gets.chomp
case cmd
when "back"
back!
back!
else
cmd = cmd.split(",")
p cmd
reverse!(cmd[0].to_i,cmd[1])
end
else
puts "<CPU>"
@boards << cpu_choice(@cpu_func[1])
end
rescue
STDERR.puts "Warning: #$!"
puts "invalid input"
end
mode1
end

def mode2
begin
case @boards.last.next_turn
when :black
puts "<CPU1>"
@boards << cpu_choice(@cpu_func[1])
else
puts "<CPU2>"
@boards << cpu_choice(@cpu_func[2])
end
rescue
STDERR.puts "Warning: #$!"
puts "invalid input"
end
mode2

end

def mode3
begin
case @boards.last.next_turn
when :black
puts "<HUMAN>"
puts "cmd input"
cmd = gets.chomp
case cmd
when "back"
back!
back!
else
cmd = cmd.split(",")
p cmd
reverse!(cmd[0].to_i,cmd[1])
end
else
puts "<HUMAN>"
puts "cmd input"
cmd = gets.chomp
case cmd
when "back"
back!
back!
else
cmd = cmd.split(",")
p cmd
reverse!(cmd[0].to_i,cmd[1])
end
end
rescue
STDERR.puts "Warning: #$!"
puts "invalid input"
end
mode3

end

def turn_select
if @mode == 1
puts "select your color"
case gets.chomp
when "black"
@turn = :black
puts "your color is black"
when "white"
@turn = :white
puts "your color is white"
else
"unecpected color"
turn_select
end
end
end

def mode_select
puts "select mode"
puts "1: Human VS CPU"
puts "2: CPU VS CPU"
puts "3: Human VS Human"
case gets.chomp
when "1"
@mode = 1
puts "Human VS CPU"
when "2"
@mode = 2
puts "CPU VS CPU"
when "3"
@mode = 3
pyts "Human VS Human"
else
puts "error: unexpected mode"
mode_select
end
end

def reverse!(row,column)
mapping = {"a"=>1,"b"=>2,"c"=>3,"d"=>4,"e"=>5,"f"=>6,"g"=>7,"h"=>8}
result = @boards.last.reverse(row,mapping[column])
if result
@boards << result
puts "reverse row:#{row}, column:#{mapping[column]}"
@boards.last.show
return true
else
return false
end
end

def back!
@boards.pop
puts "back"
@boards.last.show
end

end