scipyのlinkage関数の結果について

前回の記事の続きです。
参考:scipyで階層的クラスタリング

前回の記事で階層的クラスタリングを実行し可視化するところまで紹介しましたが、
今回は一歩戻ってlinkage関数の戻り値の中身を見てみます。
とりあえず、 linkage matrix をprintして結果を見てみましょう。


from sklearn.datasets import load_iris
from scipy.cluster.hierarchy import linkage
X = load_iris().data[::10, 2:4]
print(X.shape)  # (15, 2)
# ユークリッド距離とウォード法を使用してクラスタリング
z = linkage(X, metric='euclidean', method='ward')
print(z.shape)  # (14, 4)
print(z)
# 以下出力
[[ 2.          3.          0.1         2.        ]
 [ 0.          1.          0.1         2.        ]
 [12.         14.          0.14142136  2.        ]
 [ 4.         16.          0.2081666   3.        ]
 [ 6.          8.          0.31622777  2.        ]
 [ 5.          9.          0.36055513  2.        ]
 [ 7.         11.          0.36055513  2.        ]
 [15.         18.          0.39072582  5.        ]
 [10.         17.          0.43969687  3.        ]
 [13.         23.          0.73598007  4.        ]
 [20.         21.          1.0198039   4.        ]
 [19.         25.          2.00831604  6.        ]
 [24.         26.          3.72312593 10.        ]
 [22.         27.          9.80221064 15.        ]]

前回の記事で可視化したのと同じデータなので、以降の説明は前回の記事中の図と見比べながら読むとわかりやすいと思います。
結果のlinkage matrixは、z.shape の値から分かる通り、14行4列の行列の形をしています。
で、この14という値は、元のデータの個数15個から1減らした値です。
階層的クラスタリングのプロセスの中で、1個ずつグルーピングして集約し、もともと15個あったグループを1つにまとめるのでこうなってます。

そして、列ですが、pythonのインデックスいうと 0列目と1列目はあたらに同じグループに含まれるデータのインデックス、
2列目はそれらの要素orクラスタ間の距離、3列めはそこで新たに作られたクラスタに含まれれるデータの個数を意味します。

具体的には、次のデータは、X[2]とX[3]の距離が0.1で、この二つをまとめて要素が2個のクラスタを作ったことを意味します。
[ 2. 3. 0.1 2. ]
そして、明示はされていませんが、その新しいクラスタには、インデックス15が振られます。(元のデータが0~14の15個なので。)

同様に、次のデータで0と1がまとめられてインデックス16のクラスタが作られます。
[ 0. 1. 0.1 2. ]
で、このインデックス16のクラスタは次のデータで4番目の要素とグルーピングされて、要素数3個のクラスタになります。
[ 4. 16. 0.2081666 3. ]
前回の記事のデンドログラムで確かに0と1でできたクラスタに4が合流しているのが描かれていますね。

このようにして、 linkage matrix の中身を直接読むことができます。

scipyで階層的クラスタリング

今回紹介するのは階層型クラスタリングをscipyで実施する方法です。

階層型クラスタリングの各種アルゴリズム自体は、まだエンジニアを本職にしてたころに知り、その時はこれは面白い手法だと感心していたのですが、
いざデータサイエンティストに転職してからはあまり使ってきませんでした。
クラスタリングする時はk-meansなど、クラスタ数を指定する手法を使うことが多いし、
どれとどれが近いとか言った分析は距離行列眺めたり、次元削減してプロットしたりすることが多かったので。
ただ、他の職種のメンバーに説明するときの樹形図(デンドログラム)のわかりやすさを活用したくなり最近使い始めています

さて、本題に戻ります。

階層型クラスタリングを雑に説明すると、一旦個々のデータを全部別々のクラスタに分類し、
その中から近いものを順番に一つのクラスタにまとめるという操作を繰り返し、最終的に全データを1個のクラスタにまとめる方法です。
この操作を途中で打ち切ることで、任意の個数のクラスタにここのデータを分類することができます。
この際、個々の要素の距離をどう定義するのか、またクラスタと要素、クラスタとクラスタの距離をどのように定義するかによって、
手法が複数存在し、結果も変わります。

