🗺️

DockerでJupyterを起動させ機械学習を始める

2022/05/27に公開約12,600字

この記事は、機械学習について何から始めていいか全くわからんという方を対象にしています。

Jupyterとは

ブラウザー上で使用できるPythonの実行環境です。
localhost:8888に表示される画面にコードを入力し、Runを押すとコードが実行され結果が出力されます。

さまざまなモジュールをインストールして実行できる上に、そのコードを個別に保存できると言う機能は単純ながら想像以上に便利です。

またDocker上での起動を前提とすることで、自分のデータの分析検証結果(方法)などを誰にでも気軽に配布できる事が可能となるので、技術や数学に好奇心が強い人にとっては中々野心溢れるものがあるのではないでしょうか。

構成

  • jupyter-project
    • .docker
      • jupyter
        • Dockerfile
    • jupyter
      • requirements.txt
    • docker-compose.yml

モジュール

公式を見ると、Jupyterのインストール方法はpipで簡単にできることが分かります。
必要なモジュールをrequirements.txtにまとめてDockerビルド時にまとめてインストールできる様にします。

requirements.txt
jupyterlab
notebook
voila

また機械学習で有名な4つのモジュール(numpy, pandas, matplotlib, scikit-learn)も追加します。

requirements.txt
jupyterlab
notebook
voila
numpy
pandas
matplotlib
scikit-learn

Docker

上記インストールも踏まえてpython実行環境をDockerfileに記述します。

Dockerfile
FROM python:3.10

WORKDIR /home/jovyan

COPY ./jupyter/requirements.txt .

RUN pip install --no-cache-dir --upgrade pip && \
    pip install --no-cache-dir -r requirements.txt

/home/jovyanとはJupyterのワークスペースです。
作業内でコードを保存するとここにファイルが出来ます。

もちろんコンテナ内にできるので、Dockerを停止すると消滅してしまいます。
なので以下docker-compose.ymlでローカルディレクトリにマウントさせ永続できる様にします。

docker-compose.yml
version: "3"

services:
  jupyter:
    build:
      context: .
      dockerfile: ./.docker/jupyter/Dockerfile
    volumes:
      - ./jupyter:/app
    tty: true
    ports:
      - 8888:8888
    volumes:
      - ./jupyter:/home/jovyan
    networks:
      - jupyter_network

networks:
  jupyter_network:
    driver: bridge

起動

プロジェクトディレクトリに入り
以下のコマンドでDockerビルドを実行します。

cd jupyter-project
docker-compose build

ビルドによってイメージが出来たらコンテナとして起動します。

docker-compose up -d && docker-compose ps

起動出来たら次のコマンドでJupyterのパスワードを設定します。

docker-compose exec jupyter jupyter notebook password

パスワードを設定したら、Jupyterを起動します。

docker-compose exec jupyter jupyter notebook --port=8888 --ip=0.0.0.0 --allow-root

http://localhost:8888/を参照すると、ブラウザー上にJupyterのパスワード入力画面が表示されます。

機械学習を始める

今現在信じられないような激動となっているアメリカ経済ですが
その平均賃金は日本の2倍にも及ぶと言われています。
その平均賃金(年収)がこれからどうなるのか
線形回帰と言う学習モデルで2030年ごろを予想していきたいと思います。

データ

こちらのサイトのCSVデータを参考します。
アメリカだけじゃなく日本も含めて先進国各国も掲載されています。
日本語で記事を書くなら日本を対象にすべきでは?とも思いしたが
あまりに20年前から横ばいで全く面白味がなさそうなのでまずアメリカを予測します。

https://stats.oecd.org/index.aspx?DataSetCode=AV_AN_WAGE

データの前処理(pandas)

データの前処理とは、機械学習のために必要な情報だけを抜き取り学習にフィットするようにデータを整理整頓することです。

まずCSVを読み込みアメリカのみのデータを取得します。
(dfとはデータフレームの略で、この中に読み込んだCSVデータがカラム名も含めて丸々入っています。)

import pandas as pd

# CSVを読み込む
df = pd.read_csv('csv/AV_AN_WAGE.csv')

# アメリカのみを取得する
country = 'United States'
df = df[(df.Country == country)]
df

必要なデータのみを取得し、新しいカラムに置き換える

現在CSVを読み込みアメリカのみのデータを獲得したと思いますが
中身を見ると読み込み元のCSVデータと同じ様に13のカラムで構成されていると思います。

"COUNTRY","Country","SERIES","Series","TIME","Time","Unit Code","Unit","PowerCode Code","PowerCode","Reference Period Code","Reference Period","Value","Flag Codes","Flags"

