最小二乗法による分析結果のグラフ表示コード

 今回は前回までのメソッドを使って分析した結果をグラフに表示する部分のコードを ruby で実装します。書籍のコードの下記の部分になります。

# Main
if __name__ == '__main__':
    train_set = create_dataset(N)
    test_set = create_dataset(N)
    df_ws = DataFrame()

    # 多項式近似の曲線を求めて表示
    fig = plt.figure()
    for c, m in enumerate(M):
        f, ws = resolve(train_set, m)
        df_ws = df_ws.append(Series(ws,name="M=%d" % m))

        subplot = fig.add_subplot(2,2,c+1)
        subplot.set_xlim(-0.05,1.05)
        subplot.set_ylim(-1.5,1.5)
        subplot.set_title("M=%d" % m)

        # トレーニングセットを表示
        subplot.scatter(train_set.x, train_set.y, marker='o', color='blue')

        # 真の曲線を表示
        linex = np.linspace(0,1,101)
        liney = np.sin(2*np.pi*linex)
        subplot.plot(linex, liney, color='green', linestyle='--')

        # 多項式近似の曲線を表示
        linex = np.linspace(0,1,101)
        liney = f(linex)
        label = "E(RMS)=%.2f" % rms_error(train_set, f)
        subplot.plot(linex, liney, color='red', label=label)
        subplot.legend(loc=1)

    # 係数の値を表示
    print "Table of the coefficients"
    print df_ws.transpose()
    fig.show()

    # トレーニングセットとテストセットでの誤差の変化を表示
    df = DataFrame(columns=['Training set','Test set'])
    for m in range(0,10):   # 多項式の次数
        f, ws = resolve(train_set, m)
        train_error = rms_error(train_set, f)
        test_error = rms_error(test_set, f)
        df = df.append(
                Series([train_error, test_error],
                    index=['Training set','Test set']),
                ignore_index=True)
    df.plot(title='RMS Error', style=['-','--'], grid=True, ylim=(0,0.9))
    plt.show()

 引用元のサンプルスクリプトの全体は下記で公開されています。

github.com

iruby環境の用意

 書籍では python コードの実行環境として ipython を使用していますが、その ruby 版にあたる iruby を使用します。

github.com

 書籍の例ではGUI上のターミナルから ipython を起動し、pythonのコードを実行すると別ウィンドウでグラフが表示されるのですが、 iruby では同様なことの実現方法がわからなかったので、 jupyter notebook 上で iruby を使用します。

jupyter.org

Vagrantfile

 Anaconda でインストールされる ipython のバージョンは4だったのですが、iruby が対応してるのは ipython3 のようだったので、 Vagrantfile に下記を追加し、ipython のバージョンを下げます。

config.vm.provision :shell, path: "provisionings/condas.sh"

provisionings/condas.sh

#!/bin/bash

conda install ipython=3.2.3 -y

provisionings/libs.sh

 jupyter notebook を起動してVM上のブラウザからアクセスするために、Chrome をインストールします。下記を追加して Chrome のリポジトリを追加し、 yum でインストールします。

cat <<'EOF' > /etc/yum.repos.d/google-chrome.repo
[google-chrome]
name=google-chrome
baseurl=http://dl.google.com/linux/chrome/rpm/stable/$basearch
enabled=1
gpgcheck=1
gpgkey=https://dl-ssl.google.com/linux/linux_signing_key.pub
EOF

yum install -y google-chrome-stable

provisionings/gems.sh

 python 版ではグラフのプロットのために matplotlib を使用していますが、 ruby 版では iruby 上で使用できる nyaplot を使います。

github.com

 下記を追記して、 nyaplot をインストールします。

gem install nyaplot

Nyaplot でのグラフ描画

 次数が 0, 1, 3, 9 の4種類の多項式のグラフを扱うので、まず複数の図を一まとめで扱うための Nyaplot::Frame のインスタンスを作成します。

fig = Nyaplot::Frame.new

 それぞれの図を作成するには、まず Nyaplot::Plot のインスタンスを生成し、そこに Nyaplot::DataFrame のオブジェクトを渡すことで作成できます。

Nyaplot::Plot#add_with_df
http://www.rubydoc.info/github/domitry/nyaplot/master/Nyaplot%2FPlot%3Aadd_with_df

 戻り値のオブジェクトに対して凡例のタイトルやグラフの色の指定をすることができます。

  plot = Nyaplot::Plot.new
  sc = plot.add_with_df(train_set.to_nyaplotdf, :scatter, :x, :y)
  sc.title("train_set")
  sc.color('blue')

 Nyaplot::Plot のインスタンスに配列データを渡すことでもグラフを作成できます。

Nyaplot::Plot#add
http://www.rubydoc.info/github/domitry/nyaplot/master/Nyaplot%2FPlot%3Aadd

 また、python版では numpy.linspace を使って、あるレンジ内で指定した数の数値を等間隔で取得していますが、 ruby では同様のメソッドが見つからなかったので、 step メソッドで代用しました。

  linex = (0..1).step(1.0 / (101 - 1)).to_a
  liney = linex.map do |x|
    Math.sin(2 * Math::PI * x)
  end
  line_answer = plot.add(:line, linex, liney)
  line_answer.title('answer')
  line_answer.color('green')

 図自体の大きさやメモリの範囲を指定するには、 plot.configure にブロックを渡して各値を設定します。

  plot.configure do
    x_label("M=#{m}")
    y_label('')
    xrange([-0.05, 1.05])
    yrange([-1.5, 1.5])
    legend(true)
    height(300)
    width(490)
  end

 最後に Nyaplot::Frame インスタンスに図のオブジェクトを追加します。

  fig.add(plot)

 Daru::DataFrame の内容をテキストで見やすい形に整形して表示するには、 Daru::DataFrame#inspect を使います。

Daru::DataFrame#inspect
http://www.rubydoc.info/gems/daru/0.1.0/Daru%2FDataFrame%3Ainspect

puts 'Table ot the coefficients'
puts Daru::DataFrame.new(df_ws).inspect

 出力は下記のようになります。

Table ot the coefficients
#<Daru::DataFrame(10x4)>
                   M=0        M=1        M=3        M=9
          0 0.05554492 0.80497528 -0.0362814 0.08278865
          1        nil -1.4988607 11.7919126 -24.851479
          2        nil        nil -34.392490 657.588025
          3        nil        nil 22.6689535 -5930.9070
          4        nil        nil        nil 29069.5535
          5        nil        nil        nil -84750.916
          6        nil        nil        nil 148809.822
          7        nil        nil        nil -153632.58
          8        nil        nil        nil 85727.3726
          9        nil        nil        nil -19925.297

 Nyaplot::Frame#show を実行することで図が描画されます。

fig.show

 また、Daru は Nyaplot でのプロットに対応しているので、一つの Daru::DataFrame オブジェクトの内容をプロットするだけであれば、 Daru::DataFrame#plot を使うことで描画可能です。

Daru::DataFrame#plot
http://www.rubydoc.info/gems/daru/0.1.0/Daru%2FPlotting%2FDataFrame%3Aplot