この階層型クラスタリングを行う関数が、scipyに用意されています。
Hierarchical clustering (scipy.cluster.hierarchy)

非常に多くの関数がありますが使うのは次の3つです。

scipy.cluster.hierarchy.linkage ・・・ 主役。これが階層型クラスタリングの実装。
scipy.cluster.hierarchy.fcluster ・・・ 各要素について、クラスタリングの結果どのクラスタに属するのかを取得する。
scipy.cluster.hierarchy.dendrogram ・・・ 樹形図(デンドログラム)を描く。

ここまで読んであまりイメージがつかめないと思うので、とりあえずやってみましょう。
データは何でもいいのですが、いつものirisでやります。
(そんなに多くの件数も必要ないので、1/10の件数のデータに絞って使い、4つある特徴量のうち2個だけ使います)


# データ取得。 (15件だけ取得し、特徴量も petal lengthとpetal width に絞る
from sklearn.datasets import load_iris
import matplotlib.pyplot as plt
X = load_iris().data[::10, 2:4]

# データを可視化。
fig = plt.figure(figsize=(6, 6))
ax = fig.add_subplot(1, 1, 1, title="iris (sample)")
plt.scatter(X[:, 0], X[:, 1])
for i, element in enumerate(X):
    plt.text(element[0]+0.02, element[1]+0.02, i)
plt.show()

出力がこちら。これをクラスタリングしていきます。
散布図には番号を振っておきましたが、この番号が結果に出てくる樹形図(デンドログラム)内の番号に対応します。

つぎに階層型クラスタリングを実行して可視化します。


# 階層型クラスタリングに使用する関数インポート
from scipy.cluster.hierarchy import linkage
from scipy.cluster.hierarchy import dendrogram

# ユークリッド距離とウォード法を使用してクラスタリング
z = linkage(X, metric='euclidean', method='ward')

# 結果を可視化
fig = plt.figure(figsize=(12, 6))
ax = fig.add_subplot(1, 1, 1, title="樹形図")
dendrogram(z)
plt.show()

結果がこちら。

0,1,2,3,4 が早い段階で一つのクラスタにまとまっていたり、 12と14、6と8が早々にまとまっていたりと納得性のある形になっています。

あとは、もっと扱いやすい形で、何番のデータが何番目のクラスタに所属するのかのリストを作りましょう。
ここで fcluster 関数を使います。
詳しくはドキュメントにありますが、 criterion に ‘maxclust’を指定して、最大クラスタ数で決めたり、
criterion に’distance’ を指定して、距離で閾値を指定したりできます。

ドキュメントを読むよりやってみたほうがわかりやすいと思うのでそれぞれやってみます。
(この辺の話は専用に別記事を用意して取り上げるかも。)


from scipy.cluster.hierarchy import fcluster

# クラスタ数を指定してクラスタリング
clusters = fcluster(z, t=3, criterion='maxclust')
for i, c in enumerate(clusters):
    print(i, c)

# 以下出力
0 1
1 1
2 1
3 1
4 1
5 3
6 3
7 3
8 3
9 3
10 2
11 3
12 2
13 2
14 2

0,1,2,3,4 と 5,6,7,8,9,11 と 10,12,13,14 の3グループにきちんと別れました。

この他、樹形樹に横線を引いてその位置で分けることもできます。
距離=3くらいで分ければ同じ3グループに分かれるのですが、せっかくなので別のところで切りましょう。
距離=1.7を閾値にして、4グループに分かれるのを確認します。


# 距離の閾値を決めてクラスタリング
clusters1 = fcluster(z, 1.7, criterion='distance')
for i, c in enumerate(clusters1):
    print(i, c)

# 以下出力
0 1
1 1
2 1
3 1
4 1
5 4
6 3
7 4
8 3
9 4
10 2
11 4
12 2
13 2
14 2

