M1/M2 Macで機械学習の環境を構築する

TensorflowとPytorchがApple Siliconに対応したため、Pythonの機械学習・ディープラーニング環境を構築します。

仮想環境の作成

仮想環境を作成します。

使用するPythonのバージョンですが、このあとインストールするTensorflowが執筆時点では3.7から3.10をサポートしており、Pytorchが3.7以上をサポートしているため、3.10を使用することにします。

conda create -n datascience python=3.10
conda activate datascience

ここでは仮想環境の名前をdatascienceにしていますが、任意の名前で構いません。

機械学習関連

機械学習関連のパッケージをインストールします。

機械学習でよく使われるパッケージをインストールします。

conda install numpy scipy pandas scikit-learn

代表的な可視化パッケージをインストールします。

conda install matplotlib seaborn plotly

EDAパッケージをインストールします。

conda install -c conda-forge pandas-profiling autoviz sweetviz

ローコード機械学習をインストールします。

conda install -c conda-forge pycaret

Jupyter notebookをインストールします。

conda install notebook

スクレイピング関連

スクレイピングでよく使用されるパッケージをインストールします。

conda install -c conda-forge requests scrapy beautifulsoup4

画像処理関連

画像処理でよく使用されるパッケージをインストールします。

conda install -c conda-forge pillow opencv

ディープラーニング関連

TensorflowとPytorchをインストールします。

Tensorflow

Xcode Command Line Toolをインストールしていない場合は以下のコマンドでインストールします。 

xcode-select --install

Tensorflow dependenciesをインストールします。

conda install -c apple tensorflow-deps

Tensorflowをインストールします。

pip install tensorflow-macos

Metalプラグインをインストールします。

pip install tensorflow-metal

インストール後にGPUが使用可能か確認しましょう。

import sys
import tensorflow.keras
import pandas as pd
import sklearn as sk
import scipy as sp
import tensorflow as tf
import platform

gpu = len(tf.config.list_physical_devices('GPU'))>0
print("GPU is", "available" if gpu else "not available")

以下のように表示されればGPUが使用可能です。

GPU is available

Pytorch

PytorchはTensorflowに比べるとインストールは簡単です。以下のコマンドでインストールします。

pip install --pre torch torchvision --extra-index-url https://download.pytorch.org/whl/nightly/cpu

インストール後にGPUが使用可能か確認しましょう。

import torch

gpu = torch.backends.mps.is_available()
print("GPU is", "available" if gpu else "not available")

以下のように表示されればGPUが使用可能です。

GPU is available

今後の課題

現時点で形態素解析パッケージであるMecabのインストールに失敗しています。手順もいくつか示されていますが、実際に試したところいずれもうまくいっていません(インストール自体に失敗する、インストール後に動作しない)。

こちらは引き続きインストール方法を調査し、判明次第本記事を更新したいと思います。

Apple Silicon macOSでディープラーニングの環境を構築する(Miniforge使用、Tensorflow、Tensorflow addons導入)

Apple Silicon(M1、M1Max) macOSでディープラーニングの環境を構築する方法について解説します。

本手順は2022/4/1時点のものです。現状ではHomebrewやPyenv等でインストールしたPythonではTensorflowを導入できないようです。この状況も今後変わってくる可能性があります。

Miniforgeのインストール

現状ではMiniforgeを使うのが最も楽な手順のようです。

MiniforgeのGithubサイトからApple Silicon用のインストーラをダウンロードしてください。ファイル名はMiniforge3-MacOSX-arm64.shとなっています。

迷うところはありませんが、念のため応答する箇所について掲載しておきます。

$ bash ~/Downloads/Miniforge3-MacOSX-arm64.sh                                                                                                                                                           

Welcome to Miniforge3 4.12.0-0

In order to continue the installation process, please review the license
agreement.
Please, press ENTER to continue
>>> ← ENTERを押下
Miniforge installer code uses BSD-3-Clause license as stated below.

 ・・・

Do you accept the license terms? [yes|no]
[no] >>> yes ← yesを入力してENTERを押下

Miniforge3 will now be installed into this location:
/Users/t0k0sh1/miniforge3

  - Press ENTER to confirm the location
  - Press CTRL-C to abort the installation
  - Or specify a different location below

[/Users/t0k0sh1/miniforge3] >>> ← ENTERを押下
PREFIX=/Users/t0k0sh1/miniforge3
Unpacking payload ...

 ・・・

Do you wish the installer to initialize Miniforge3
by running conda init? [yes|no]
[no] >>> no ← noを入力してENTERを押下

