データサイエンティストってなくなるのでは?社内で感じた焦りと今後の生存戦略について

f:id:mamama039356:20210612173120p:plain 産業機械業界でデータサイエンスとドメイン知識を組み合わせて機械の自動化をしている者です。

最近、社内でノンコードツールの普及と人材教育の波をひしひしと感じ、このままデータサイエンスにしがみついていいのか不安になりました。

現場で感じた焦りと現場データエンジニアの生存戦略について考えてみました。

ノンコードツールの普及

私のいる会社ではノンコード化のプロジェクトが各拠点で立ち上がっています。

ノンコードツールはPythonやRには到底及ばないだろうと思っていましたが、実演している様子を見ると考えが変わりました。

ノンコードツールはデータ解析フローが画面1枚にブロック形式で記述でき中身が理解しやすかったです。

また今回作成されたスクリプトは素人の方が作ったものでしたが、データサイエンティストと同レベルの解析ができていました。

作成に要した時間も短く、作成時間は数分だったようです。

今後ノンコードツールが普及すると、データ解析言語のコーディング作業は不要になるのではと思ってしまいました。

コーディング不要になると解析の中身を知るだけでよいので、プログラマーと非プログラマーの差はなくなるでしょう。

解析を一つにしたブロックが用意されていることから、解析技術の差もエンジニア間でなくなるのではないでしょうか。

とは言ってもデータサイエンティストが工夫をしたモデルを作るにはセンスが必要です。

ノンコード化の普及ではデータサイエンティストの職は無くならないとは思いますが、敷居が低くなるのは確かなので市場価値は低くなると思います。

ただしコードの自動生成技術が実用化レベルになったときには、AIが最も適したモデルを提案してくれるようになるのではと考えています。

もしもコードの自動生成が実現すれば、エンジニアの仕事は問題設定を与えることだけになりデータサイエンティストは世の中からいなくなるかもしれません。

社内データサイエンティスト人材育成の活発化

現場に近い環境で技術開発をするために、現場エンジニアを対象にデータサイエンス人材を育てる動きが活発になっています。

現場エンジニアも近年のAIブームの動向からデータサイエンスに興味のある人が多く講義は盛況しているとのことです。

特に若手は積極的な人をよくみます。

専門的だったデータサイエンスが一般的になることで、データサイエンティストの市場価値はさらに低くなるかと思われます。

今後ノンコードツールと知識が普及することでデータ解析を本職とする人の間で差別化をすることは難しくなるのではないでしょうか。

現場エンジニアが市場価値を高めるには?

解析技術でエンジニア間の差別化は難しくなり、今後は+αの要素として扱われるのではと考えています。

今後はコードで表現できない思考、AIが取り扱えない知識に価値が生まれるのではないでしょうか?

現在のAIは問題設定ができませんので、人間が問題設定をしたり新しい切り口の提案(ひらめき)は再現できないと思われます。

またAIが自律しない限りドメイン知識の習得は人にしかできないと思われます。

私が考える現場エンジニア生存戦略は、DXの流れとは逆行して現場でしか得られない経験や情報を蓄積することです。

ただしドメイン知識を使える環境は限られていますので、今後伸びるであろう業界で身につけたいところです。

線形モデルの逆解析 ー 制約付き最小二乗法(CLS)

逆問題では観測誤差により逆過程で求めた解が大きくばらつく誤差鋭敏性が起きます。 誤差鋭敏性の対応策として制約付き最小二乗法の説明と実装方法を紹介します。

制約付き最小二乗法

誤差鋭敏性を直接的に抑える手法ではありませんが、解集合を有界にすることで解の変動を制約条件内に抑えられます。 手法は解に制約条件を設けて最小二乗法をするだけでシンプルです。

$$ Min \space (Ax-b)^{2} \space \text{s.t.} \space lb \leq x \leq ub$$

import numpy as np
from scipy.optimize import lsq_linear

# lbは下限、ubは上限、Arは一般逆行列、yは観測値
bounds = np.array([lb0, lb1], [ub0, ub1])
res = lsq_linear(Ar, y, bounds=bounds, lsmr_tol="auto", verbose=2)
# 解
res.x 

制約付き最小二乗解の可視化