クラスタ1や、クラスタ2は先ほどと同じですが、6,8 と 5,7,9,11 が別のクラスタに別れました。
これらが分かれる理由は上の樹形図を見ていただければ理解できると思います。

pandasでカテゴリー変数を数値に変換する

pandasの関数を使ってカテゴリー変数を数値に変換する方法のメモです。
one-hot表現とは異なり、[“a”, “b”, “c”] みたいなデータを、 “a”は0,”b”は1,”c”は2みたいな感じで数字に変換します。

scikit-learnのデータセットにおける、正解ラベルのようなデータ型にする、と言ったほうがわかりやすいかもしれません。
Rにはカテゴリカル変数の型があるのでそちらの方がイメージしやすい人もいるかも。

使う関数は、pandas.factorizeです。

この関数に変換したいリストを渡すと、数値に変換したリストと、何番めの数がもともと何の値だったのかを示す配列を返してくれます。

実際にやってみましょう。
numpyでランダムに10個のデータを作って、それを数値化します。


import numpy as np
import pandas as pd

data = np.random.choice(["a", "b", "c"], 10)
print(data)
# ['b' 'c' 'a' 'c' 'c' 'c' 'c' 'a' 'b' 'a']
labels, uniques = pd.factorize(data)
print(labels)
# [0 1 2 1 1 1 1 2 0 2]
print(uniques)
# ['b' 'c' 'a']

うまくいきました。
uniques の値をアルファベット順にしたい時は、
factorize する時に sort=Trueを指定するとできます。


labels, uniques = pd.factorize(data, sort=True)
print(labels)
# [1 2 0 2 2 2 2 0 1 0]
print(uniques)
# ['a' 'b' 'c']

できました。

numpyで配列内の値を特定の値に制限する

前の記事の最後で、
異常時の前処理として、1〜99パーセンタイルでクリップするって話を少し書いたので、
それをnumpyで実現する関数の紹介です。
と言っても、わざわざ専用関数を使わなくても容易に実装できるのですが、せっかく用意されているのがあるので知っておくと便利です。

ドキュメントはこちら。
numpy.clip

np.clip(配列, 最小値, 最大値)と指定すると、
配列の値のうち、区間[最小値, 最大値]からはみ出た値を、その範囲に収まるように区切ってくれます。

ためしに、標準正規分布に従う値20個を生成して、[-1, 1]の範囲にクリッピングしてみましょう。


import numpy as np
data = np.random.randn(20)
print(data)
'''
[-1.71434343  0.33093523 -0.0648882   1.34432289 -0.15426638 -1.05988754
 -0.41423379 -0.8896041   0.12403786  1.40810052  0.61199047  1.60193951
 -0.72897283 -0.00861939 -0.38774556  0.40188148  0.08256356  1.61743754
 -0.12320721  1.45184382]
'''
data = np.clip(data, -1, 1)
print(data)
'''
[-1.          0.33093523 -0.0648882   1.         -0.15426638 -1.
 -0.41423379 -0.8896041   0.12403786  1.          0.61199047  1.
 -0.72897283 -0.00861939 -0.38774556  0.40188148  0.08256356  1.
 -0.12320721  1.        ]
'''

-1 より小さかった値は-1に、 1より大きかった値は1になりました。

ちなみに下記のコードでも同じことができます。


data[data < -1] = -1
data[data > 1] = 1

前回紹介した、percentileと組み合わせて使うことで、
nパーセンタイルからmパーセンタイルにクリップするということも簡単に実現できます。

試しに 5〜95パーセンタイルにクリップしたのが次のコードです。


data = np.random.randn(10)
print(data)
'''
[-0.41127091 -1.34043164  0.09598778 -1.19662011 -0.04607188 -0.02745831
  0.23184919  0.85601106  0.58430572  0.88205005]
'''
c_min, c_max = np.percentile(data, [5, 95])
print(c_min, c_max)
'''
-1.2757164503037743 0.8703325037861378
'''
data = np.clip(data, c_min, c_max)
'''
[-0.41127091 -1.27571645  0.09598778 -1.19662011 -0.04607188 -0.02745831
  0.23184919  0.85601106  0.58430572  0.8703325 ]
'''
print(data)