この中から今回の機械学習で使うSERIES、Time、Valueカラムのみを取得します。
pandasのおかげで以下の一行で実行できます。
JupyterでRunすればdf内のカラムは最初の13から3つになったのが確認できるかと思います。

df = df[['SERIES', 'Time', 'Value']]
df

次に少しややこしいのですが
Valueのカラム値をSERIESのカラム値ごとに配列として分け
それを新しいカラムとしてdfに設定し直します。

SERIESの値は以下の3つです。

  • USDPPP
    通貨価値の減少を考慮した自国の平均賃金のドル換算
  • CNPNCU
    通貨価値の減少を考慮した自国の平均賃金
  • CPNCU
    現在の価値で換算する自国の平均賃金

以下で、各SERIESの時のValueカラム値を配列として定義します。

## 各SERIESのValue値を配列として定義する
USDPPP = df.loc[df['SERIES'] == 'USDPPP', 'Value']
CNPNCU = df.loc[df['SERIES'] == 'CNPNCU', 'Value'].astype(int)
CPNCU = df.loc[df['SERIES'] == 'CPNCU', 'Value'].astype(int)

そして取り出した配列を新カラムとしてdfに格納していきます。

df['USDPPP'] = USDPPP
df['CNPNCU'] = CNPNCU
df['CPNCU'] = CPNCU

この時点でSERIESとValueの値は新しいカラム名とその値として格納されたのでdfから削除します。

df = df.drop(columns=['SERIES', 'Value'])
df

現在、SERIESとValueが削除され必要なカラムのみとなっているのですが
各SERIES名ごとにTimeデータが重複してしまっています。

    Time        USDPPP   CNPNCU    CPNCU
525   2000           NaN      NaN  38968.0
526   2001           NaN      NaN  40052.0
527   2002           NaN      NaN  40900.0
528   2003           NaN      NaN  42206.0
529   2004           NaN      NaN  44062.0
530   2005           NaN      NaN  45369.0
531   2006           NaN      NaN  47343.0
532   2007           NaN      NaN  49510.0
533   2008           NaN      NaN  50824.0
534   2009           NaN      NaN  51188.0
535   2010           NaN      NaN  52561.0
536   2011           NaN      NaN  53967.0
537   2012           NaN      NaN  55448.0
538   2013           NaN      NaN  55932.0
539   2014           NaN      NaN  57599.0
540   2015           NaN      NaN  59184.0
541   2016           NaN      NaN  59894.0
542   2017           NaN      NaN  61611.0
543   2018           NaN      NaN  63588.0
544   2019           NaN      NaN  65603.0
545   2020           NaN      NaN  69391.0
1134  2000           NaN  55365.0      NaN
1135  2001           NaN  55834.0      NaN
1136  2002           NaN  56274.0      NaN
1137  2003           NaN  56965.0      NaN
1138  2004           NaN  58023.0      NaN
1139  2005           NaN  58092.0      NaN
1140  2006           NaN  59013.0      NaN
1141  2007           NaN  60187.0      NaN
1142  2008           NaN  59986.0      NaN
1143  2009           NaN  60471.0      NaN
1144  2010           NaN  61047.0      NaN
1145  2011           NaN  61131.0      NaN
1146  2012           NaN  61634.0      NaN
1147  2013           NaN  61346.0      NaN
1148  2014           NaN  62263.0      NaN
1149  2015           NaN  63844.0      NaN
1150  2016           NaN  63941.0      NaN
1151  2017           NaN  64618.0      NaN
1152  2018           NaN  65302.0      NaN
1153  2019           NaN  66382.0      NaN
1154  2020           NaN  69391.0      NaN
1743  2000  55365.702595      NaN      NaN
1744  2001  55834.599266      NaN      NaN
1745  2002  56274.659366      NaN      NaN
1746  2003  56965.563722      NaN      NaN
1747  2004  58023.429937      NaN      NaN
1748  2005  58092.351673      NaN      NaN
1749  2006  59013.859879      NaN      NaN
1750  2007  60187.984745      NaN      NaN
1751  2008  59986.265888      NaN      NaN
1752  2009  60471.108510      NaN      NaN
1753  2010  61047.874227      NaN      NaN
1754  2011  61131.680004      NaN      NaN
1755  2012  61634.201032      NaN      NaN
1756  2013  61346.909406      NaN      NaN
1757  2014  62263.410358      NaN      NaN
1758  2015  63844.555768      NaN      NaN
1759  2016  63941.857045      NaN      NaN
1760  2017  64618.452265      NaN      NaN
1761  2018  65302.940142      NaN      NaN
1762  2019  66382.505552      NaN      NaN
1763  2020  69391.806422      NaN      NaN