逆問題の誤差鋭敏性についての観測値b1にガウスノイズ(観測誤差)を加えます。 制約付き最小二乗法で求めた解の分布を一般逆行列と比較しましょう。

n=100で解x1とx2の分布を箱ヒゲ図にしました。 横軸はノイズの標準偏差です。 下限を-40、上限を40にしています。

f:id:mamama039356:20210529194912p:plain

制約付き最小二乗解のx1は上限の40に張り付き、x2も上限40に張り付いています。 これは最小二乗解が制約条件外にあり、制約条件を満たす中で最もいい解は上限になったからです。 誤差鋭敏性はありませんが最小二乗解から外れているので精度は良くありません。

まとめ

制約付き最小二乗法の説明と実装方法を紹介しました。解集合を有界にすることで解の変動を制約条件内に抑えられます。低ランク近似やチホノフ正則化と併用できる強力な手法です。最小二乗法と謳っています線形計画を使っているのでご注意を。

線形モデルの逆解析 ー チホノフ正則化

逆問題では観測誤差により逆過程で求めた解が大きくばらつく誤差鋭敏性が起きます。 誤差鋭敏性を抑える手法としてチホノフ正則化(Tikhonov regularization)の説明と実装方法を紹介します。

チホノフ正則化

残差平方和に原因$ X$のL2ノルム正則化項を加えたものを最小化して一般逆行列を得る方法です。 正則化項を加えることで誤差鋭敏性で$ X_{pred}$が大きくなるのを抑えられます。

チホノフ正則化により特異値は$ \mu$から$ \mu(1+\frac{\alpha}{\mu})$に大きくなります。 小さな特異値成分を大きくすることで誤差鋭敏性が抑えられますが、正則化パラメーターは他の特異値にも影響するので注意が必要です。

$$ Min \space |Ax-b|^2 + \alpha |x|^2$$

$$ A ^ {†} _ \alpha = \sum_ {k=0} ^ {r} \frac{\mu_k}{\mu ^ 2 _ k + \alpha} v _ k u _ k ^ {T}$$

$$ x _ {\alpha} = A _ {\alpha}^{†} b $$

# Arは低ランク近似済み
ur, sr, vhr = np.linalg.svd(Ar, full_matrices=False)
# alphaは正則化パラメータ
alpha_root = np.ones(sr.shape[0]) * alpha
# 正則化した特異値の逆数を計算
sr_root = sr/(np.power(sr, 2) + alpha_root)
# チホノフ正則化した一般逆行列
A_tikhonov = np.matmul(vhr.T, np.multiply(sr_root[..., np.newaxis], ur.T))

実はリッジ回帰と効果は同じです。 リッジ回帰では偏回帰係数を正則化項にするのに対して、チホノフ正則化では原因$ X$を正則化項にします。 どちらの手法を選んだとしても偏回帰係数が正則化されると解釈され同じ効果が予想されます。 しかしチホノフ正則化ではモロゾフの相反原理という強力な手法で正則化パラメータを一意に決めることができます。

モロゾフの相反原理

誤差鋭敏性が抑制され予測精度を落とさない適切な正則化パラメーター$ \alpha$を事前に決める必要があります。 観測誤差を$\delta$とすると$ |Ax_{\alpha}-b|=\delta$になるように正則化パラメーターを決めるとちょうど良くなります。 これをモロゾフ相反原理といいます。 残差を観測誤差にすることで観測誤差が出力空間に閉じ込められ、観測誤差を含む観測値の逆像も入力空間に閉じ込められることになり誤差鋭敏性を抑えることができます。

二分法でモロゾフの相反原理を満たす正則化パラメータを求めます。

# Aは偏回帰係数、srは特異値行列、vhrは右特異行列、urは左特異行列、b0は観測値、dは観測誤差
def f(x, A_, sr_, vhr_, ur_, b0_, d_):
    return np.linalg.norm((A_ @ np.matmul(vhr_.T, np.multiply(sr_/(np.power(sr_, 2) + np.ones(sr_.shape[0]* x))[..., np.newaxis], ur_.T))@b0_)-b0_) -d_
alpha = optimize.bisect(f_test, 0, 1000, args=(Ar, sr, vhr, ur, b0, d))

チホノフ正則化の可視化

逆問題の誤差鋭敏性についての観測値b1にガウスノイズ(観測誤差)を加えます。 チホノフ正則化で求めた解の分布を一般逆行列と比較しましょう。