この例だけ見てもありがたみを感じないのですが、実際のデータを決定木などにかける時、
ほんの数件のデータだけ極端な外れ値になっていたりすると、
いい感じの範囲にデータを収めることができるので便利です。

また、scikit-learnなどのライブラリのコードを見てみると、
値を 0より大きく1より小さい範囲に収める目的などでも使われています。
ここなど
n以上m以下、ではなくnより大きいmより小さい、で区切る時は便宜上、eps=1e-15のような非常に小さい値を用意して、
[n+eps, m-eps]で代用するようですね。
こういう書き方も参考になります。

numpyのpercentile関数の仕様を確認する

中央値や四分位数を一般化した概念に分位数ってのがあります。
その中でも特にq/100分位数をqパーセンタイルといい、numpyに専用の関数が用意されています。
numpy.percentile

データの可視化や外れ値の除外で使うためにこれの仕様を確認したのでそのメモです。

そもそも僕が何を疑問に思ったのかを説明したほうがいいと思うので、いくつか例を紹介します。

まずわかりやすい例で50パーセンタイル。
これは、奇数個の値があればその中央の値、偶数個の値に対しては、真ん中の二つの値の中点を返します。


import numpy

# 5個の値の3番目の数を返す
data_1 = np.array([3, 12, 3, 7, 10])
print(np.percentile(data_1, 50))  # 7.0

# 6個の値の3番目の数と4番目の数の平均を返す
data_2 = np.array([3, 12, 3, 7, 10, 20])
print(np.percentile(data_2, 50))  # 8.5

同様にして、区切りのいい値がある時のパーセンタイルは非常にわかりやすい。
11個の値があれば、それぞれ順番に 0パーセンタイル, 10パーセンタイル, … 90パーセンタイル, 100パーセンタイルです。


data_3 = np.random.randint(0, 2000, 11)
print(data_3)
# 出力
# [1306  183 1323  266  998 1263 1503 1986  250  305 1397]
for p in range(0, 101, 10):
    print(p, "パーセンタイル・・・", np.percentile(data_3, p))
# 出力
'''
0 パーセンタイル・・・ 183.0
10 パーセンタイル・・・ 250.0
20 パーセンタイル・・・ 266.0
30 パーセンタイル・・・ 305.0
40 パーセンタイル・・・ 998.0
50 パーセンタイル・・・ 1263.0
60 パーセンタイル・・・ 1306.0
70 パーセンタイル・・・ 1323.0
80 パーセンタイル・・・ 1397.0
90 パーセンタイル・・・ 1503.0
100 パーセンタイル・・・ 1986.0
'''

ここまではわかりやすいのですが、自分が疑問に思ったのは、
もっと中途半端なパーセンタイルです。

(例)この出力の40.16ってどうやって算出された?


data_4 = np.array([15, 52, 100, 73, 102])
print(np.percentile(data_4, 17))
#  出力
# 40.16

この疑問放置したままなのが気持ち悪かったので、
これまでパーセンタイルや四分位数、そしてこれらを使う箱ひげ図などを使わなかったのですが、
とあるタスクの中で箱ひげ図を使いたくなったのでこの機会に仕様を確認しました。

といっても、numpyの該当ページにもNote.として記されていますし、
wikipediaにも普通に載ってます。
分位数
あと、pを1刻みで動かして適当なデータに対してパーセンタイル算出してプロットしたら明快にわかりました。

要は、中途半端な値に対しては、隣接の2つの値を線形補完して求めるそうです。
上の例で言えば、
15が0パーセンタイル、52が25パーセンタイルなので、17パーセンタイルは
$(52-15)*17/25+15=40.16$ と計算されています。
仕様がわかったのでこれからはバシバシ使おう。