You have chosen to not have conda modify your shell scripts at all.
To activate conda's base environment in your current shell session:

eval "$(/Users/t0k0sh1/miniforge3/bin/conda shell.YOUR_SHELL_NAME hook)"

To install conda's shell functions for easier access, first activate, then:

conda init

If you'd prefer that conda's base environment not be activated on startup,
   set the auto_activate_base parameter to false:

conda config --set auto_activate_base false

Thank you for installing Miniforge3!

インストールが完了すると、以下のようなメッセージが表示されています。このうち、YOUR_SHELL_NAME部分を書き換えてシェルの設定ファイルに追記します。

eval "$(/Users/t0k0sh1/miniforge3/bin/conda shell.YOUR_SHELL_NAME hook)"

macOSのデフォルトシェルはzshですので、以下のように書き換えて、.zshrcに追記してください。

eval "$(/Users/t0k0sh1/miniforge3/bin/conda shell.zsh hook)"

conda環境の設定を行う

次にconda環境の設定を行います。

conda環境の自動有効化をOFFにする

前述の作業が完了し、再度シェルにログインすると、自動でconda環境が有効になります。これでも問題ない方は以下の設定変更を行う必要はありませんが、そうでない方はconda環境が自動で有効にならないように設定変更をしてください。

$ conda config --set auto_activate_base false

一度conda環境を無効化しておきます。

$ conda deactivate

Tensorflowをインストールする環境を作成する

Tensorflowをインストールする環境を作成しましょう。

執筆時点では、TensorFlow 2.8.0が最新なため、これをインストールします。Tensorflow 2.8.0ではPython 3.9に対応しているため、これを使用します。

$ conda create --name tensorflow28 python=3.9

環境名はなんでも構いませんが、ここではtensorflow28としています。

$ conda create --name tensorflow28 python=3.9                                                                                                                                                           
Collecting package metadata (current_repodata.json): done
Solving environment: done

 ・・・

#
# To activate this environment, use
#
#     $ conda activate tensorflow28
#
# To deactivate an active environment, use
#
#     $ conda deactivate

作成した環境を有効化します。

$ conda activate tensorflow28

以下の手順は作成した環境が有効化されていることを前提に進めます。

Tensorflowをインストールする

作成・有効化した環境にTensorflowをインストールします。

Numpy、OpenCV、Matplotlibをインストールする

Tensorflowをインストールする前にNumpy、OpenCV、Matplotlibをインストールしておいた方がよいようなので、先にconda installコマンドでインストールします。

$ conda install numpy opencv matplotlib

Tensorflowをインストールする

Tensorflowを以下の順番でインストールします。

$ conda install -c apple tensorflow-deps
$ python -m pip install tensorflow-macos
$ python -m pip install tensorflow-metal

Tensorflow addonsのビルド・インストールする

Tensorflowの拡張ライブラリであるTensorflow addonsを使用するためには、Apple Siliconではソースコードからビルドしてインストール必要があります(conda installpip installではうまくいかない)。

ビルドにはbazelが必要なため、まずはこれをインストールします。

$ conda install bazel

次にwheelsetuptoolsが最新でないとビルドに失敗するという報告もあるため、最新化しておきます。

$ python -m pip install --upgrade wheel setuptools

準備が整いましたので、Tensorflow addonsをダウンロードし、ビルドします。

$ git clone https://github.com/tensorflow/addons.git
$ cd addons
$ python ./configure.py
$ bazel build build_pip_pkg
$ bazel-bin/build_pip_pkg artifacts

ビルドが完了すると、artifactsディレクトリの下にファイルが作成されます。

$ ls ./artifacts                                                                                                                                                                                          
tensorflow_addons-0.17.0.dev0-cp39-cp39-macosx_11_0_arm64.whl

作成されていることが確認できましたら、これをインストールします。

$ python -m pip install ./artifacts/tensorflow_addons-0.17.0.dev0-cp39-cp39-macosx_11_0_arm64.whl

これで、TensorflowおよびTensorflow addonsのインストールが完了となります。

参考文献

本手順は以下を参考に作成しました。

[NumPy]正規化(Normalization)を行う

正規化とはデータを扱いやすいスケールに変更する手法です。ここでは代表的なMin-Max normalization、Z-score normalizationについて解説します。

  • Min-Max normalization
  • Z-score normalization

Min-Max normalization

正規化というとMin-Max normalizationを指すといわれることもありますが、正直そこまで気にする必要はありません。どちらかというと、正規化を行うときにデータの性質に応じて適切な手法を選択できる方が重要です。