n=100で解x1の分布を箱ヒゲ図にしました。 横軸はノイズの標準偏差です。 正則化パラメータは0.1にしています。

f:id:mamama039356:20210529194418p:plain

一般逆行列の解に対してチホノフ正則化解の分布は狭いことが一目瞭然です。 誤差鋭敏性が抑えられています。

まとめ

チホノフ正則化の説明と実装方法をまとめました。使い方としてはモロゾフ相反原理で正則化パラメータを事前に決めてSVDを用いた低ランク近似で粗調整をしてチホノフ正則化で微調整をするという流れです。 低ランク近似とチホノフ正則化をしても誤差鋭敏性は付き纏いますが、グリッドサーチに比べて計算量が圧倒的に小さいです。 制御では解を陽に求める方法がおすすめです。

線形モデルの逆解析 ー SVDを用いた低ランク近似

f:id:mamama039356:20210529193506p:plain 逆問題では観測誤差により逆過程で求めた解が大きくばらつく誤差鋭敏性が起きます。 誤差鋭敏性を抑える手法として特異値分解(Singular Value Decomposition; SVD)を用いた低ランク近似手法の説明と実装方法を紹介します。

特異値と誤差鋭敏性の関係

SVDは行列分解の一手法です。正方行列に限らず任意の形の行列を分解できます。 行列$M$は$M$の入力空間の正規直交基底を表す右特異行列$V$と$M$の出力空間の正規直交基底を表す左特異行列$U$と入力成分がそれぞれ何倍されて出力されるかを表す特異値行列$\Sigma$に分解されます。 SVDは行列$ M$が右特異ベクトルで表せる固有ベクトルに対してどれだけ回転と伸長させるかを表現しています。

逆解析は出力空間上の観測値ベクトルの逆像を求めることですが、観測誤差は実数空間上に任意のベクトルをとるため逆像は入力空間からはみ出してしまいます。 特異値が小さな特異ベクトルでは出力空間上の観測誤差に特異値の逆数が掛け合わされるため、解が大きくばらついてしまいます。 これが誤差鋭敏性です。

観測誤差を$ 0$にすれば誤差鋭敏性は起きませんが現実的に不可能です。 誤差鋭敏性は小さな特異値を削除する低ランク近似、あるいは大きくするチホノフ正則化により抑えられます。 ちなみに順過程で逆解析すると特異値の逆数が掛け合わされないため誤差鋭敏性は生じません。 計算量が大きくても構わないのであればグリッドサーチで逆解析するのが正確です。

SVDを用いた低ランク近似

偏回帰係数$ A$の小さな特異値成分を打ち切り、大きな特異値成分のみで行列を表現します。ただし行列$ A$のランクが小さい場合は特異ベクトルの持つ情報量が大きいので削除後の行列$ A_r$は$ A$と乖離する恐れがあるので注意が必要です。

$$ A = U \Sigma V^{T}$$

$$ A = \sum_{k=0}^{} \mu_k u_k v_k^{T}$$

$$ A_r = \sum_{k=0}^{r} \mu_k u_k v_k^{T}$$

$$ A_r^{†} = \sum_{k=0}^{r} \frac{1}{\mu_k} v_k u_k^{T}$$

$ A$をSVDして$ 0.01$以下の特異値ベクトルを除去します。

u, s, vh = np.linalg.svd(A)
# 0.01以上のランクを求めます。
r = np.linalg.matrix_rank(np.diag(s), tol=0.01)
ur = u[:, r]
sr = s[:r]
vhr = vh[:r, :]
# 低ランク近似をした行列
Ar = np.matmul(ur, np.multiply(sr[..., np.newaxis], vhr))
# 低ランク近似をした一般逆行列
Ard = np.matmul(vhr.T, np.multiply(1/sr[..., np.newaxis], ur_T))

観測値$y$の原因$X$は以下で求められます。 $$ X = A_r^{†} y$$

まとめ

SVDを用いた低ランク近似手法を紹介しました。低ランク近似は小さな特異値成分をバッサリ切れるのがメリットです。一方で基準が明確でないため切り過ぎてしまう、あるいは足りないことがあります。なので低ランク近似で特異値が小さすぎる成分を粗く切っておき、次に紹介するチホノフ正則化で微調整をするとうまくいきます。