機械学習を行う時、異常時の前処理として、1〜99パーセンタイルでクリップすると有効なことがあるという話を最近聞いたので、
それも試してみたいです。

pandasでクロス集計

テーブルデータを分析する時、2つの列の値の関係を調べたいことが良くあります。
対象の列がどちらも連続値をとるのであれば、散布図を書きますが、
これが連続値ではなく性別や都道府県、世代、アンケートのn段階評価などの時は、散布図はあまり適しません。

このような場合は、クロス集計を使うと便利です。
(wikipediaでは分割表と呼んでいるようです。)

pandasにクロス集計を行う専用の関数があるのでそれを紹介します。
(職場ではcsvに書き出してtableauに読み込ませてやることも多いのですが、pythonで完結できればそれはそれ便利です。)

ドキュメントはこちら。
pandas.crosstab

aggfuncに値を渡せば、数え上げ以外にも色々できることがあり、pivot_table的な使い方もできるのですが、
とりあえずダミーデータを用意してデータの数え上げでクロス表を作ってみましょう。
ABテストなどで頻繁に使いますからね。


import pandas as pd
import numpy as np
# ダミーデータ生成
df = pd.DataFrame(
        {
            "data1": np.random.randint(1, 6, 100),
            "data2": np.random.choice(["foo", "bar"], 100),
        }
    )
# サンプル(5件)
print(df.sample(5))
# 出力
'''
    data1 data2
71      2   bar
13      1   foo
7       5   bar
16      1   bar
56      2   foo
'''

# クロス集計
cross_df = pd.crosstab(df.data2, df.data1)
print(cross_df)
# 出力
'''
data1   1   2   3   4  5
data2                   
bar    10  13   8  12  8
foo    12   9  11   9  8
'''

とても楽にクロス集計ができました。
SQLでこれを実装しようとするとかなりの手間なので、地味に重宝しています。

完全に蛇足ですが、crosstabを知らなかった頃はこういうコードを書いていました。
indexとcolumnsだけ指定して値が全部0のデータフレームを作って、
元のデータの値を順番に数え上げています。
今思えばcrosstab知らなくても、もう少しまともな書き方がありそうです。


cross_df_2 = pd.DataFrame(
        0,
        index=set(df.data2),
        columns=set(df.data1),
    )
for _, row in df.iterrows():
    cross_df_2.loc[row.data2, row.data1] += 1    
print(cross_df_2)
# 出力
'''
     1   2   3   4  5
foo  12   9  11   9  8
bar  10  13   8  12  8
'''

pythonで一般化線形モデル(GLM) – ポアソン回帰

以前の記事で、statsmodelsを使って線形回帰をやったので、今回はポアソン回帰をやってみます。
参考:statsmodelsで重回帰分析

データは久保拓弥先生の、データ解析のための統計モデリング入門 (通称緑本)の第3章から拝借し、
本に載っているのと同じ結果を得ることを目指します。
ちなみに本のコードはRで実装されています。
Rは勉強中なので写経はしましたがそれはそれとして、
今回はいつも使っているpythonのstatsmodelsでやってみます。

データはこちらのサポートページからダウンロードできます。
生態学のデータ解析 – 本/データ解析のための統計モデリング入門
3章の data3a.csv を保存しておきましょう。

このデータは、架空の植物の種子の数に関するもので、
種子の数が$y_i$, 植物のサイズが$x_i$, 施肥処理を行ったかどうかが$f_i$列に格納されています。

そして、種子の個数$y_i$が期待値 $\lambda_i=\exp(\beta_1+\beta_2x_i+\beta_3f)$のポアソン分布に従うと仮定した場合の尤度を最大化するように係数を決定します。
($f$は施肥処理を行ったら1, 行ってない場合は0)

早速やってみましょう。
ドキュメントはこのあたりが参考になります。
statsmodels.genmod.generalized_linear_model.GLM
Model Family に Poisson を指定すると、リンク関数は自動的にlogが選ばれます。