Min-Max normalizationとは

Min-Max normalizationとは、データを最小値0、最大値1にスケールする手法です。

データ$x_i$をMin-Max normalizationで正規化した$x^{\prime}_i$は以下の式で求めます。

$$ x^{\prime}_i = \frac{x_i – min(x)}{max(x) – min(x)} \quad (i = 1, 2, \cdots , n) $$

Min-Max normalizationは外れ値に弱いという特徴があります。最小値と最大値がはっきりしている場合に適した手法です。外れ値が含まれる場合は後述のZ-score normalizationを使用する方が適しています。

NumPyによる実装

では、実際にNumPyで実装してみましょう。

まずは、正規化前の5×5の行列を作成します。再現しやすいように乱数のシードは固定しておきます。

np.random.seed(123)
x = np.random.random((5, 5))
print(x)
[[0.69646919 0.28613933 0.22685145 0.55131477 0.71946897]
 [0.42310646 0.9807642  0.68482974 0.4809319  0.39211752]
 [0.34317802 0.72904971 0.43857224 0.0596779  0.39804426]
 [0.73799541 0.18249173 0.17545176 0.53155137 0.53182759]
 [0.63440096 0.84943179 0.72445532 0.61102351 0.72244338]]

正規化前後でどう変わったかわかりやすいように最小値、最大値、平均値、標準偏差を求めておきます。

print('最小値 :%f' % np.min(x))
print('最大値 :%f' % np.max(x))
print('平均値 :%f' % np.mean(x))
print('標準偏差:%f' % np.std(x))
最小値 :0.059678
最大値 :0.980764
平均値 :0.524464
標準偏差:0.226296

式の定義どおりに実装します。np.min関数で最小値、np.max関数で最大値を求めています。