Axでベイズ最適化によるパラメータチューニングを可視化

Ax は Facebook のオープンソース最適化ライブラリです。 カーネルや獲得関数を定義しなくても動くので、知識がなくてもベイズ最適化が使えます。 Ax にはインタラクティブのグラフを描く api があり、ベイズ最適化によるパラメータチューニングを可視化できます。

インストール

pip あるいは conda でインストールします。

pip install ax-platform jupyter

APIs

Ax の API には loop, service, developer の3つがあります。 中でも developer api はデータサイエンティスト、機械学習エンジニア、研究者向けに作られた api でカスタム性が高いのが特徴です。今回は developer api を使ってパラメータチューニングをします。

ベイズ最適化について

ベイズ最適化は目的関数が最大になるパラメータを効率よく探索する数理最適化手法です。 期待値が大きいところを採用する「活用」と、分散が大きいところを採用する「探索」を数値化した獲得関数が最大になるパラメータを推薦し、これを繰り返して逐次的に最適な条件を探します。 ベイズ最適化で膨大な時間がかかるパラメータチューニングをさっくりと済ませられます。

SVR のパラメータチューニング

ボストン住宅価格データセットを用いて SVR のパラメータチューニングをします。

import numpy as np
from ax import ParameterType, RangeParameter, SearchSpace, SimpleExperiment, modelbridge, models
from sklearn.svm import SVR
from sklearn.preprocessing import StandardScaler
from sklearn.model_selection import train_test_split, KFold
from sklearn.datasets import load_boston
from ax.utils.notebook.plotting import render, init_notebook_plotting
init_notebook_plotting()

boston = load_boston()
X = boston.data
y = boston.target
X_train, X_test, y_train, y_test = train_test_split(X, y)
scaler = StandardScaler()
autoscale_X_train = scaler.fit_transform(X_train)
autoscale_X_test = scaler.transform(X_test)
kf = KFold()

次に SVR のハイパーパラメータ C, epsilon, gamma の探索領域を定義します。

svr_search_space = SearchSpace(
    parameters=[
        RangeParameter(name="C",
                       parameter_type=ParameterType.FLOAT,
                       lower=0.1, 
                       upper=100, 
                       log_scale=True
                      ), 
        RangeParameter(name="epsilon",
                       parameter_type=ParameterType.FLOAT, 
                       lower=0.001,
                       upper=100,
                       log_scale=True
                      ),
        RangeParameter(name="gamma",
                       parameter_type=ParameterType.FLOAT, 
                       lower=0.001,
                       upper=100,
                       log_scale=True
                      ),
    ]
)

ベイズ最適化でパラメータチューニングをします。 SVR のスコアを返す関数 svr_evaluation_function を事前に定義し、SimpleExperiment オブジェクトを生成します。

kfold_scores=[]
kfold_parameters=[]
for train_index, valid_index in kf.split(X_train):
    X2d_train = X_train[train_index]
    X2d_valid = X_train[valid_index]
    y2d_train = y_train[train_index]
    y2d_valid = y_train[valid_index]
    scaler_2d = StandardScaler()
    autoscale_X2d_train = scaler_2d.fit_transform(X2d_train)
    autoscale_X2d_valid = scaler_2d.transform(X2d_valid)
    # 目的関数を出力する関数
    def svr_evaluation_function(parameterization, weight=None):
        svr = SVR(C=parameterization["C"],
                  epsilon=parameterization["epsilon"],
                  gamma=parameterization["gamma"]
                 )
        svr.fit(autoscale_X2d_train, y2d_train)
        accuracy = svr.score(autoscale_X2d_valid, y2d_valid)
        return {"accuracy": (accuracy, 0.0)}
    exp = SimpleExperiment(name="test_svm",
                           search_space=svr_search_space,
                           evaluation_function=svr_evaluation_function,
                           objective_name="accuracy"
                          )
    # ベイズ最適化
    sobol = modelbridge.get_sobol(search_space=exp.search_space)
    print(f"Running Sobol initialization trials...")
    for _ in range(5):
        exp.new_trial(generator_run=sobol.gen(1))
    for i in range(15):
        print(f"Running GP+EI optimization tiral {i+1}/15...")
        gpei=modelbridge.get_GPEI(experiment=exp, data=exp.eval())
        exp.new_trial(generator_run=gpei.gen(1))
    best_objectives = exp.eval().df["mean"]
    bestParameter_score = np.max(best_objectives)
    bestParameter_trial = np.argmax(best_objectives)
    bestParameter = exp._trials[bestParameter_trial].arm.parameters
    kfold_scores.append(bestParameter_score)
    kfold_parameters.append(bestParameter)