import pandas as pd
import statsmodels.api as sm

# データの読み込み
# ファイルの取得元
# http://hosho.ees.hokudai.ac.jp/~kubo/ce/IwanamiBook.html
df = pd.read_csv("./data3a.csv")

# f 列の値を Tならば 1 , その他(Cのみ)ならば0に符号化
df["fT"] = (df.f == "T").astype(int)

y = df.y
X = df[["x", "fT"]]
# 定数列(1)を作成
X = sm.add_constant(X)

# モデル生成と学習
model = sm.GLM(y, X, family=sm.families.Poisson())
result = model.fit()

# 結果出力
print(result.summary())

# 以下出力結果
                 Generalized Linear Model Regression Results                  
==============================================================================
Dep. Variable:                      y   No. Observations:                  100
Model:                            GLM   Df Residuals:                       97
Model Family:                 Poisson   Df Model:                            2
Link Function:                    log   Scale:                          1.0000
Method:                          IRLS   Log-Likelihood:                -235.29
Date:                Sun, 21 Apr 2019   Deviance:                       84.808
Time:                        16:36:24   Pearson chi2:                     83.8
No. Iterations:                     4   Covariance Type:             nonrobust
==============================================================================
                 coef    std err          z      P>|z|      [0.025      0.975]
------------------------------------------------------------------------------
const          1.2631      0.370      3.417      0.001       0.539       1.988
x              0.0801      0.037      2.162      0.031       0.007       0.153
fT            -0.0320      0.074     -0.430      0.667      -0.178       0.114
==============================================================================

本のP58に載っているのと全く同じ係数を得ることができました。

ついでに実際のデータとこのモデルから予測される種子数をプロットして可視化してみます。


import matplotlib.pyplot as plt
import numpy as np

# プロット用のデータ作成
xx = np.linspace(7, 13, 101)
yy0 = np.exp(result.params["const"] + result.params["x"]*xx)
yy1 = np.exp(result.params["const"] + result.params["x"]*xx + result.params["fT"])

# 可視化
fig = plt.figure(figsize=(8, 7))
ax = fig.add_subplot(1, 1, 1)
ax.set_xlabel("個体のサイズ")
ax.set_ylabel("種子の個数")
ax.scatter(X[X.fT == 0]["x"], y[X.fT == 0].values, marker="x", label="肥料無し")
ax.scatter(X[X.fT == 1]["x"], y[X.fT == 1].values, alpha=0.7, label="肥料有り")
ax.plot(xx, yy0, linestyle="--",  label="肥料無し")
ax.plot(xx, yy1, label="肥料有り")
plt.legend()
plt.show()

出力結果がこちら。

pandasで散布図行列を書く

何かしらのデータについて調べる時、各データごとの関係をまとめて可視化するために
散布図行列(matrix of scatter plots)を描きたくなることがあります。

matplotlibでゴリゴリ実装するのは手間なので、seabornが良く使われていると思うのですが、
実はpandasでも作成できるのでその紹介です。
(seabornを使えばいいじゃん、というのはごもっともなのですが、
なぜか僕の自宅環境ではwarningが発生し、その原因調査中なのであまり使いたくないのです。)

ドキュメントはこちら。
pandas.plotting.scatter_matrix

早速ですが、irisでやってみましょう。


import pandas as pd
import matplotlib.pyplot as plt
from sklearn.datasets import load_iris

iris = load_iris()
df = pd.DataFrame(
        iris.data,
        columns=iris.feature_names,
    )
pd.plotting.scatter_matrix(df, figsize=(15, 15), c=iris.target)
plt.show()

そして表示される結果がこちらです。

オプションで、 引数cにクラス情報を渡して色もつけました。
このコーディング量でかける図としては十分なクオリティではないでしょうか。

機械学習のプロダクト適用におけるリアルを語る会に参加しました

先日、ABEJAさんのオフィスで開催された、機械学習のプロダクト適用におけるリアルを語る会に参加しました。