x_norm = (x - np.min(x)) / (np.max(x) - np.min(x))
print(x_norm)
[[0.02615693 0.72523256 0.32482616 0.41610292 0.11393134]
 [[0.69134813 0.24586343 0.18149608 0.53375766 0.71631841]
 [0.39456516 1.         0.67871147 0.45734477 0.36092125]
 [0.30778888 0.72671997 0.41135597 0.         0.36735576]
 [0.73643209 0.13333586 0.12569274 0.51230105 0.51260093]
 [0.62396223 0.85741574 0.72173197 0.59858193 0.71954765]]

ぱっと見で最小値0、最大値1であることはわかりますが、念のため先ほどと同じように最小値、最大値、平均値、標準偏差を確認しましょう。

print('最小値 :%f' % np.min(x_norm))
print('最大値 :%f' % np.max(x_norm))
print('平均値 :%f' % np.mean(x_norm))
print('標準偏差:%f' % np.std(x_norm))
最小値 :0.000000
最大値 :1.000000
平均値 :0.504606
標準偏差:0.245684

最小値0、最大値1であることが確認できます。加えて、若干値が違いますが、平均値と標準偏差はほとんど変わっていないことがわかります。

最小値0、最大値1にスケールする仕組みを理解する

Z-score normalizationは平均0、標準偏差1にスケールする手法になります。

わかりやすいデータを使って計算過程をひとつずつ確認して、最小値0、最大値1の範囲にスケールする仕組みを確認していきましょう。

まずは、以下のようなデータを用意します。10、20、・・・、50と10ずつ増える5個のデータです。

x = np.array([10, 20, 30, 40, 50])
print(x)
[10 20 30 40 50]

まずは分子から確認します。データから最小値を引きます。最小値は10なので、各要素から引くと以下のようになります。

print(x - np.min(x))
[ 0 10 20 30 40]

次に分母を見てみます。最大値50から最小値10を引くので、40になります。

print(np.max(x) - np.min(x))
40

すでに計算した分子の最大値は40で、分母と同じ値ですので、割り算の結果は1になります。一方で、最小値は0ですので、割り算の結果は0になります。それ以外の値は最小値と最大値の間に収まるため、計算結果はすべて0以上1以下になります。

print((x - np.min(x)) / (np.max(x) - np.min(x)))
[0.   0.25 0.5  0.75 1.  ]

Z-score normalization

次にZ-score normalizationについて確認します。標準化(Standardization)とも呼ばれることもありますが、こちらもそれほど気にする必要はありません。

Z-score normalizationとは

データ$x_i$をZ-score normalizationで正規化した$x^{\prime}_i$は以下の式で求めます。

$$ x^{\prime}_i = \frac{x_i – \bar{x}}{\sigma} \quad (i = 1, 2, \cdots , n) $$

NumPyによる実装

正規化前のデータおよび最小値、最大値、平均値、標準偏差を再掲しておきます。

np.random.seed(123)
x = np.random.random((5, 5))
print(x)
[[0.69646919 0.28613933 0.22685145 0.55131477 0.71946897]
 [0.42310646 0.9807642  0.68482974 0.4809319  0.39211752]
 [0.34317802 0.72904971 0.43857224 0.0596779  0.39804426]
 [0.73799541 0.18249173 0.17545176 0.53155137 0.53182759]
 [0.63440096 0.84943179 0.72445532 0.61102351 0.72244338]]
print('最小値 :%f' % np.min(x))
print('最大値 :%f' % np.max(x))
print('平均値 :%f' % np.mean(x))
print('標準偏差:%f' % np.std(x))
最小値 :0.059678
最大値 :0.980764
平均値 :0.524464
標準偏差:0.226296

こちらも同様に式どおりに実装します。np.mean関数が平均(算術平均)、np.std関数が標準偏差を求める関数です。

x_norm = (x - np.mean(x)) / np.std(x)
print(x_norm)
[[ 0.76008999 -1.05315055 -1.31514268  0.11865512  0.86172564]
 [-0.44789519  2.01638476  0.70865548 -0.19236556 -0.58483479]
 [-0.8010976   0.90406276 -0.37955215 -2.05387975 -0.55864464]
 [ 0.94359364 -1.51116753 -1.54227706  0.03132102  0.0325416 ]
 [ 0.48581156  1.43602912  0.88376026  0.38250702  0.87486952]]

正規化後の最小値、最大値、平均値、標準偏差を見てみましょう。

print('最小値 :%f' % np.min(x_norm))
print('最大値 :%f' % np.max(x_norm))
print('平均値 :%f' % np.mean(x_norm))
print('標準偏差:%f' % np.std(x_norm))
最小値 :-2.053880
最大値 :2.016385
平均値 :-0.000000
標準偏差:1.000000

平均値0、標準偏差1にスケールされていることがわかります。また、最小値と最大値も変わっていることにも注意してください。

平均値0、標準偏差1にスケールする仕組みを理解する

Z-scoreについても平均値0、標準偏差1にスケール仕組みをひとつずつ確認していきましょう。

データは先ほどと同じデータを使います。

x = np.array([10, 20, 30, 40, 50])
print(x)
[10 20 30 40 50]

このデータの平均は30、標準偏差は$\sqrt{200}$になります。

print('平均値 :%f' % np.mean(x))
print('標準偏差:%f' % np.std(x))
平均値 :30.000000
標準偏差:14.142136

まずは分子から見ていきます。平均値30を引くので、分子の平均値は0になります。同様に標準偏差求めると$\sqrt{200}$になっています。

print('平均値 :%f' % (x - np.mean(x)).mean())
print('標準偏差:%f' % (x - np.mean(x)).std())
平均値 :0.000000
標準偏差:14.142136

分母は分子の標準偏差と同じ$\sqrt{200}$でしたので、割った結果の標準偏差は1になります。

print((x - np.mean(x)) / np.std(x))
[-1.41421356 -0.70710678  0.          0.70710678  1.41421356]

実際に平均値と標準偏差を求めます。

print('平均値 :%f' % np.mean((x - np.mean(x)) / np.std(x)))
print('標準偏差:%f' % np.std((x - np.mean(x)) / np.std(x)))
平均値 :0.000000
標準偏差:1.000000

外れ値に弱いとはどういうことか

Min-Max normalizationは外れ値に弱いという説明をしました。これがどういうことか、Z-score normalizationなら大丈夫なのかについて具体的な例を使って確認します。

以下のような具体例を用意します。

1,000人分の身長データを使って正規化を行います。身長データは正規分布に従いますが、990件は正常データで10件は外れ値であるとします。
この外れ値は100の位の1が読み取れず欠損したデータ(180cmが80cmになってしまった)が10件あるとします。

まずは必要なパッケージをインポートします。

import numpy as np
import matplotlib.pyplot as plt
%matplotlib inline

正常データを作成する

まずは正常データ990件を用意します。それっぽい身長データにするために、平均値170、標準偏差10の正規分布に従う乱数を生成します。

np.random.seed(123)
x = np.random.normal(loc=170, scale=10, size=990)

これをヒストグラムで描画してみます。

plt.hist(x, bins=100)
plt.show()

numpy.random.normal関数を使って作成したデータですので正規分布に従っています。

ではこのデータをMin-Max normalization、Z-score normalizationで正規化し、同様にグラフを出力してみましょう。

正常データをMin-Max normalizationで正規化する

まずはMin-Max normalizationを使って正規化します。

x_norm = (x - np.min(x)) / (np.max(x) - np.min(x))
plt.hist(x_norm, bins=100)
plt.show()

最小値0、最大値1にスケールしただけで分布は変わっていません。

正常データをZ-score normalizationで正規化する

次にZ-score normalizationを使って正規化してみます。

x_norm = (x - np.mean(x)) / np.std(x)
plt.hist(x_norm, bins=100)
plt.show()

こちらも中心が0になっているだけで、分布は変わっていません。

外れ値を生成して正常データに加える

では、外れ値を10件生成し、それを正常データに加えていきます。

e1 = np.random.normal(loc=170, scale=10, size=10)-100
print(e1)
[60.91733437 76.55533937 72.40244965 63.547099   73.60026527 51.22368305
 72.34040489 76.24323922 78.11939825 49.77843013]

外れ値は単純に100を引いた値を使います。

このデータを正常値に加え、念のためシャッフルしておきます。

x2 = np.concatenate([x, e1])
np.random.shuffle(x2)
plt.hist(x2, bins=100)
plt.show()

外れ値は小さい値を用意したため、グラフは右に寄った形になっています。

では、これをMin-Max normalization、Z-score normalizationで正規化してみます。

外れ値を含むデータをMin-Max normalizationで正規化する

先ほどと同様の手順で正規化してグラフを出力します。

x2_norm = (x2 - np.min(x2)) / (np.max(x2) - np.min(x2))
plt.hist(x2_norm, bins=100)
plt.show()

外れ値を含まない場合は0.2から0.8くらいの範囲にデータが分布していましたが、外れ値を含むと0.6から0.9くらいの範囲に分布していることがわかります。
もともとは0.5くらいが中心でしたが、0.7から0.8あたりに中心が来ており、データが偏っていることになります。

これが外れ値に弱い(外れ値に敏感ともいう)ということになります。

外れ値を含むデータをZ-score normalizationで正規化する

では、Z-score normalizationだとどうなるでしょうか。

x2_norm = (x2 - np.mean(x2)) / np.std(x2)
plt.hist(x2_norm, bins=100)
plt.show()

こちらもグラフ自体は偏っていますが、外れ値を含まない場合も外れ値を含む場合も-2から2の範囲に分布していることがわかります。
外れ値を含んでいますが、正規化後の分布はほとんど変わっていない、すなわち外れ値に強いということがいえます。

このことから外れ値を含む場合や外れ値を除外できない場合は、Min-Max normalizationよりもZ-score normalizationの方が適しているといえます。
ただし、外れ値のデータを除外したり、外れ値を修正・補完して、想定される最小値・最大値の範囲できるのであれば、Min-Max normalizationを使用して正規化することも可能であることも覚えておいてください。

[NumPy]単位行列を作成する(eye関数、identity関数)

NumPyで単位行列(identity matrix)を作成する2つの方法について解説します。

NumPyには、単位行列を作成する方法が2つ用意されています。

  • eye関数(numpy.eye) – N×Mの単位行列を作成する
  • identity関数(numpy.identity) – 正方行列の単位行列を作成する

N×Mの単位行列を作成するeye関数

単位行列を作成する関数としてよく見かけるのがeye関数です。以下のように書くことで単位行列を作成することができます。

# 3x3の単位行列を作成する
E = np.eye(3)
print(E)

この例では引数に3を指定することで、3×3の単位行列を作成していますが、引数をもう一つ指定することで、正方行列以外の単位行列を作ることもできます。

以下の例では引数に3と4を指定することで、3×4の単位行列を作成しています。

# 3x4の単位行列を作成する
E = np.eye(3, 4)
print(E)
[[1. 0. 0. 0.]
 [0. 1. 0. 0.]
 [0. 0. 1. 0.]]

正方行列の単位行列を作成するidentity関数

eye関数に比べると知名度はありませんが、正方行列の単位行列を作成することができる、identity関数も用意されています。

先ほどと同様に3×3の単位行列を作成する場合は、以下のように書きます。

# 3x3の単位行列を作成する
E = np.identity(3)
print(E)
[[1. 0. 0.]
 [0. 1. 0.]
 [0. 0. 1.]]

どのように使い分けるのか

結論からいうと、使い分ける必要はありません。これまでどおりeye関数だけを使用していただいても、eye関数とidentity関数を使い分けても構いません。とりあえず、2つの方法があることだけは覚えておいてください。

モバイルバージョンを終了