# チュー二ングされたパラメータ
svr_parameter = kfold_parameters[np.argmax(kfold_scores)]
svr = SVR(**svr_parameter)
# テストデータでスコアを計算
svr.fit(autoscale_X_train, y_train)
svr_score = svr.score(autoscale_X_test, y_test)

sobol で最初の 5 点をランダムに決めて、20 回探索で最もスコアの高かったパラメータをベストパラメータとします。 exp.evalで過程が見れます。 best_objectives には探索した期待値が入っており、最も高いスコアをベストパラメータとしています。 gpei の名前はガウス過程回帰と獲得関数 EI から来ており, gpeiに詳細が記録されています。 グラフを描くときは、gpeiを参照することになります。

チューニングされたパラメータを用いてテストデータのスコアを計算します。

>>> svr_score
0.77991406620649

計算時間は 1 分 15 秒でした。

探索過程の可視化

jupyterlab で plotly をインラインで描けるように設定して ax でグラフを描きます。 詳細は以下のリンクを参照してください。 オフライン環境でplotlyを活用するための基礎(html出力、png出力、インライン描画)

先ほどの逐次最適化の過程をグラフにします。 横軸を探索回数、縦軸をベストスコアにしたグラフです。

from ax.plot.trace import optimization_trace_single_method

best_objectives = np.array([[trial.objective_mean*100 for trial in exp.trials.values()]])
best_objective_plot = optimization_trace_single_method(
y=np.maximum.accumulate(best_objectives, axis=1),
title="Model performance vs. # of iterations",
ylabel="Regression Accuracy, %",)
render(best_objective_plot)

f:id:mamama039356:20210529120230p:plain

17 回以降はスコアが変わらなかったので、チューニングができているはずです。

2 つのパラメータを軸に取って期待値と標準偏差を表したグラフです。

from ax.plot.contour import interact_contour

render(interact_contour(model=gpei, metric_name='accuracy'))

グラフを見ると期待値が大きな領域を重点的に探索しています。 ただ外れた領域も探索している傾向が見られます。 これは探索の過程で標準偏差が大きい領域が見つかったが、計算したところ期待値は小さく以降探索されなくなったのだと思います。

パラメータ 1 つ選択してスコアの期待値と標準偏差を表したグラフです。

from ax.plot.slice import plot_slice

render(plot_slice(gpei, "epsilon", "accuracy"))

f:id:mamama039356:20210529120257p:plain

f:id:mamama039356:20210529120328p:plain

f:id:mamama039356:20210529120404p:plain

まとめ

Ax で SVR のパラメータチューニングを行いました。sklearn のモデルにも簡単に組み込めて使いやすかったです。機械学習のパラメータチューニングにはもってこいです。

おまけ

グリッドサーチでパラメータチューニングをした結果です。

from sklearn.pipeline import make_pipeline
from sklearn.model_selection import GridSearchCV

pipe = make_pipeline(StandardScaler(), SVR())
C_grid = np.linspace(0.01, 100, 50)
gamma_grid = np.linspace(0.001, 100, 50)
epsilon_grid = np.linspace(0.001, 100, 50)
param_grid = [{pipe.steps[1][0] + "__C": C_grid, 
               pipe.steps[1][0] + "__gamma": gamma_grid, 
               pipe.steps[1][0] + "__epsilon": epsilon_grid
              }]
reg = GridSearchCV(pipe, param_grid=param_grid)
reg.fit(X_train, y_train)
score = reg.score(X_test, y_test)
>>> score
0.6963721758604782

計算時間は10分49秒でした。分割数が50点で少なめですが、ベイズ最適化よりもおよそ10倍時間がかかり、スコアは低くなっています。

オフライン環境でplotlyを活用するための基礎(html出力、png出力、インライン描画)