AI/MLシステム開発の難しさ

発表者はディー・エヌ・エーの鈴木翔太さん。

企画からリリースまでの各段階において大変だったことを話されていました。
自分も経験があることが多くて全面的に納得です。

企画段階では、チームで何を作るのかのゴールの認識合わせが重要。
精度何%くらいを目指すのかと言った話もしておく必要があります。
また、全てを機械学習でやろうとしないことも大切ですね。

組織については、ML/AI開発チームにもソフトウェアエンジニアを入れて連携しているという話以前に、
MLチーム自体を作れてることを羨ましいと思いました。
(自分の職場には機械学習専門チームはなく、アナリストタスクと掛け持ちなので。)
懇親会で伺った話によるとやはりkaggler枠などの影響もあり、リファラルで採用が進んでいるそうです。

データ収集やアノテーション、データ管理が大変なのはどこでも語られている通り。
インフラをコスパ良く作るのも大変です。

また、誰が本番用コードを書くのかの話題も取り上げられました。
データサイエンティストが自分で書くのか、エンジニアに依頼するのか。
どちらも一長一短あります。
データサイエンティストのコードが汚いのは世界共通なんだろうか。

品質担保の話も取り上げられていたのは珍しいと感じました。
ただ、自分としてもここは悩んだ覚えがあります。
推論を含むシステムがエラーなく動くことをテストするのはいいとして、
予測である以上は結果が外れのこともあるわけで、
ここのデータに対して予測を外すのが正解みたいなテストを書くのも変で、
何がベストなのかよくわからなくなります。
そして最後はリリース後の評価やコストの話。

Deep Learningでリアルタイムに マーケット予測をしてみた

発表者はAlpaca Japanの梅澤慶介さん。

自分自身が個人トレーダーでもあるので密かに楽しみにしていたマーケット予測の事例。
ただ、やはり苦労されていたのはチームで開発するにあたって発生するあれこれのようでした。
メンバーの開発環境を再現できないとか、渡されたnotebookが動かないとか。
うちもここのメンバーのローカル環境揃ってないので良くわかります。
(自分一人に限ってもローカルPCで動いてたコードが開発用に建ててもらったインスタンスで動かないことがある。)
それをコンテナなどの技術で解決していったそうです。
Notebook禁止は衝撃。

機械学習のプラットフォームを自前で構築したり、
コードを共有してライブラリ化したりと、チームでの開発が順調に進んでいるようです。
ModelPackageみたいなの、僕も作りたいですね。

懇親会でも機械学習以外の部分含めて色々と質問ができ、非常に有意義でした。

智を集約しツラみを乗り越えたリピート推定の開発現場

発表者はABEJAの中川裕太さん。

題材はいくつかのミートアップですでに伺っているABEJA Insight for Retailのリピーター推定の話でした。
ただ、これまでと少し違った視点でのつらみの話があり興味深かったです。

「無限のビジネス可能性の前に立ち尽くす」ってのは新しい視点ですね。
たしかに、これができればいろんなことができそうっていうワクワクはありますが、
なんでもできるはなんにもできないと同じ、っていうのはあると思います。
(自分もかなり身に覚えがある)
顧客が片付けたいジョブに集中することが大事です。

研究と開発の時間軸が違って大変という話は、以前も伺った通りで
自分も直面するところなので良くわかります。
また、作ってる側から新しい手法が発見されるというも辛いところ。

コストと精度のトレードオフもありますね。
ここは、小売業向けのビジネスとしてのシビアな側面もありそうです。
(金融や医療、公的機関や大企業などお金があるところ向けでも無縁ではないと思いますが。)

今回はそれぞれの解決策もあり参考になりました。

マイクロサービスのDAGにして共通部分を使い回すなど、
実際、やってみるとかなり大変なのですがよく実現されたと思います。
スケーリングなどによるコスト最適化も、
アイデアは単純ですがおそらく実現は結構難しかったのではないかと。
精度の最適化の方にまだまだ課題も残っているようなので、今後に期待です。