df.plot(type: [:line, :line], x: [:m, :m], y: ['Training set'.to_sym, 'Test set'.to_sym]) do |plot, diagrams|
  train_set_diagram = diagrams[0]
  test_set_diagram = diagrams[1]
  
  train_set_diagram.title('Training set')
  train_set_diagram.color('blue')
  test_set_diagram.title('Test set')
  test_set_diagram.color('green')
  
  plot.x_label('M')
  plot.y_label('RMS Error')
  plot.yrange([0, 0.9])
  plot.legend(true)
end

 ここまでの内容を踏まえたコードの全体を下記のように実装しました。

train_set = create_dataset(N)
test_set = create_dataset(N)
df_ws = {}

fig = Nyaplot::Frame.new

M.each_with_index do |m, c|
  f, ws = resolve(train_set, m)
  df_ws["M=#{m}"] = Daru::Vector.new(ws, name: "M=#{m}")
  
  plot = Nyaplot::Plot.new
  sc = plot.add_with_df(train_set.to_nyaplotdf, :scatter, :x, :y)
  sc.title("train_set")
  sc.color('blue')
  
  linex = (0..1).step(1.0 / (101 - 1)).to_a
  liney = linex.map do |x|
    Math.sin(2 * Math::PI * x)
  end
  line_answer = plot.add(:line, linex, liney)
  line_answer.title('answer')
  line_answer.color('green')
  
  liney = linex.map do |x|
    f.call(x)
  end
  line_erms = plot.add(:line, linex, liney)
  line_erms.title("E(RMS#{sprintf("%.2f", rms_error(train_set, f))})")
  line_erms.color('red')

  plot.configure do
    x_label("M=#{m}")
    y_label('')
    xrange([-0.05, 1.05])
    yrange([-1.5, 1.5])
    legend(true)
    height(300)
    width(490)
  end
  
  fig.add(plot)
end

puts 'Table ot the coefficients'
puts Daru::DataFrame.new(df_ws).inspect

fig.show

df = Daru::DataFrame.new({m: [], 'Training set': [], 'Test set': []})  
10.times do |m|
  f, ws = resolve(train_set, m)
  train_error = rms_error(train_set, f)
  test_error = rms_error(test_set, f)
  df.add_row(Daru::Vector.new([m, train_error, test_error], index: [:m, 'Training set'.to_sym, 'Test set'.to_sym]))
end

df.plot(type: [:line, :line], x: [:m, :m], y: ['Training set'.to_sym, 'Test set'.to_sym]) do |plot, diagrams|
  train_set_diagram = diagrams[0]
  test_set_diagram = diagrams[1]
  
  train_set_diagram.title('Training set')
  train_set_diagram.color('blue')
  test_set_diagram.title('Test set')
  test_set_diagram.color('green')
  
  plot.x_label('M')
  plot.y_label('RMS Error')
  plot.yrange([0, 0.9])
  plot.legend(true)
end

 Jupyter Notebook 上で実行すると下記のようなグラフが描画されます。

f:id:akanuma-hiroaki:20161205085255p:plain

f:id:akanuma-hiroaki:20161205085315p:plain

 Jupyter Notebook 上での実行結果は下記に公開しました。

http://nbviewer.jupyter.org/github/h-akanuma/ml4se_study/blob/master/02-square_error.ipynb

 また、実装の全体のコードやVagrantfile等も含めてgithubに公開してあります。

github.com

平方根平均二乗誤差の計算部分のコード

 今回も引き続き書籍のサンプルコードを ruby で実装します。今回は平方根平均二乗誤差を計算するメソッドです。サンプルコードのうちの下記部分になります。渡された多項式で平方根平均二乗誤差を計算しています。

# 平方根平均二乗誤差(Root mean square error)を計算
def rms_error(dataset, f):
    err = 0.0
    for index, line in dataset.iterrows():
        x, y = line.x, line.y
        err += 0.5 * (y - f(x))**2
    return np.sqrt(2 * err / len(dataset))

 引用元のサンプルスクリプトの全体は下記で公開されています。

github.com

行列データのイテレーション

 python では pandas.DataFrame.iterrows() でデータセットをイテレーションして処理しています。

http://pandas.pydata.org/pandas-docs/stable/generated/pandas.DataFrame.iterrows.html

    err = 0.0
    for index, line in dataset.iterrows():
        x, y = line.x, line.y
        err += 0.5 * (y - f(x))**2

 ruby では Daru::DataFrame#each_row_with_index が該当します。

http://www.rubydoc.info/gems/daru/0.1.0/Daru%2FDataFrame%3Aeach_row_with_index

 今回は index は使用されていないので、 Daru::DataFrame.each_row を使います。

http://www.rubydoc.info/gems/daru/0.1.0/Daru%2FDataFrame%3Aeach_row

  err = 0.0
  dataset.each_row do |line|
    err += 0.5 * (line.y - f.call(line.x))**2
  end

行列データの行数取得

 python では len() でデータセットの行数を取得できます。

>>> dataset          
          x         y
0  0.000000 -0.054637
1  0.111111  0.368772
2  0.222222  0.928976
3  0.333333  0.363668
4  0.444444  0.024165
5  0.555556 -0.521905
6  0.666667 -0.830954
7  0.777778 -0.464237
8  0.888889  0.058286
9  1.000000  0.183302
>>>                  
>>> len(dataset)     
10                   

 ruby では Daru::DataFrame#size で行数を取得できます。

http://www.rubydoc.info/gems/daru/0.1.0/Daru%2FDataFrame%3Asize

irb(main):018:0* dataset
=> #<Daru::DataFrame(10x2)>
                     x          y
          0        0.0 -0.5455317
          1 0.11111111 0.86331945
          2 0.22222222 1.00393856
          3 0.33333333 0.82127838
          4 0.44444444 0.49240164
          5 0.55555555 -0.0866971
          6 0.66666666 -0.2723084
          7 0.77777777 -0.9982033
          8 0.88888888 -0.3344831
          9        1.0 -0.2558949
irb(main):019:0> 
irb(main):020:0* dataset.size
=> 10

 普通の配列であれば #size でも #count でも基本的には同じ結果になりますが、 Daru::DataFrame のオブジェクトで #count を使うとそれぞれの列のnullではない値の行数を返してくれます。

http://www.rubydoc.info/gems/daru/0.1.0/Daru%2FMaths%2FStatistics%2FDataFrame%3Acount

irb(main):027:0* dataset.count
=> #<Daru::Vector(2)>
       count
     x    10
     y    10

平方根

 python では numpy.sqrt で平方根を求めています。

https://docs.scipy.org/doc/numpy/reference/generated/numpy.sqrt.html

>>> np.sqrt(2)    
1.4142135623730951
>>> np.sqrt(4)    
2.0               

 ruby では Math.sqrt で平方根を求めます。

https://docs.ruby-lang.org/ja/latest/method/Math/m/sqrt.html

irb(main):029:0* Math.sqrt(2)
=> 1.4142135623730951
irb(main):030:0> Math.sqrt(4)
=> 2.0

rms_error メソッドを ruby で

 ここまでの内容を踏まえて rms_error メソッドを ruby で実装します。