Plotlyは インタラクティブなグラフをかけるデータ可視化ツールです。 オフライン環境でhtml, pngに出力する方法、jupyterlabにインラインでグラフを出力する方法をまとめました。

環境

  • Ubuntu 20.0.4
  • Python 3.8.10

インストール

グラフをhtml出力するためにplotlyをインストールします。

pip install plotly

次にグラフをpngで保存するためにkaleidoをインストールします。

pip install kaleido

最後にjupyterlabのインラインでグラフを描くためにjupyterlabの拡張機能をインストールします。

pip install jupyterlab
curl -sL https://deb.nodesource.com/setup_14.x -o nodesource_setup.sh
sudo bash nodesource_setup.sh
sudo apt update && install nodejs
jupyter labextension install jupyterlab-plotly@4.14.3

注意する事はnode.jsのバージョンが12.0.0以上であることです。plotly拡張機能が動きません。bashの命令でnode.jsのインストールバージョンを12.0.0以上にしています。

オフラインでhtml出力

plotly.offline.plot()でhtml出力されます。

import numpy as np
import plotly.graph_objects as go
from plotly import offline

X = np.linspace(-np.pi, np.pi, 100)
Y = np.sin(X)

fig = go.Figure()
trace = go.Scatter(x=X, y=Y)
fig.add_trace(trace)
fig.update_layout(title="y=sin(x)")
offline.plot(fig)

実行するとグラフがhtmlで出力されます。

f:id:mamama039356:20210529115813p:plain

オフラインでpng出力