まとめ

つらみの共有的な話を聞くことが増えていますが、
他社の人たちもそれぞれ苦戦しながら課題に立ち向かわれているのを知ると励みになりますね。
(普段は下手すると自分だけが効率悪く苦労しているような気がしてくるので。)

若干人数少なめな回だったのもあって、
懇親会でも発表者の方含めてゆっくり話ができて非常に有意義な時間を過ごせました。

機械学習の活用における課題は、
データサイエンティストだけで解決できるものだけではないと思うので、
この辺の知見はきちんと職場で共有して他チームの協力を得て取り組んでいきたいです。

Mac book に R環境構築

普段のデータ分析に使う環境はSQL, python, Tableau, Excelなどで完結させていたのですが、
業務上の都合でRも使うことになったので改めて学習を始めました。

Rを使うこと自他は初めてではなくpython使い始める前に何度か勉強したのですが、
どうにも好きになれなかったんで最近遠ざけていたのですがいよいよそうは言ってられなくなりましたね。

とりあえず、私物のMacにはインストールしていなかったので環境構築から始めます。
まずはRのインストールから。
配布サイトからダウンロードする方法もありますが、
homebrewを使う方法を採用しました。
いくつかのページを見ると、何かリポジトリを tapしないといけないように書いてあるところもあるのですが、
何もせずに brew install コマンドだけで入りました。


~$ brew install r
==> Installing dependencies for r: mpfr, gcc, libpng, openblas, pcre and readline
#(略) 依存パッケージとr本体がインストールされる

# 実行と終了
~$ r

R version 3.5.3 (2019-03-11) -- "Great Truth"
Copyright (C) 2019 The R Foundation for Statistical Computing
Platform: x86_64-apple-darwin18.2.0 (64-bit)

R は、自由なソフトウェアであり、「完全に無保証」です。
一定の条件に従えば、自由にこれを再配布することができます。
配布条件の詳細に関しては、'license()' あるいは 'licence()' と入力してください。

R は多くの貢献者による共同プロジェクトです。
詳しくは 'contributors()' と入力してください。
また、R や R のパッケージを出版物で引用する際の形式については
'citation()' と入力してください。

'demo()' と入力すればデモをみることができます。
'help()' とすればオンラインヘルプが出ます。
'help.start()' で HTML ブラウザによるヘルプがみられます。
'q()' と入力すれば R を終了します。

> q()

続いて、開発環境であるRStudioもインストールします。
これも配布元からダウロードする手もあるのですが、
homebrewで入れました。

これはアプリケーションフォルダに入れてGUIで使いたいので、brew cask を使います。


~$ brew cask install rstudio
==> Tapping homebrew/cask
Cloning into '/usr/local/Homebrew/Library/Taps/homebrew/homebrew-cask'...
remote: Enumerating objects: 4110, done.
remote: Counting objects: 100% (4110/4110), done.
remote: Compressing objects: 100% (4103/4103), done.
remote: Total 4110 (delta 24), reused 406 (delta 5), pack-reused 0
Receiving objects: 100% (4110/4110), 1.32 MiB | 187.00 KiB/s, done.
Resolving deltas: 100% (24/24), done.
Tapped 1 command and 4002 casks (4,116 files, 4.2MB).
==> Caveats
rstudio depends on R. The R Project provides official binaries:

  brew cask install r

Alternatively, the Homebrew-compiled version of R omits the GUI app:

  brew install r

==> Satisfying dependencies
==> Downloading https://download1.rstudio.org/desktop/macos/RStudio-1.2.1335.dmg
######################################################################## 100.0%
==> Verifying SHA-256 checksum for Cask 'rstudio'.
==> Installing Cask rstudio
==> Moving App 'RStudio.app' to '/Applications/RStudio.app'.
🍺  rstudio was successfully installed!

これで完了です。
念の為ですが、homebrew何も問題が起きてないことも見ておきました。


~$ brew doctor
Your system is ready to brew.