def rms_error(dataset, f)
  err = 0.0
  dataset.each_row do |line|
    err += 0.5 * (line.y - f.call(line.x))**2
  end

  Math.sqrt(2 * err / dataset.size)
end

 python版とruby版それぞれの実行結果は下記のようになりました。

>>> dataset                      
          x         y            
0  0.000000 -0.380472            
1  0.111111  0.719204            
2  0.222222  0.909939            
3  0.333333  1.022670            
4  0.444444  0.254537            
5  0.555556 -0.127610            
6  0.666667 -0.557395            
7  0.777778 -0.652528            
8  0.888889 -0.436643            
9  1.000000  0.450449            
>>>                              
>>> f, ws = resolve(train_set, 3)
>>>                              
>>> rms_error(train_set, f)      
0.23852710875750457              
irb(main):012:0* train_set
=> #<Daru::DataFrame(10x2)>
                     x          y
          0        0.0 -0.1806039
          1 0.11111111 1.22769157
          2 0.22222222 1.17466241
          3 0.33333333 0.67013919
          4 0.44444444 0.61218251
          5 0.55555555 -0.3105334
          6 0.66666666 -0.7770481
          7 0.77777777 -0.8269982
          8 0.88888888 -0.3653022
          9        1.0 -0.0443871
irb(main):013:0> 
irb(main):014:0* f, ws = resolve(train_set, 3)
=> [#<Proc:0x007fb60ddd3798@/vagrant/02-square_error.rb:45 (lambda)>, Vector[0.012228114152125085, 11.30192331783317, -32.97851812391701, 21.81003531172303]]
irb(main):015:0> 
irb(main):016:0* rms_error(train_set, f)
=> 0.20727664859912284

最小二乗法で解を求めるコード

 今回は前回に引き続き書籍のサンプルコードの下記部分を ruby で実装します。最小二乗法の公式を用いて係数を計算するメソッドです。 メソッドの戻り値として、決定された多項式と係数を返しています。

# 最小二乗法で解を求める
def resolve(dataset, m):
    t = dataset.y
    phi = DataFrame()
    for i in range(0,m+1):
        p = dataset.x**i
        p.name="x**%d" % i
        phi = pd.concat([phi,p], axis=1)
    tmp = np.linalg.inv(np.dot(phi.T, phi))
    ws = np.dot(np.dot(tmp, phi.T), t)

    def f(x):
        y = 0
        for i, w in enumerate(ws):
            y += w * (x ** i)
        return y

    return (f, ws)

 引用元のサンプルスクリプトの全体は下記で公開されています。

github.com

行列の結合

 python では pandas の concat メソッドで複数の pandas オブジェクトを結合します。

http://pandas.pydata.org/pandas-docs/stable/generated/pandas.concat.html

 オプションとして axis に 0 を指定すると縦方向の結合、1 を指定すると横方向の結合になります。 ここでは 1 を指定していますので、まず空の DataFrame を用意して、そこに dataset の x 列を i 乗した Series を順次横方向に追加してく形になります。

 ruby では空の Daru::DataFrame インスタンスは作成できないようでしたので、まず Hash に行列の内容を構成して、それを元に Daru::DataFrame のインスンタンスを作成しました。

  columns = {}
  (m+1).times do |i|
    columns["x**#{i}"] = dataset.x ** i
  end
  phi = Daru::DataFrame.new(columns)

転置行列

python では転置行列は pandas.DataFrame.T メソッドで取得することができます。

http://pandas.pydata.org/pandas-docs/stable/generated/pandas.DataFrame.T.html

ruby では Daru::DataFrame#transpose メソッドで取得することができます。

http://www.rubydoc.info/gems/daru/0.1.0/Daru%2FDataFrame%3Atranspose

irb(main):016:0* dataset = create_dataset(10)
=> #<Daru::DataFrame(10x2)>
                     x          y
          0        0.0 0.35893030
          1 0.11111111 0.75466334
          2 0.22222222 1.16130076
          3 0.33333333 0.76565346
          4 0.44444444 0.56397365
          5 0.55555555 -0.5168586
          6 0.66666666 -1.2497477
          7 0.77777777 -0.7234279
          8 0.88888888 -0.4937113
          9        1.0 -0.4791075
irb(main):017:0> 
irb(main):018:0* dataset.transpose
=> #<Daru::DataFrame(2x10)>
                     0          1          2          3          4          5          6          7          8          9
          x        0.0 0.11111111 0.22222222 0.33333333 0.44444444 0.55555555 0.66666666 0.77777777 0.88888888 1.0
          y 0.35893030 0.75466334 1.16130076 0.76565346 0.56397365 -0.5168586 -1.2497477 -0.7234279 -0.4937113 -0.479 075
irb(main):019:0> 

行列の積

 python では pandas.DataFrame.dot メソッドで行列の積を計算できます。

http://pandas.pydata.org/pandas-docs/stable/generated/pandas.DataFrame.dot.html

 ruby では Daru::DataFrame で * メソッドが定義されているので、* で積を計算することができるのですが、今回は元の行列と転置行列をかけているため、indexに数値と文字列が混ざることになり、下記のようなエラーになってしまいます。

irb(main):024:0* dataset
=> #<Daru::DataFrame(10x2)>
                     x          y
          0        0.0 -0.0742627
          1 0.11111111 0.64832694
          2 0.22222222 1.62979372
          3 0.33333333 1.16074147
          4 0.44444444 0.19131551
          5 0.55555555 0.09922296
          6 0.66666666 -0.6080503
          7 0.77777777 -0.9894763
          8 0.88888888 -0.4535080
          9        1.0 0.03518189
irb(main):025:0> 
irb(main):026:0* dataset * dataset
=> #<Daru::DataFrame(10x2)>
                     x          y
          0        0.0 0.00551495
          1 0.01234567 0.42032782
          2 0.04938271 2.65622758
          3 0.11111111 1.34732077
          4 0.19753086 0.03660162
          5 0.30864197 0.00984519
          6 0.44444444 0.36972517
          7 0.60493827 0.97906348
          8 0.79012345 0.20566954
          9        1.0 0.00123776
irb(main):027:0> 
irb(main):028:0* dataset * dataset.transpose
ArgumentError: comparison of Symbol with 0 failed
        from /usr/local/rbenv/versions/2.3.1/lib/ruby/gems/2.3.0/gems/daru-0.1.4.1/lib/daru/maths/arithmetic/dataframe.rb:62:in `sort'
        from /usr/local/rbenv/versions/2.3.1/lib/ruby/gems/2.3.0/gems/daru-0.1.4.1/lib/daru/maths/arithmetic/dataframe.rb:62:in `dataframe_binary_operation'
        from /usr/local/rbenv/versions/2.3.1/lib/ruby/gems/2.3.0/gems/daru-0.1.4.1/lib/daru/maths/arithmetic/dataframe.rb:55:in `binary_operation'
        from /usr/local/rbenv/versions/2.3.1/lib/ruby/gems/2.3.0/gems/daru-0.1.4.1/lib/daru/maths/arithmetic/dataframe.rb:18:in `*'
        from (irb):28
        from /usr/local/rbenv/versions/2.3.1/bin/irb:11:in `<main>'

 なので今回は Daru::DataFrame#to_matrix メソッドで標準の Matrix クラスに変換した上で * メソッドで計算します。

http://www.rubydoc.info/gems/daru/0.1.0/Daru%2FDataFrame%3Ato_matrix

irb(main):034:0* dataset.to_matrix * dataset.transpose.to_matrix
=> Matrix[[0.00551495178032287, -0.04814652292445256, -0.12103291720768458, -0.08619982081517798, -0.014207610754013434, -0.007368567440448755, 0.045155470293611104, 0.07348120775362572, 0.033678741797590464, -0.002612703024438115], [-0.04814652292445256, 0.4326735009138319, 1.0813305373090474, 0.7895770073355075, 0.1734177183698852, 0.12605731675017345, -0.3201413193247968, -0.555084433087857, -0.19525605324040052, 0.13392047936519433], [-0.12103291720768458, 1.0813305373090474, 2.705610300861487, 1.9658432484239334, 0.4105702573458222, 0.28516975670661376, -0.8428484217621567, -1.4398028683439108, -0.5415937066084376, 0.2795614486454444], [-0.08619982081517798, 0.7895770073355075, 1.9658432484239334, 1.4584318864404977, 0.3702160011396125, 0.3003573967170241, -0.4835669855859539, -0.8892670006157779, -0.23010930503481497, 0.37417041433074033], [-0.014207610754013434, 0.1734177183698852, 0.4105702573458222, 0.3702160011396125, 0.23413249035458505, 0.2658964729681377, 0.17996683941236247, 0.1563768318219559, 0.30829860276768317, 0.45117528617537195], [-0.007368567440448755, 0.12605731675017345, 0.28516975670661376, 0.3003573967170241, 0.2658964729681377, 0.31848717220014827, 0.3100378159950401, 0.33391998590584915, 0.4488287470660896, 0.5590464071902203], [0.045155470293611104, -0.3201413193247968, -0.8428484217621567, -0.4835669855859539, 0.17996683941236247, 0.3100378159950401, 0.8141696167863541, 1.120169924736844, 0.8683482991417188, 0.6452743066777754], [0.07348120775362572, -0.555084433087857, -1.4398028683439108, -0.8892670006157779, 0.1563768318219559, 0.33391998590584915, 1.120169924736844, 1.5840017535734776, 1.1400935207548575, 0.7429661273074588], [0.033678741797590464, -0.19525605324040052, -0.5415937066084376, -0.23010930503481497, 0.30829860276768317, 0.4488287470660896, 0.8683482991417188, 1.1400935207548575, 0.9957930064525073, 0.872933617825996], [-0.002612703024438115, 0.13392047936519433, 0.2795614486454444, 0.37417041433074033, 0.45117528617537195, 0.5590464071902203, 0.6452743066777754, 0.7429661273074588, 0.872933617825996, 1.001237765508352]]

逆行列

 python では pandas.linalg.inv メソッドで逆行列が取得できます。

https://docs.scipy.org/doc/numpy-1.10.0/reference/generated/numpy.linalg.inv.html

 ruby では Matrix#inv メソッドで取得できます。

irb(main):050:0* (dataset.to_matrix * dataset.transpose.to_matrix).inv                                                
=> Matrix[[6.438056238931283e+17, -3.5959222998233816e+16, 4.88081712482937e+16, -4.18039023242074e+16, -4.90936881765618e+16, 1.9473195412556384e+16, 3.021796172248485e+16, -1.1756838226941134e+17, 1.104440295083465e+17, -8.790885266451936e+15], [7.894377025631253e+15, 4.130512217329567e+15, 1.226948454741468e+15, -1.080925575443484e+15, 9.51855025409944e+15, 1576790734528046.5, -1355771888949234.5, 1.191314652268012e+16, -1.3688955666893698e+16, -1.671726231048433e+15], [-4.530896280313147e+15, 6.832627236988067e+15, -256467997105680.2, -2.854089097619272e+15, -361754002852689.25, 1442321524185798.5, -293843073250848.9, 2.291785009164425e+15, -2164437097239707.2, -53995045129483.5], [6.56400708662395e+15, -8.278847572597992e+15, 2945428805276741.5, 611874690847290.5, -8.040224399925562e+15, 4.146425733346407e+15, 1.0063070988405478e+16, -1.099361383748988e+16, 7.012887589668771e+15, -3.060566106986782e+15], [2.0276047297689546e+17, -2.150234284194681e+16, 2.325837285911574e+15, 1.3855500557957406e+16, -9.66616800705062e+15, -1.4971038030737476e+16, -6.859023781680976e+15, -2.61127868104491e+16, 3.872186122638793e+16, 330309925585268.0], [1.1925716211143941e+17, -1.6248336393828732e+16, 6.71882570034233e+15, 7.539028212975366e+15, -2.6810813298632516e+16, 1.540682966685851e+16, 8.331481526823681e+15, -2.4692863560056996e+16, 3.42380291696946e+16, -1.5626708042627156e+16], [-6.604745687626929e+15, 727565952289667.5, 1.286427881109916e+15, 1508601318395916.8, -1.6562628335161202e+16, -4.61937542936672e+15, 2.284703494536934e+15, -3.360855015175375e+15, 7.170630317185643e+15, 3774876268811336.5], [6.0918212393723944e+16, -3.266090027295123e+15, 1.229518586531585e+15, 4197189849440108.5, -2.435086220896028e+15, 4.660830138590578e+15, 5.09467236899953e+15, -3.794140272523739e+15, 1.59348998039823e+15, -4.678366072552667e+15], [-1.1382587909489373e+17, 9.677645630477756e+15, -2.030349802911037e+15, -7.640675796532893e+15, 1.8680150397993788e+16, 7.114574957016326e+15, 1.658128693269082e+15, 1.4272195974858228e+16, -1.875905159959477e+16, -5.863347355441972e+15], [-1.00227143117868e+17, 1.2818655421880554e+16, -5.836686165645619e+15, -7.27446600919549e+15, 1.7224948871781866e+16, -1.0652985027149534e+16, -1.1678113837058996e+16, 1.966068157926607e+16, -2.591158029231125e+16, 1.6086644321288758e+16]]

resolveメソッドをrubyで

ここまでの内容を踏まえてサンプルコードの resolve メソッドを ruby で実装します。

def resolve(dataset, m)
  t = dataset.y

  columns = {}
  (m+1).times do |i|
    columns["x**#{i}"] = dataset.x ** i
  end
  phi = Daru::DataFrame.new(columns)

  tmp = (phi.transpose.to_matrix * phi.to_matrix).inv
  ws = (tmp * phi.transpose.to_matrix) * Vector.elements(t.to_a)

  f = lambda {|x|
    y = 0
    ws.each_with_index do |w, i|
      y = y + w * (x ** i)
    end

    y
  }

  return f, ws
end

 下記については、 (tmp * phi.transpose.to_matrix) の結果は標準の Matrix クラスになり、t は Daru::Vector になるのですが、 標準の Matrix と Daru::Vector はそのまま内積が計算できないので、 Daru::Vector を標準の Vector クラスに変換した上で内積を計算しています。

  tmp = (phi.transpose.to_matrix * phi.to_matrix).inv
  ws = (tmp * phi.transpose.to_matrix) * Vector.elements(t.to_a)

 また、python版ではメソッド内でさらに def でメソッドを定義して return でメソッドを返している部分を、 ruby では lambda を返すように実装しています。

 python版とruby版それぞれの実行結果は下記のようになりました。

>>> train_set                                                  
          x         y                                          
0  0.000000 -0.333941                                          
1  0.111111  1.425262                                          
2  0.222222  0.853840                                          
3  0.333333  0.982145                                          
4  0.444444  0.244465                                          
5  0.555556 -0.136371                                          
6  0.666667 -0.961705                                          
7  0.777778 -1.462275                                          
8  0.888889 -0.668861                                          
9  1.000000 -0.216649                                          
>>>                                                            
>>> f, ws = resolve(train_set, 3)                               
>>>                                                            
>>> type(f)                                                    
<type 'function'>                                              
>>>                                                                                                        
>>> ws                                                         
array([ -0.12912272,  13.31977786, -38.77768783,  25.50897351])
irb(main):072:0* train_set
=> #<Daru::DataFrame(10x2)>
                     x          y
          0        0.0 -0.3822801
          1 0.11111111 0.74486105
          2 0.22222222 0.83353384
          3 0.33333333 0.53626611
          4 0.44444444 0.31220508
          5 0.55555555 -0.6527569
          6 0.66666666 -1.2765675
          7 0.77777777 -0.9600806
          8 0.88888888 -0.7323687
          9        1.0 -0.5115570
irb(main):073:0> 
irb(main):074:0* f, ws = resolve(train_set, 3)
=> [#<Proc:0x007f496bd59c90@/vagrant/02-square_error.rb:42 (lambda)>, Vector[-0.2636530068380138, 10.784779039058368, 
-31.470536541107172, 20.64727993759569]]
irb(main):075:0> 
irb(main):076:0* f.class
=> Proc
irb(main):077:0> 
irb(main):078:0* ws
=> Vector[-0.2636530068380138, 10.784779039058368, -31.470536541107172, 20.64727993759569]

誤差関数(最小二乗法)による回帰分析サンプルのデータセット作成コード

 とりあえず前回で ruby と python のコードを動かす環境を作ったので、サンプルコードを ruby に書き換えていきます。まずは誤差関数(最小二乗法)による回帰分析のサンプルコード。書籍のコードは下記に公開されています。

github.com

 この中で、まず今回はデータセットを作成するためのコードをrubyで書いてみます。上記サンプルコードの中の下記の部分です。

import numpy as np
from pandas import Series, DataFrame

from numpy.random import normal

# データセット {x_n,y_n} (n=1...N) を用意
def create_dataset(num):
    dataset = DataFrame(columns=['x','y'])
    for i in range(num):
        x = float(i)/float(num-1)
        y = np.sin(2*np.pi*x) + normal(scale=0.3)
        dataset = dataset.append(Series([x,y], index=['x','y']),
                                 ignore_index=True)
    return dataset

 ここでは下記のライブラリが使われています。

  • NumPy:ベクトルや行列を扱う数値計算ライブラリ
  • pandas:Rに類似のデータフレームを提供するライブラリ

 こういったライブラリが充実しているのが、pythonが機械学習系に強い理由の一つですね。 これらのライブラリの中で、サンプルコードの中で使われている内容を、rubyで書くにはどうするのが良いか調べてみました。

pandas.DataFrame

http://pandas.pydata.org/pandas-docs/stable/generated/pandas.DataFrame.html#pandas.DataFrame

 pythonのDataFrameクラスはスプレッドシートのようなデータ構造を持っており、サンプルの中では下記のように使われています。

>>> from pandas import DataFrame
>>> 
>>> df = DataFrame(columns = ['x', 'y'])
>>> df
Empty DataFrame
Columns: [x, y]
Index: []
>>> 

 DataFrameのコンストラクタに渡している columns パラメータの意味は下記のように説明されています。データカラムのラベルとして使われます。

columns : Index or array-like Column labels to use for resulting frame. Will default to np.arange(n) if no column labels are provided

 rubyで pandas にあたるライブラリとして、daruというgemがあるようなのでこれを利用し、Daru::DataFrameクラスを使用します。

Daru::DataFrame
http://www.rubydoc.info/gems/daru/0.1.4.1/Daru/DataFrame

irb(main):001:0> require 'daru'

Install the reportbuilder gem version ~>1.4 for using reportbuilder functions.

Install the spreadsheet gem version ~>1.1.1 for using spreadsheet functions.
=> true
irb(main):002:0> df = Daru::DataFrame.new({'x': [], 'y': []})
=> #<Daru::DataFrame(0x2)>
   x   y
irb(main):003:0> 

pandas.Series

http://pandas.pydata.org/pandas-docs/stable/generated/pandas.Series.html#pandas.Series

 Seriesは一次元配列に似た構造を持っており、サンプルコードでは下記のような使われ方をしています。

>>> from pandas import Series
>>>                                       
>>> x = 0.0                               
>>> y = 1.0                               
>>> s = Series([x, y], index = ['x', 'y'])
>>> s                                     
x    0.0                                  
y    1.0                                  
dtype: float64                            
>>>                                       

 rubyではDataFrameと同じくdaruのVectorクラスを使用します。

Daru::Vector http://www.rubydoc.info/gems/daru/0.1.4.1/Daru/Vector

irb(main):005:0* x = 0.0
=> 0.0
irb(main):006:0> y = 1.0
=> 1.0
irb(main):007:0> v = Daru::Vector.new([x, y], index: [:x, :y])
=> #<Daru::Vector(2)>
   x 0.0
   y 1.0
irb(main):008:0> 

pandas.DataFrame.append

http://pandas.pydata.org/pandas-docs/stable/generated/pandas.DataFrame.append.html#pandas.DataFrame.append

 サンプルコードでは空のDataFrameに下記のようにappendメソッドでSeriesのインスタンスを追加しています。

>>> dataset = DataFrame(columns = ['x', 'y'])
>>> s = Series([x, y], index = ['x', 'y'])   
>>> dataset.append(s, ignore_index = True) 
     x    y                                
0  0.0  1.0                                
>>>                                        

 rubyではDataFrameクラスのadd_rowメソッドでVectorインスタンスを追加します。

Daru::DataFrame.add_row
http://www.rubydoc.info/gems/daru/0.1.4.1/Daru%2FDataFrame%3Aadd_row

irb(main):026:0* dataset = Daru::DataFrame.new({x: [], y: []})
=> #<Daru::DataFrame(0x2)>
   x   y
irb(main):027:0> v = Daru::Vector.new([x, y], index: [:x, :y])
=> #<Daru::Vector(2)>
   x 0.0
   y 1.0
irb(main):028:0> dataset.add_row(v)
=> #<Daru::Vector(2)>
   x 0.0
   y 1.0
irb(main):029:0> dataset
=> #<Daru::DataFrame(1x2)>
       x   y
   0 0.0 1.0
irb(main):030:0> 

numpy.pi

 pythonではpi(円周率)はnumpyに定数として用意されています。

nullege.com

>>> np.pi        
3.141592653589793
>>>              

 rubyではMathモジュールの定数として用意されています。

Math::PI
https://docs.ruby-lang.org/ja/latest/class/Math.html#C_-P-I

irb(main):002:0* Math::PI
=> 3.141592653589793
irb(main):003:0> 

numpy.sin

 pythonでは三角関数のsinを求めるメソッドはnumpyに用意されています。

numpy.sin
https://docs.scipy.org/doc/numpy-1.9.1/reference/generated/numpy.sin.html

>>> np.sin(2 * np.pi)  
-2.4492935982947064e-16
>>>                    

 rubyではMathモジュールに用意されています。

Math.sin
https://docs.ruby-lang.org/ja/latest/class/Math.html#M_SIN

irb(main):004:0* Math.sin(2 * Math::PI)
=> -2.4492935982947064e-16
irb(main):005:0> 

正規分布

 pythonでは正規分布を求めるメソッドがnumpyに用意されています。

numpy.random.normal
https://docs.scipy.org/doc/numpy/reference/generated/numpy.random.normal.html

>>> np.random.normal(scale = 0.3)
0.0948107519352898               
>>>                              

 rubyでは調べてみた限りでは正規分布を求めるメソッドは用意されていないようなので、下記サイトを参考にボックス=ミュラー法で正規分布を求めるメソッドを定義します。

乱数発生の手法

irb(main):363:0* def normal_rand(mu = 0, sigma = 1.0)
irb(main):364:1>   random = Random.new
irb(main):365:1>   (Math.sqrt(-2 * Math.log(random.rand)) * Math.sin(2 * Math::PI * random.rand) * sigma) + mu
irb(main):366:1> end
=> :normal_rand
irb(main):367:0> 
irb(main):368:0* normal_rand(0, 0.3)
=> -0.5960439895226316
irb(main):369:0> 

 下記のようなgemもあるようですが、あまり使われている感じではなさそうかなと。

bitbucket.org

create_datasetメソッドをrubyで

 ここまでの内容を使ってサンプルコードのcreate_datasetメソッドをrubyで実装します。

require 'daru'

def normal_rand(mu = 0, sigma = 1.0)
  random = Random.new
  (Math.sqrt(-2 * Math.log(random.rand)) * Math.sin(2 * Math::PI * random.rand) * sigma) + mu
end


def create_dataset(num)
  dataset = Daru::DataFrame.new({'x': [], 'y': []})  

  num.times do |i|
    x = i.to_f / (num - 1).to_f
    y = Math.sin(2 * Math::PI * x) + normal_rand(0, 0.3)
    dataset.add_row(Daru::Vector.new([x, y], index: [:x, :y]))
  end
  
  return dataset
end

 元のサンプルコードとrubyで実装したメソッドのそれぞれの実行結果は下記のようになりました。

>>> create_dataset(10) 
          x         y  
0  0.000000 -0.470924  
1  0.111111  0.417868  
2  0.222222  0.420972  
3  0.333333  0.494881  
4  0.444444  0.654892  
5  0.555556 -0.737098  
6  0.666667 -0.605475  
7  0.777778 -1.809355  
8  0.888889 -1.211044  
9  1.000000 -0.114083  
>>>                    
irb(main):382:0* create_dataset(10)
=> #<Daru::DataFrame(10x2)>
                     x          y
          0        0.0 -0.3909174
          1 0.11111111 0.78616375
          2 0.22222222 1.11320947
          3 0.33333333 0.97217041
          4 0.44444444 0.21530738
          5 0.55555555 -0.8419577
          6 0.66666666 -0.8907612
          7 0.77777777 -1.2237687
          8 0.88888888 -1.0662619
          9        1.0 0.26103827
irb(main):383:0> 

 とりあえずちゃんと動いているようですが、他の部分の実装を進める上で不都合があった場合は都度修正していきたいと思います。

2016/11/15 追記: rubyのnormal_randへの引数の渡し方が間違っていたので修正しました。

Vagrant + rbenv + pyenv で機械学習の勉強用環境構築

 機械学習の勉強をしようと下記書籍を読み始めました。機械学習といえばやはり言語はPythonなのですが、普段Rubyをメインで使っている自分としては、Rubyで同様のことができないかなと思い、書籍のサンプルコード実行の為の環境に加えてRubyの実行環境も用意し、Pythonのサンプルコードの内容をRubyに置き換えていくことに挑戦してみたいと思います。結果として機械学習はやっぱりPythonだね、ということになる可能性は大いにありますが。。

ITエンジニアのための機械学習理論入門
https://www.amazon.co.jp/IT-ebook/dp/B016Q22IX2/

 ということでまず今回は環境作成です。環境はVagrantで作っておいた方があとで何かと便利なので、VagrantでCentOSのVMを用意し、そこにrbenvとpyenvをインストールしてRubyとPythonの実行環境を用意します。CentOSを使うのは、上記書籍での例でCentOSが使われているためです。

Vagrantfile

# Vagrantfile API/syntax version. Don't touch unless you know what you're doing!
VAGRANTFILE_API_VERSION = "2"

Vagrant.configure(VAGRANTFILE_API_VERSION) do |config|

  # Every Vagrant virtual environment requires a box to build off of.
  config.vm.box = "esss/centos-7.1-desktop"

  # Create a private network, which allows host-only access to the machine
  # using a specific IP.
  config.vm.network :private_network, ip: "192.168.59.104"

  config.vm.provider "virtualbox" do |vb|
    vb.customize ["modifyvm", :id, "--memory", "2048"]
  end

  config.vm.provision :shell, path: "provisionings/libs.sh"
  config.vm.provision :shell, path: "provisionings/rbenv.sh"
  config.vm.provision :shell, path: "provisionings/pyenv.sh"
  config.vm.provision :shell, path: "provisionings/ruby2.3.1.sh"
  config.vm.provision :shell, path: "provisionings/anaconda2-4.1.1.sh"
  config.vm.provision :shell, path: "provisionings/env.sh"
  config.vm.provision :shell, path: "provisionings/os_env.sh"
end

 サンプルコードの実行結果としてグラフを表示するためにGUI環境が必要なので、Vagrantのboxには esss/centos-7.1-desktop を使用します。

Vagrant box esss/centos-7.1-desktop | Atlas by HashiCorp

 必要なライブラリやrbenv, pyenv等のインストール用の設定は provisionings ディレクトリを作成してその下にまとめ、 プロビジョニングで実行されるようにします。ファイルを細分化するのは、あとで一部だけ変更して再実行したいときに、そのファイルだけ run オプションで "always" を指定すれば、 vagrant reload 時に再実行できるようにするためです。例えば provisionings/env.sh の内容を変更して再実行したいときは、下記のような記述に変更して vagrant reload します。

config.vm.provision :shell, path: "provisionings/env.sh", run: "always"

 Rubyの違うバージョンを追加でインストールしたいときは、provisionings/ruby2.0.0.sh のようにファイルを用意して、下記のような記述を追加して vagrant reload すればインストールされます。

config.vm.provision :shell, path: "provisionings/ruby2.0.0.sh", run: "always"

 一度実行した後は run: "always" を削除しておけば、次回以降は実行されません。また、各プロビジョニングファイルの内容はデフォルトで root アカウントとして実行されるので、 sudo は不要です。(参考:下記ドキュメントページの privileged についての説明を参照)

www.vagrantup.com

各プロビジョニングファイル

provisionings/libs.sh

#!/bin/bash

yum -y update
yum install -y git
yum install -y openssl-devel readline-devel zlib-devel

 rbenvとpyenvのインストールに必要なgit等をインストールしておきます。

provisionings/rbenv.sh

#!/bin/bash

RBENV_ROOT=/usr/local/rbenv

git clone https://github.com/sstephenson/rbenv.git ${RBENV_ROOT}
git clone https://github.com/sstephenson/ruby-build.git ${RBENV_ROOT}/plugins/ruby-build

echo "export RBENV_ROOT=${RBENV_ROOT}" >> /etc/profile.d/rbenv.sh
echo 'export PATH="${RBENV_ROOT}/bin:$PATH"' >> /etc/profile.d/rbenv.sh
echo 'eval "$(rbenv init -)"' >> /etc/profile.d/rbenv.sh
source /etc/profile.d/rbenv.sh
rbenv --version

${RBENV_ROOT}/plugins/ruby-build/install.sh

 システムワイドで rbenv が使えるように /usr/local/rbenv にインストールします。また、ログイン時にパスが通るように /etc/profile.d/rbenv.sh として設定ファイルを追加します。ちなみに rbenv の環境構築についてググっていくつか記事を見てみましたが、 ruby-build の install.sh の実行について書かれていない記事が多かった気がしました。私の環境ではこれを実行しないと ruby-build が使えるようにならなかったのですが、環境によるものなんですかね?

provisionings/pyenv.sh

#!/bin/bash

PYENV_ROOT=/usr/local/pyenv

git clone https://github.com/yyuu/pyenv.git ${PYENV_ROOT}

echo "export PYENV_ROOT=${PYENV_ROOT}" >> /etc/profile.d/pyenv.sh
echo 'export PATH="${PYENV_ROOT}/bin:$PATH"' >> /etc/profile.d/pyenv.sh
echo 'eval "$(pyenv init -)"' >> /etc/profile.d/pyenv.sh
source /etc/profile.d/pyenv.sh

 pyenv も rbenv と同様にインストールします。pyenv では rbenv の ruby-build にあたるものは不要のようです。

provisionings/ruby2.3.1.sh

#!/bin/bash

rbenv install 2.3.1

 ruby2.3.1をインストールします。古い rbenv だと ruby をインストールした後に rehash が必要でしたが、最近のバージョンでは rehash は不要になっています。

github.com

provisionings/anaconda2-4.1.1.sh

#!/bin/bash

pyenv install anaconda2-4.1.1
pyenv rehash

 Pythonの実行環境を anaconda を使って用意します。書籍の実行環境のセットアップの説明では、必要なツールやライブラリが一括でセットアップされるように Enthought Canopy を使用する方法が紹介されていますが、 anaconda によるインストールでも機械学習関連のライブラリが一通り一括でインストールされるため、こちらを使用しています。また、書籍のサンプルではpython2.7系を使っているので、anacondaの2系を使用します。書籍のサンプル実行において必要なライブラリは下記の通りです。

  • NumPy
  • SciPy
  • matplotlib
  • pandas
  • PIL
  • scikit-learn
  • IPython

provisionings/env.sh

#!/bin/bash

rbenv global 2.3.1
pyenv global anaconda2-4.1.1

 rbenvとpyenvで使用するrubyとpythonのバージョンを指定します。特にディレクトリでの切り分けも現状は必要ないので、globalで同じバージョンを使用します。

provisionings/os_env.sh

#!/bin/bash

echo 'vagrant' | passwd --stdin vagrant

 Vagrantで作成したVMのユーザとパスワードはいずれもvagrantだと思っていたのですが、GUIでそのパスワードだとログインできなかったため、ここで vagrant ユーザのパスワードを設定し直しています。

起動後の確認

 vagrant up 後の ruby と python のバージョンを確認してみます。

$ vagrant ssh
Last login: Sun Nov  6 06:25:32 2016
[vagrant@localhost ~]$ 
[vagrant@localhost ~]$ ruby -v
ruby 2.3.1p112 (2016-04-26 revision 54768) [x86_64-linux]
[vagrant@localhost ~]$ 
[vagrant@localhost ~]$ python -V
Python 2.7.12 :: Anaconda 4.1.1 (64-bit)

 想定通りのバージョンが使われるようになっています。また、GUI環境でログインし、IPythonからサンプルスクリプトを実行したところ、正しく実行されグラフが表示されました。

f:id:akanuma-hiroaki:20161106153533p:plain

vagrantディレクトリのマウントエラーの対処

 ここまでの手順を終えた後に vagrant reload もしくは、 vagrant halt / vagrant up した場合に、下記のようなエラーが出て vagrant ディレクトリのマウントに失敗することがあります。

Failed to mount folders in Linux guest. This is usually because
the "vboxsf" file system is not available. Please verify that
the guest additions are properly installed in the guest and
can work properly. The command attempted was:

mount -t vboxsf -o uid=`id -u vagrant`,gid=`getent group vagrant | cut -d: -f3` vagrant /vagrant
mount -t vboxsf -o uid=`id -u vagrant`,gid=`id -g vagrant` vagrant /vagrant

The error output from the last command was:

/sbin/mount.vboxsf: mounting failed with the error: No such device

 ホストOSとゲストOS間のディレクトリ共有機能は Vagrant の Guest Additions によって提供されていますが、 yum -y update することで kernel が更新された場合に、古いバージョンの Kernel でビルドされた Guest Additions が動作しなくなるためにエラーになるようです。VMは起動していますので、 vagrant ssh でログインした後に下記コマンドを実行して Guest Additions を再インストールしてから vagrant reload することで解消されます。

$ sudo /etc/rc.d/init.d/vboxadd setup
Removing existing VirtualBox non-DKMS kernel modules       [  OK  ]
Building the VirtualBox Guest Additions kernel modules
Building the main Guest Additions module                   [  OK  ]
Building the shared folder support module                  [  OK  ]
Building the OpenGL support module                         [  OK  ]
Doing non-kernel setup of the Guest Additions              [  OK  ]
Starting the VirtualBox Guest Additions                    [  OK  ]

 Guest Additions の再インストールを自動化してくれる、 vagrant-vbguest というプラグインがあるようなのですが、私の環境ではこのプラグインをインストールしようとするとエラーになってしまいました。原因を追求して解決したいところではありますが、本来の目的とは違うので、ひとまず手動での対応ができれば良いということにしておきます。

 ちなみに書き終わってから知ったのですが、 anyenv という、rbenv や pyenv など複数の **env をまとめて管理できるものがあるようなので、いずれはそちらに乗り換えてみたいと思います。

からあげ Beer Bash を開催(社内イベント)

 先日社内イベントとして、開発部で「からあげ Beer Bash」を開催しました。イベント名は、唐揚げとビール片手に交流しましょう、ということでつけました。今まであまり開発部内でこういったイベントはできていなかったのですが、下記のような意図から今回やってみることにしました。

意図・目的

  • リアルなコミュニケーション機会の増加
  • ディスカッション機会の増加
  • プレゼンの練習の場

 弊社では開発部は自宅勤務を取り入れていることもあり、全員が一堂に会することがあまり多くありません。普段のコミュニケーションはSlackを中心に行っていて、業務に支障はないのですが、やはりリアルなコミュニケーションに勝るものではありません。また、何かディスカッションするにしても、やはり直接話す方がやりやすいものです。なので、気軽に対面でのコミュニケーションをする機会を定期的に持つことで、部内での風通しをさらに良くしていきたいと思っています。

 それともう一つの側面として、各メンバーにプレゼンの練習の場を提供したいということもありました。開発部メンバーといえども技術的なスキルだけではなく、アウトプットのスキルも磨いて、外部の勉強会等でも発表の機会があれば積極的に発表して欲しいと思っています。アウトプットすることによって技術的なスキルも確かなものになっていきますし、個人のキャリア形成、ブランディングにもつながります。また、それが結果的に会社にとってもプラスになります。

事前準備

 今回は業務外の時間ということで、任意で参加者を募りました。発表者もやりたい人がやるという感じで、事前にConfluenceにページを作って、やりたい人は名前と内容を書いておくという形にしました。

 飲み物・食べ物は、からあげ Beer Bash ということで、基本的に唐揚げとビールだけです。色々手配しようと思うと準備が大変になるので。その分、量は多くということで、10人の参加者で唐揚げ100個を手配しました。ビールは以前オフィスで懇親会をやったときの余りがあったのでそれを消費+チューハイを少々買い足した感じです。

f:id:akanuma-hiroaki:20161025084202j:plain:w300,left 唐揚げ 10個 x 10パック。オフィス近くの なか卯 に事前に予約しておきました。

からあげ Beer Bash 当日

 当日は業務終了後に執務スペース内のミーティングスペースのテーブルを移動して、プロジェクタを設置して会場を準備しました。そしてさらっと乾杯した後にとりあえず私から今回のイベントのイントロダクションを兼ねてLTして、その後は発表予定のメンバーで、やりたい人から発表していくという形。特に制限時間等も設けずにゆるい感じでやってみました。

 こういう形でそれぞれのメンバーに発表してもらうというのは初めてだったのでどうなるかなーと思っていたのですが、皆熱心に発表してくれて、聞いているメンバーからの質問もあり、思ったより良い感じのイベントになったのではないかと思っています。

 発表内容についても特にテーマを限定していなかったので、業務に直結するものではなくても、お互いにどんなことに興味を持っていて、どんなことを考えているのかが見えたのが良かったと思います。普段は基本的には業務に関係ない話をする時間はあまり取れないのと、飲み会やランチのときに話すにしても技術的な内容などしっかり話すのは難しいので、良い機会になったのではないかと思います。

f:id:akanuma-hiroaki:20161026082301j:plain

f:id:akanuma-hiroaki:20161025091646j:plain

 一通り発表が終わったところで一旦締めて、後は各自時間の許す限り懇親会という形で、みんな終電近くまで話していました。

良かったこと

 重複するところもありますが、改めて今回開催して良かったことをまとめてみます。

  • 業務と関係ないところでそれぞれどんなことに興味があるかとか、どんなことをやっているかがわかった
  • プレゼンで自分の考えを発表する練習の場になった
  • それぞれの発表に対して質問も複数あり、一方的に話すだけという感じにならなかった
  • 飲み物や食べ物にあまり力を入れ過ぎなかったことで、準備の手間をあまりかけない形で実施できた
  • 唐揚げ100個というインパクト
  • 残さず食べ切れた

改善したいこと

 逆に今後改善した方が良さそうなところもあるのでまとめてみます。

  • 一人あたりの発表の一応の制限時間を設けた方が良さそう。完全自由だと、毎回うまく収まるとは限らないので。
  • 懇親会の方で各発表に対してのフィードバックがもっとあっても良い。発表者のスキル向上のために。
  • リモートメンバーの参加のハードルを下げたい。
  • プロジェクタの性能があまり良くなく、部屋を暗くしないと良く見えないので、明るい中でも良く見える環境を用意したい。(部屋が暗いと雰囲気も暗くなりがちな気がする)

 また、今回は業務とは直結しない内容についての発表&ディスカッションで交流することを意図していたのですが、業務上解決すべき課題についてのディスカッションの時間も十分に確保できてはいないので、そちらもどのようにディスカッションしていくのがいいか、考えていきたいと思っています。

 ともあれ今回はそれなりにうまくいったと思うので、ひとまず月一回ペースで開催してきたいと思っています。唐揚げは増量かな?

システム思考セミナー

 先日、システム思考のセミナーに参加してきました。

learningvesper.doorkeeper.jp

 イベントやセミナー情報のメルマガでたまたま今回のセミナーを見かけて、課題の深掘りをできるようにしていくというところに興味を持ちました。

システム思考とは

 システム思考がどんなものかは、今回のセミナーの告知ページに下記のように説明されています。

ビジネス課題など対象の物事に影響を与える構造を見極め、
その要素間の因果関係をグラフとして表し、
その構造を利用して振舞の特徴把握や定性的な分析を行う考え方。

 つまりは対象の領域の構造を図解して、現状の課題を視覚的に捉えることによって、そこから特徴や関連を見つけやすくするということかと思います。

 また、根本原因を分析するためのツールではなく、現状を正しく把握するためのツールということで、使いどころを間違えないように注意が必要のことでした。

ワークショップ

 セミナーでは最初にシステム思考についての説明が少しあった後、実際にシステム思考で課題を表してみるというワークショップが実施されました。入門編ということもあり、下記参考書籍の中から2問ほど、状況の説明が与えられてそれを元に課題を図式するという問題に取り組み、図式したものを近くの人と見せ合うということをやりました。

www.amazon.co.jp

やってみた感想

  • 与えられた状況についてどこから図(グラフ)にしていくのかのとっかかりを考えるのに少し苦労しました。一度どこから取り掛かるかを決めてしまえれば、それなりに芋づる式でグラフを広げていくことができます。

  • グラフの各要素を事実(ファクト)にするのか、取り得るアクションにするのかで悩みました。また、それぞれの要素の粒度についても、どれぐらいが最適なのかというのは判断が難しいところでした。

  • ひとまず最初は細かいところは気にせず、また、全体の構成のようなところも一度置いておいて、思いつくままにグラフを書いていき、書いたもの全体を眺めた上でどことどこが関連しているかを考えるのが良さそうに思えました。

  • 原因や対策の検討につなげていくためには事実だけでなく、関連や原因を推測してそれもグラフに落としていくことが必要なので、推測の部分についても様々な角度から考える必要があります。

 図式するというのは現状を正しく把握するということに有効な手段だと改めて感じました。現状の課題を正しく把握することが、有効な解決策につながりますので、最初に見えていることだけでなく、色々な角度から物事を捉えていくのが大切ですね。

 セミナーで紹介されたシステム思考のツールについても、下記で詳しく説明されているので、あとで改めて読んでみようと思います。

www.change-agent.jp

 また、同僚から下記の書籍も紹介してもらいましたので、読んでみたいと思います。

www.amazon.co.jp