plotly.io.pio(engine="kaleido)でpng出力されます。

import plotly.io as pio
import kaleido

pio.write_image(fig, "sin.png", engine="kaleido", scale=10)

scaleを指定しなければ解像度が低めで出力されます。

f:id:mamama039356:20210529115848p:plain

インラインでグラフ描画

plotly.offline.iplot()でインライングラフを描けます。

offline.iplot(fig)

セルの下にインラインでグラフが描かれます。

f:id:mamama039356:20210529115913p:plain

参考

おまけ

Windowsではkaleidoの代わりにorca.exeが使えます。 orca.exeをインストール後、plotlyの設定ファイルを変更します。

pip install plotly
pip install psutil
pio.orca.config.executable="path/to/orca.exe"
pio.orca.config.save()

Docker composeで現場エンジニアのデータ解析環境を構築(jupyterlab mlflow mysql streamlit)

産業機械業界で働いているプロセスエンジニアのデータ解析環境を公開します。

プロセスエンジニアに必要なツール

  • モデルが異なる実験を数多くする 👉 インタラクティブなデータ解析
  • 過去の実験データを使えると好ましい 👉 モデル管理 👉 データベース活用
  • 生データをグラフにして報告する機会が多い 👉 可視化アプリ

データ解析環境の概要

上記を満たす環境を以下のコンテナを用意して Docker compose でデプロイします。 - データ解析: jupyterlab - モデル管理: mlflow - データベース: mysql - 可視化アプリ: streamlit

f:id:mamama039356:20210529115056p:plain


ディレクトリ構成

.
├── Read.md
├── db
│   └── data
├── docker
│   ├── jupyter
│   │   └── Dockerfile
│   ├── mlflow
│   │   └── Dockerfile
│   └── streamlit
│       └── Dockerfile
├── docker-compose.yml
├── etc
│   ├── cookiemamama.yaml
│   ├── exp
│   ├── my.cnf
│   └── requirements_simple.txt
├── mlruns
├── projects
│   └── experiment1
└── struns
    └── main.py

実験ディレクトリは cookiecutter/datascience を用いて生成します。exp_name/notebookにノートブック、exp_name/srcにスクリプト、exp/data/rawにデータを配置します。

experiment1
├── LICENSE
├── Makefile
├── README.md
├── data
│   ├── external
│   ├── interim
│   ├── processed
│   └── raw
├── docs
│   ├── Makefile
│   ├── commands.rst
│   ├── conf.py
│   ├── getting-started.rst
│   ├── index.rst
│   └── make.bat
├── models
├── notebooks
├── references
├── reports
│   └── figures
├── requirements.txt
├── setup.py
├── src
│   ├── __init__.py
│   ├── data
│   │   ├── __init__.py
│   │   └── make_dataset.py
│   ├── features
│   │   ├── __init__.py
│   │   └── build_features.py
│   ├── models
│   │   ├── __init__.py
│   │   ├── predict_model.py
│   │   └── train_model.py
│   └── visualization
│       ├── __init__.py
│       └── visualize.py
├── test_environment.py
└── tox.ini

Docker-compose ファイル

version: '3'
services:
  db:
    container_name: mysql
    command: --default-authentication-plugin=mysql_native_password
    environment:
      - MYSQL_DATABASE=analysis_db
      - MYSQL_ROOT_PASSWORD=root
      - MYSQL_USER=docker
      - MYSQL_PASSWORD=docker
      - TZ=Asia/Tokyo
    image: mysql:8.0
    platform: linux/x86_64
    ports:
      - "3306:3306"
    restart: always
    volumes:
      - ./db/data:/var/lib/mysql
      - ./etc/my.cnf:/etc/mysql/conf.d/my.cnf
      
  jupyter:
    build:
      context: .
      dockerfile: ./docker/jupyter/Dockerfile
    command: jupyter lab --ip=0.0.0.0 --port=8080 --allow-root --no-browser --NotebookApp.token=""
    container_name: jupyterlab
    depends_on: 
      - mlflow
      - db
    ports:
      - "8080:8080"
    restart: always
    volumes:
      - ./projects:/home/projects
    
  mlflow:
    build:
      context: .
      dockerfile: ./docker/mlflow/Dockerfile
    command: mlflow server --backend-store-uri /home/mlruns --host 0.0.0.0 --port 5000
    container_name: mlflow
    ports:
      - "5000:5000"
    restart: always
    volumes:
      - ./mlruns:/home/mlruns

  streamlit:
    build:
      context: .
      dockerfile: ./docker/streamlit/Dockerfile
    command: streamlit run struns/main.py
    container_name: streamlit
    depends_on: 
      - db
    ports:
      - "8501:8501"
    volumes:
      - "./struns:/usr/src/app/struns"

Docker ファイル

jupyterlab

pip で統一したかったので python ベースイメージを用いました。 python:3.x-slim, -slim-buster では jupyterlab がインストールできなかったので、-busterを使っています。 etc/cookiemamama.yaml は cookiecutter の設定ファイルです。 etc/exp は cookiecutter の設定ファイル実行するためのシェルスクリプトです。 jupyterlab==1.7.0 ではjupyter server extension disable nbclassicを実行しないと起動しないようです。

FROM python:3.8.10-buster
MAINTAINER mamamaJohn
USER root
WORKDIR /home/projects

COPY etc/requirements_simple.txt /tmp/requirements_simple.txt
COPY etc/cookiemamama.yaml /home/cookiemamama.yaml
COPY etc/exp /usr/local/bin/exp
RUN chmod 777 /usr/local/bin/exp
RUN pip install --upgrade pip setuptools && \
    pip install -r /tmp/requirements_simple.txt
RUN jupyter server extension disable nbclassic

mlflow

ベストプラクティスには、プロセスごとにコンテナを分割するべきと書かれています。 jupyterlabコンテナでmlflowを起動させずに、別コンテナでmlflowを起動することにしました。

FROM continuumio/miniconda:4.5.4
MAINTAINER mamamaJohn
USER root
WORKDIR /home

RUN pip install --upgrade pip && \
    pip install mlflow

streamlit

mysqlのデータを可視化できるように ORM ライブラリの dataset をインストールしています。 /struns/main.py でアプリを立ち上げます。

FROM python:3.8
MAINTAINER mamamaJohn
USER root
WORKDIR /usr/src/app

RUN pip install --upgrade pip setuptools && \
    pip install streamlit pandas numpy matplotlib dataset mysqlclient

導入手順

  1. github から Development をクローンします。
git clone https://github.com/mamama039356/Development.git
  1. docker-compose.yml でコンテナを生成します。
cd Development
docker compose up -d
  1. projects 下で exp を入力して repo_nameで "project/expname" を入力します。2回目以降は project 下で exp を入力します。
  2. data/raw にデータを配置します。
  3. src/ にスクリプトを配置します。
  4. notebook に参考となるノートブックを配置します。

localhost:8080でjupyterlab, localhost:5000でmlflow, localhost:8501でstreamlitに繋がります。