データサイエンスや機械学習の分野において、データのスケールを整える「正規化」は欠かせない前処理のひとつです。
NumPyを利用すれば、複雑な数式を記述することなく、多次元配列に対して効率的に正規化処理を実行できます。
しかし、実際の現場では「列ごとに計算したい」「行ごとに処理を適用したい」といった具体的なニーズに応じて、多次元配列の軸(axis)を正しく操作する必要があります。
本記事では、NumPyを用いた正規化の手法について、行ごと・列ごとの計算方法の違いや、ブロードキャスト機能を活用した効率的な実装方法を詳しく解説します。
正規化(Normalization)の目的と基本的な考え方
正規化とは、データの値の範囲を特定のルールに基づいて変換し、扱いやすいスケールに揃える操作を指します。
例えば、ある特徴量が0から1,000の範囲を持ち、別の特徴量が0から1の範囲を持っている場合、そのままでは機械学習モデルが正しく学習できないことがあります。
一般的に正規化と呼ばれる処理には、値を0から1の範囲に収める最小最大スケーリング(Min-Max Scaling)や、平均を0、標準偏差を1にする標準化(Standardization)が含まれます。
NumPyを使用すると、ベクトル化演算によってこれらの計算を高速に実行できるため、大規模なデータセットでもストレスなく処理が可能です。
特に多次元配列を扱う際には、どの方向に沿って統計量を計算するかを明確に指定することが重要になります。
Min-Max Scalingによる0から1への正規化
まずは、最も一般的な手法であるMin-Max Scalingについて見ていきましょう。
この手法では、データの最小値を0、最大値を1となるように変換します。
公式は「(x – min) / (max – min)」で表されます。
列ごとに正規化を行う方法(axis=0)
機械学習のデータセットでは、通常、列が各特徴量を表し、行が個々のサンプルを表します。
そのため、「特徴量ごとにスケールを揃える」場合は、列方向(axis=0)に対して最小値と最大値を計算するのが一般的です。
import numpy as np
# サンプルデータの作成(3行3列)
data = np.array([
[10, 200, 0.5],
[50, 100, 0.1],
[30, 300, 0.9]
])
# 列ごとの最小値と最大値を計算
min_vals = data.min(axis=0)
max_vals = data.max(axis=0)
# 正規化の計算
normalized_data = (data - min_vals) / (max_vals - min_vals)
print("元のデータ:\n", data)
print("列ごとの正規化後:\n", normalized_data)
元のデータ:
[[ 10. 200. 0.5]
[ 50. 100. 0.1]
[ 30. 300. 0.9]]
列ごとの正規化後:
[[0. 0.5 0.5 ]
[1. 0. 0. ]
[0.5 1. 1. ]]
上記のコードでは、axis=0を指定することで、列ごとの統計量を算出しています。
NumPyのブロードキャスト機能により、3行3列の配列から1行3列の統計量配列を引いたり割ったりする操作が、各行に対して自動的に適用されます。
行ごとに正規化を行う方法(axis=1)
一方で、個々のサンプル内での相対的なバランスを重視する場合、行方向(axis=1)に対して正規化を行うことがあります。
例えば、1つの行が1枚の画像データや1つの波形データを表している場合などが該当します。
行ごとに計算を行う際は、ブロードキャストが正しく機能するようにkeepdims=Trueオプションを使用するのがコツです。
# 行ごとの最小値と最大値を計算(次元を維持)
min_rows = data.min(axis=1, keepdims=True)
max_rows = data.max(axis=1, keepdims=True)
# 行ごとの正規化計算
normalized_rows = (data - min_rows) / (max_rows - min_rows)
print("行ごとの正規化後:\n", normalized_rows)
行ごとの正規化後:
[[0.04761905 1. 0. ]
[0.498998 1. 0. ]
[0.09729087 1. 0. ]]
keepdims=Trueを指定しない場合、計算結果の形状が1次元配列になってしまい、元の多次元配列との間で計算エラーが発生することがあります。
次元の整合性を保つことで、意図した通りの行方向計算が可能になります。
標準化(Z-score Normalization)の実装
データの平均を0、標準偏差を1に変換する手法は、一般に「標準化」と呼ばれます。
外れ値の影響を完全に排除したくない場合や、多くの機械学習アルゴリズム(SVMや線形回帰など)を利用する際に推奨される手法です。
数式は「(x – mean) / std」となります。
列ごとの標準化
列ごとの標準化も、基本的な考え方はMin-Max Scalingと同じです。
np.mean()とnp.std()を使用します。
# 列ごとの平均と標準偏差
mean_cols = data.mean(axis=0)
std_cols = data.std(axis=0)
# 標準化の適用
standardized_data = (data - mean_cols) / std_cols
print("列ごとの標準化後:\n", standardized_data)
列ごとの標準化後:
[[-1.22474487 0. 0. ]
[ 1.22474487 -1.22474487 -1.22474487]
[ 0. 1.22474487 1.22474487]]
平均がほぼ0になり、データのばらつきが統一されていることがわかります。
axis引数とデータの形状(Shape)の理解
NumPyを使いこなす上で最も重要なのは、axis引数の意味を正確に把握することです。
以下の表は、2次元配列におけるaxisの動作をまとめたものです。
| 指定値 | 計算の方向 | 用途の例 |
|---|---|---|
axis=0 | 垂直方向(列ごと) | 特徴量ごとのスケーリング |
axis=1 | 水平方向(行ごと) | サンプルごとの正規化 |
axis=None | 全要素 | 配列全体の範囲調整 |
特にaxis=1で行ごとの処理を行う場合、NumPyの計算ルールの都合上、(n, 1)という形状を維持しなければ、各行に正しく値が分配されません。
そのため、先ほど紹介したkeepdims=Trueが非常に重要な役割を果たします。
応用:ゼロ除算を防ぐためのテクニック
実務で正規化を行う際、稀に最大値と最小値が等しい(すべての値が同じ)列や行が存在することがあります。
この場合、分母が0になり、計算結果にNaN(非数)が含まれてしまいます。
これを防ぐためには、分母に極めて小さな値(イプシロン)を加える手法が一般的です。
# ゼロ除算対策を含めた正規化
eps = 1e-8
diff = max_vals - min_vals
# 差が0の場所を考慮
normalized_safe = (data - min_vals) / (diff + eps)
このように、微小な値を加算することで計算の安定性を高めることができます。
これはディープラーニングのライブラリ内部でも頻繁に使われるテクニックです。
正規化と標準化の使い分け
どちらの手法を使うべきかは、扱うデータの性質やアルゴリズムに依存します。
以下のガイドラインを参考にしてください。
- Min-Max Scalingが適している場合: データの分布が正規分布に従っていない場合や、画像処理のように値の範囲(0-255など)が明確に決まっている場合。
- 標準化(Z-score)が適している場合: 多くの機械学習アルゴリズム。勾配降下法を利用するモデルでは、標準化の方が学習が早く進む傾向があります。
基本的には標準化を第一の選択肢とし、必要に応じてMin-Max Scalingを検討するのが定石です。
まとめ
NumPyを使用した正規化処理は、データの構造を正しく理解し、適切なaxisを指定することでシンプルに実装できます。
列ごとの正規化は特徴量のスケーリングに、行ごとの正規化はサンプル内の比率調整に利用されます。
また、計算時にはkeepdims引数やブロードキャストの仕組みを意識することで、コードの可読性と効率を両立させることが可能です。
今回紹介した手法を活用して、より精度の高いデータ分析や機械学習モデルの構築に役立ててください。
NumPyの機能を最大限に引き出すことで、大規模な行列演算も驚くほどスムーズに処理できるようになるはずです。