これではよくないので
以下のコードで同じTime行に収まるように各SERIESのカラム値を縦に移動させます。

# 最初の2000年から2020年に収まる様に上にずらす
df['CNPNCU'] = df['CNPNCU'].shift(-21)
df['USDPPP'] = df['USDPPP'].shift(-42)
df = df[df['CPNCU'].notna()]

移動後に残ったNaNデータを削除します。
そして2000から2020年までのデータのIndexを0から指定し直します。

df = df[df['CPNCU'].notna()]
df = df.reset_index(drop=True)
df
    Time        USDPPP   CNPNCU    CPNCU
0   2000  55365.702595  55365.0  38968.0
1   2001  55834.599266  55834.0  40052.0
2   2002  56274.659366  56274.0  40900.0
3   2003  56965.563722  56965.0  42206.0
4   2004  58023.429937  58023.0  44062.0
5   2005  58092.351673  58092.0  45369.0
6   2006  59013.859879  59013.0  47343.0
7   2007  60187.984745  60187.0  49510.0
8   2008  59986.265888  59986.0  50824.0
9   2009  60471.108510  60471.0  51188.0
10  2010  61047.874227  61047.0  52561.0
11  2011  61131.680004  61131.0  53967.0
12  2012  61634.201032  61634.0  55448.0
13  2013  61346.909406  61346.0  55932.0
14  2014  62263.410358  62263.0  57599.0
15  2015  63844.555768  63844.0  59184.0
16  2016  63941.857045  63941.0  59894.0
17  2017  64618.452265  64618.0  61611.0
18  2018  65302.940142  65302.0  63588.0
19  2019  66382.505552  66382.0  65603.0
20  2020  69391.806422  69391.0  69391.0

これで2021年から2030年を予測するための
2000年から2020年のデータを獲得することができました。

機械学習

機械学習で予測を行う場合
まず説明変数(X)とその答えをもつ目的変数(y)の組み合わせたパターンを学習させる必要があります。
上記データで言うと今回は未来の平均賃金を求めるわけですから
目的変数(y)はCPNCUとなります。
そして残りのTime、USDPPP、CNPNCUは説明変数(X)となります。

ただ時系列的に未来の予測を行う場合は、説明変数(X)も未来の値である必要があります。
なので2021年から2030年の平均賃金(CPNCU)を求めるには
2021年から2030年の説明変数(X['Time', 'USDPPP', 'CNPNCU'])を取得する必要が出てきます。

ここで誰もが予測できるものは何かと考えた時に、それは時間だということが言えるかと言えます。
時間だけは2020年の次の年は2021年だと誰もが予測できます。
なので時間だけをまず説明変数(X)とすることで未来のUSDPPPもしくはCNPNCUを予測し取得していきます。

以下のように多次元配列で未来の時間を定義します。

from sklearn.linear_model import LinearRegression

# from 2021 to 2030
years = [[2021], [2022], [2023], [2024], [2025], [2026], [2027], [2028], [2029], [2030]]

未来のUSDPPPの取得

以下のコードで未来のUSDPPPを取得します。
2000年から2020年までの過去データを説明変数(X)と目的変数(y)に分割して学習させ
その後2021年から2030年までの時間(years)を予測メソッドに代入して予測値を獲得します。

# 説明変数
X = df[['Time']].values 
# 目的変数
y = df['USDPPP'].values
# 線形回帰モデルを定義
model = LinearRegression()
# 学習
model.fit(X, y)
# 予測
predicted_USDPPP = model.predict(years)

未来のCNPNCUの取得

上記と同じように、次はCNPNCUを求めます。
USDPPPはCNPNCUのアメリカドルへの換算なので
CNPNCUを取得してもアメリカの場合は上下幅など特にあまり意味がない感じですが
数少ない材料の一つなので予測し取得します。

# 説明変数
X = df[['Time']].values 
# 目的変数
y = df['CNPNCU'].values
# 線形回帰モデルを定義
model = LinearRegression()
# 学習
model.fit(X, y)
# 予測
predicted_CNPNCU = model.predict(years)

これで未来のUSDPPPとCNPNCUが取得できました。
この二つの未来データを説明変数として合わせます。

# 2021年から2030年の10年をデータフレームとして定義
future = pd.DataFrame(years, columns=['Time'])
# 2021年から2030年のUSDPPPを代入
future['USDPPP'] = predicted_USDPPP
# 2021年から2030年のCNPNCUを代入
future['CNPNCU'] = predicted_CNPNCU
future
   Time        USDPPP        CNPNCU
0  2021  67372.819824  67372.161905
1  2022  67951.640164  67950.977489
2  2023  68530.460503  68529.793074
3  2024  69109.280843  69108.608658
4  2025  69688.101183  69687.424242
5  2026  70266.921523  70266.239827
6  2027  70845.741863  70845.055411
7  2028  71424.562202  71423.870996
8  2029  72003.382542  72002.686580
9  2030  72582.202882  72581.502165

これで学習後の未来予測に必要なデータ(future)ができました。
これで未来の平均賃金(CPNCU)が予測できます。

未来のCPNCUの取得

# 過去の説明変数(X)と目的変数(y)を再定義
X = df[['Time', 'USDPPP', 'CNPNCU']]
y = df['CPNCU']
# 線形回帰モデルを定義
model = LinearRegression()
# 学習
model.fit(X, y)
# 予測
predicted_CPNCU = model.predict(future)
# 予測をfutureに合わせる
future['CPNCU'] = predicted_CPNCU
future
   Time        USDPPP        CNPNCU         CPNCU
0  2021  67372.819824  67372.161905  68170.171429
1  2022  67951.640164  67950.977489  69583.044156
2  2023  68530.460503  68529.793074  70995.916883
3  2024  69109.280843  69108.608658  72408.789610
4  2025  69688.101183  69687.424242  73821.662338
5  2026  70266.921523  70266.239827  75234.535065
6  2027  70845.741863  70845.055411  76647.407792
7  2028  71424.562202  71423.870996  78060.280519
8  2029  72003.382542  72002.686580  79473.153247
9  2030  72582.202882  72581.502165  80886.025974

以上でアメリカの2030年ごろの平均賃金の予測値を獲得できたかと思います。

$80886

2020年から$10000ぐらい上がってますね。
線形回帰の直線で10年も先を見ているので
実際どうなるのかは分かりませんがS&P500に積み立てを行う人が多くいるのが頷けます。

データの可視化

最後にmatplotlib(グラフ可視化モジュール)でグラフ予測結果を表示したいと思います。
上記コードから現在以下のようなデータがあるかと思います。

  • df
    2000年から2020までのデータ(['Time', 'USDPPP', 'CNPNCU', 'CPNCU'])
  • future
    2021年から2030までのデータ(['Time', 'USDPPP', 'CNPNCU', 'CPNCU'])

このdfとfutureの結果を分けて表示させます。

import matplotlib.pyplot as plt
from matplotlib.pyplot import figure

figure(figsize=(8, 4.5), dpi=100)
plt.title(country)
plt.xlabel('Time')
plt.ylabel('Value')
# dfのCPNCUをx1とx2に分ける
x1 = df['CPNCU'].copy(deep=True)
x2 = df['CPNCU'].copy(deep=True)
# x1の2000年から2020年までを出力させるため、2021年から2030年までにNaNを代入する
x1[(x1.index >= 21) & (x1.index <= 30)] = np.nan
# x2の2021年から2030年までを出力させるため、2000年から2020年までにNaNを代入する
x2[(x2.index >= 0) & (x2.index <= 20)] = np.nan
# 2000年から2020年までを青(ディフォルト)で表示
plt.bar(df['Time'], x1)
# 2021年から2030年までを緑(lightgreen)で表示
plt.bar(df['Time'], x2, color='lightgreen')
plt.show()

線形回帰とは直線的な予想になるので
本来は直近の未来のみを予測するのに向いていると思うのですが
今回はデータの前処理や可視化を分かりやすくするのも兼ねて
10年もの未来予測を行いました。

補足になるのですが
実は一番初めのcountryの代入値を

country = 'United States'
df = df[(df.Country == country)]

'Japan' に置き換えることで日本の予測結果も出せます。

country = 'Japan'
df = df[(df.Country == country)]

結果は。。。良くも悪くもない感じで
なんとも暇を感じながら徐々に衰退するという結果でした。

なんらかの企業革命が起きて、この線形回帰予想から外れることを強く願います。

なお、今回の記事のコードはこちらのレポジトリにまとめました。

https://github.com/heshikirihasebe/dojo/tree/feature/average-wages

個人的には未来において経済学的数値がどうなるかと言うのは
心から関心がある課題ですので、今後も何か他のモデルを試しつつ
数学も勉強して何か高度なものを簡潔にZennに投げれたらと思います。

Discussion

ログインするとコメントできます