機械学習のモデル精度を高める上で欠かせない「データ前処理」。その中でも、特徴量のスケール(尺度)を揃えるスケーリングは非常に重要です。
Scikit-learn(サイキットラーン)には多くのスケーリング手法が用意されていますが、Normalizer(ノーマライザー)は、他のスケーラーとは少し異なるユニークな役割を持っています。
この記事では、PythonのScikit-learnライブラリを使ってNormalizerを使いこなす方法に焦点を当てます。
Normalizerが具体的に何をしているのか?StandardScalerと何が違うのか?(StandardScalerに関する詳しい記事はこちら)- L1正規化とL2正規化の違いと使い分けは?
といった疑問を、具体的なPythonコード例と共に、初心者から中級者の方にも分かりやすく解説していきます。この記事を読めば、Normalizerを使ったサンプル(行)ごとの正規化をマスターできるはずです。
本記事で解説するScikit-learn Normalizerの公式ドキュメントはこちらです。
データ前処理に関するユーザーガイド(公式)はこちらです。
はじめに:Scikit-learnのNormalizerとは?
まずはNormalizerがどのようなものなのか、その基本的な役割と必要性、そして最も混同されがちなStandardScalerとの違いを明確にしましょう。
Normalizerの役割:サンプル(行)ごとのデータを正規化する
Normalizerの役割は、**「サンプル(行)ごと」**にベクトルの「大きさ」を揃えることです。
具体的には、データセットの各行(各サンプル)を、その行が持つL1ノルムまたはL2ノルム(ベクトルの長さや大きさの尺度)が1になるように変換します。
例えば、[3, 4] というデータ(行)があった場合、L2ノルム(ユークリッド距離)は $\sqrt{3^2 + 4^2} = 5$ です。Normalizer(norm='l2') は、この行の各要素を $5$ で割り、[3/5, 4/5] すなわち [0.6, 0.8] に変換します。この変換後のベクトルのL2ノルムは $\sqrt{0.6^2 + 0.8^2} = \sqrt{0.36 + 0.64} = \sqrt{1.0} = 1$ となります。
このように、各サンプルの「大きさ」を $1$ に統一するのがNormalizerの仕事です。
なぜNormalizerによる前処理が必要なのか?
Normalizerによる前処理が必要なのは、特徴量の「絶対的な大きさ」ではなく、「方向」や「パターン」に注目したい場合です。
例えば、テキストデータを扱う機械学習(自然言語処理)の分野でよく使われます。TF-IDFなどで単語の出現頻度をベクトル化した際、文書の長さ(単語数)が異なると、ベクトルの大きさもバラバラになってしまいます。
- 短い文書:「Python」が1回
- 長い文書:「Python」が5回
このままでは、「Python」という単語の重要性が、文書の長さによって左右されてしまいます。
Normalizerを使って各文書ベクトルを正規化(大きさを1に揃える)することで、文書の長さに関わらず、単語の構成比率(=ベクトルの方向)で比較できるようになります。
コサイン類似度のように、ベクトルの「方向」の近さを計算する手法を用いる場合、Normalizerによる前処理は特に有効です。
NormalizerとStandardScaler(標準化)との決定的な違い
NormalizerとStandardScaler(標準化)は、名前は似ていますが、処理する「向き」が全く異なります。これが最も重要な違いです。
- Normalizer(正規化): 行(サンプル)ごとに処理します。各サンプルの特徴量間の関係性(比率)を保ったまま、そのサンプルのノルムを1にします。
- StandardScaler(標準化): 列(特徴量)ごとに処理します。各特徴量(全サンプル)の平均が0、分散が1になるように変換します。
以下の図のようなイメージです。
Normalizer: $\to [x_1, x_2, x_3]$ (行を見る)StandardScaler: $\downarrow$ (列を見る)
データセット全体を見て「この特徴量A(列)は平均XX、分散YYだから…」と処理するのがStandardScaler。
それに対して、「このサンプル1(行)はベクトル長がXXだから…」「このサンプル2(行)はベクトル長がYYだから…」と、サンプル(行)ごとに独立して処理するのがNormalizerです。
どちらも「スケーリング」手法ですが、目的と処理単位が根本的に異なることを理解しておきましょう。
Normalizerの基本的な使い方(L2正規化)
Normalizerのデフォルトの動作は「L2正規化」です。これは最も一般的に使われるノルムで、ベクトルのユークリッド距離(原点からのまっすぐな距離)に基づいています。
早速、Pythonコードで使い方を見ていきましょう。
ライブラリのインポートとサンプルデータの準備
まず、必要なライブラリ(Normalizerとnumpy)をインポートし、サンプルデータを作成します。
import numpy as np
from sklearn.preprocessing import Normalizer
# サンプルデータを作成
# 3サンプル, 4特徴量
X = np.array([
[1, 1, 2, 4], # サンプル1
[3, 0, 0, 5], # サンプル2
[0, 6, 8, 0] # サンプル3
], dtype=np.float64) # 計算のために浮動小数点数型にしておきます
print("元のデータ (X):")
print(X)Normalizerインスタンスの作成 (デフォルトはL2)
次に、Normalizerのインスタンスを作成します。引数を指定しない場合、自動的に norm='l2' が選択されます。
# Normalizerのインスタンスを作成
# デフォルトは norm='l2'
normalizer_l2 = Normalizer()
# 以下と同じ意味
# normalizer_l2 = Normalizer(norm='l2')fit_transform()メソッドで正規化を実行する
作成したインスタンスの fit_transform() メソッドを使って、データを正規化します。
NormalizerはStandardScalerとは異なり、fit()メソッドではデータを「学習」しません(パイプライン互換性のために形式的に存在するだけです)。transform()(またはfit_transform)が呼ばれた時点で、入力されたデータの各行を独立して正規化します。
# L2正規化を実行
X_normalized_l2 = normalizer_l2.fit_transform(X)
print("\nL2正規化後のデータ (X_normalized_l2):")
print(X_normalized_l2)実行すると、以下のような結果が得られます(表示桁数は環境によります)。
L2正規化後のデータ (X_normalized_l2):
[[0.21821789 0.21821789 0.43643578 0.87287156]
[0.51449576 0. 0. 0.85749293]
[0. 0.6 0.8 0. ]]PythonコードでL2正規化の計算結果を確認する
L2正規化は、各行の要素の「二乗和の平方根(L2ノルム)」で、その行の各要素を割ることで実行されます。
$$\text{L2ノルム} = \sqrt{x_1^2 + x_2^2 + \dots + x_n^2}$$
手計算で確認してみましょう。
- サンプル1
[1, 1, 2, 4]:- L2ノルム: $\sqrt{1^2 + 1^2 + 2^2 + 4^2} = \sqrt{1 + 1 + 4 + 16} = \sqrt{22} \approx 4.5826$
- 正規化後:
- $1 / \sqrt{22} \approx 0.2182$
- $1 / \sqrt{22} \approx 0.2182$
- $2 / \sqrt{22} \approx 0.4364$
- $4 / \sqrt{22} \approx 0.8728$
- これは先ほどの実行結果と一致します。
- サンプル3
[0, 6, 8, 0]:- L2ノルム: $\sqrt{0^2 + 6^2 + 8^2 + 0^2} = \sqrt{0 + 36 + 64 + 0} = \sqrt{100} = 10.0$
- 正規化後:
- $0 / 10.0 = 0.0$
- $6 / 10.0 = 0.6$
- $8 / 10.0 = 0.8$
- $0 / 10.0 = 0.0$
- こちらも実行結果
[0., 0.6, 0.8, 0.]と一致します。
numpyを使って、正規化後の各行のL2ノルムが本当に $1$ になっているか確認することもできます。
# L2正規化後の各行のL2ノルムを計算
row_norms_l2 = np.linalg.norm(X_normalized_l2, ord=2, axis=1)
print("\nL2正規化後の各行のL2ノルム:")
print(row_norms_l2)実行結果:
L2正規化後の各行のL2ノルム:
[1. 1. 1.]すべての行(サンプル)のL2ノルムが $1.0$ になっていることが確認できました。
L1正規化の使い方 (norm=’l1′ を指定)
次に、もう一つの主要なノルムである「L1正規化」を見ていきましょう。L1正規化は、各行の要素の「絶対値の合計(L1ノルム)」が $1$ になるように変換します。
NormalizerでL1正規化を指定する方法
L1正規化を行うのは簡単です。Normalizerのインスタンスを作成する際に norm='l1' と指定するだけです。
# L1正規化用のNormalizerインスタンスを作成
normalizer_l1 = Normalizer(norm='l1')L1正規化の実行コード例
先ほどと同じサンプルデータ X を使って、L1正規化を実行してみます。
# L1正規化を実行
X_normalized_l1 = normalizer_l1.fit_transform(X)
print("元のデータ (X):")
print(X)
print("\nL1正規化後のデータ (X_normalized_l1):")
print(X_normalized_l1)実行結果は以下のようになります。
元のデータ (X):
[[1. 1. 2. 4.]
[3. 0. 0. 5.]
[0. 6. 8. 0.]]
L1正規化後のデータ (X_normalized_l1):
[[0.125 0.125 0.25 0.5 ]
[0.375 0. 0. 0.625 ]
[0. 0.42857143 0.57142857 0. ]]PythonコードでL1正規化の計算結果を確認する
L1正規化は、各行の要素の「絶対値の合計(L1ノルム)」で、その行の各要素を割ることで実行されます。
$$\text{L1ノルム} = |x_1| + |x_2| + \dots + |x_n|$$
- サンプル1
[1, 1, 2, 4]: (すべて正の数なので絶対値はそのまま)- L1ノルム: $1 + 1 + 2 + 4 = 8$
- 正規化後:
- $1 / 8 = 0.125$
- $1 / 8 = 0.125$
- $2 / 8 = 0.25$
- $4 / 8 = 0.5$
- 実行結果と一致します。
- サンプル3
[0, 6, 8, 0]:- L1ノルム: $0 + 6 + 8 + 0 = 14$
- 正規化後:
- $0 / 14 = 0.0$
- $6 / 14 \approx 0.42857$
- $8 / 14 \approx 0.57142$
- $0 / 14 = 0.0$
- こちらも実行結果と一致します。
numpyで、正規化後の各行のL1ノルム(絶対値の合計)が $1$ になっているか確認してみましょう。
# L1正規化後の各行のL1ノルムを計算 (絶対値の合計)
row_norms_l1 = np.sum(np.abs(X_normalized_l1), axis=1)
print("\nL1正規化後の各行のL1ノルム (絶対値の合計):")
print(row_norms_l1)実行結果:
L1正規化後の各行のL1ノルム (絶対値の合計):
[1. 1. 1.]こちらも、すべての行のL1ノルムが $1.0$ になっていることが確認できました。
L1正規化とL2正規化の違いと使い分け
L1とL2、どちらもベクトルの「大きさ」を $1$ に揃える処理ですが、その計算方法と特性が異なります。どちらを選べば良いのでしょうか?
計算方法の違い:L1ノルム(絶対値の合計)
L1ノルムは「マンハッタン距離」とも呼ばれます。各要素の絶対値を単純に合計した値です。
$$\text{L1ノルム} = \sum_{i} |x_i|$$
L1正規化は、外れ値(極端に大きな値)の影響を比較的受けにくいという特徴があります。また、結果がスパース(疎:0が多くなる)な特徴量と相性が良いとされることがあり、テキストマイニングなどでL1正規化が選ばれることもあります。
計算方法の違い:L2ノルム(ユークリッド距離)
L2ノルムは「ユークリッド距離」とも呼ばれ、私たちが普段イメージする「原点からの直線距離」です。各要素の二乗和の平方根です。
$$\text{L2ノルム} = \sqrt{\sum_{i} x_i^2}$$
L2正規化は、L1に比べて外れ値の影響をより大きく受けます(二乗するため)。しかし、数学的に扱いやすく、ベクトルの幾何学的な「長さ」を直感的に表現するため、最も一般的に使用されるノルムです。NormalizerのデフォルトもL2です。
どちらを選ぶべきか?使い分けの目安
どちらを使うべきか迷った場合の、簡単な目安を示します。
- L2正規化 (デフォルト):
- 迷ったらまずこちらを試す。
- コサイン類似度を計算する前処理など、ベクトルの「方向」や「角度」が重要な場合に広く使われます。
- 一般的な機械学習タスクでまず選択されることが多いです。
- L1正規化:
- データに外れ値が多く、その影響を抑えたい場合。
- 特徴量がスパース(0が多い)なデータ(例:テキストデータのBag-of-WordsやTF-IDF)を扱う場合。
多くの場合、L2正規化(デフォルト)で十分な性能が得られます。もしL2で期待した結果が得られない場合に、L1を試してみる、というアプローチが良いでしょう。
Normalizerの便利な使い方と注意点
最後に、Normalizerを使う上での便利な使い方と、特に注意すべき点について解説します。
transform()メソッド:学習済みモデルで新しいデータを変換する
StandardScalerなどの他のスケーラーでは、fit(X_train)で訓練データの平均・分散を「学習」し、その学習結果を使ってtransform(X_test)でテストデータを変換するのが一般的です。
しかしNormalizerは、前述の通りfit()では何も学習しません。
Normalizerにおけるfit()の主な役割は、sklearn.pipeline.Pipelineなどの仕組みの中で、他の中間処理(StandardScalerなど)とAPIの形式(fitしてtransformする)を統一するために存在しています。
Normalizerにおいては、fit_transform(X) を実行することと、transform(X) を実行することは、実質的に同じ結果(Xの各行を正規化する)になります。
# 新しいデータ
X_new = np.array([[10, 20, 0]], dtype=np.float64)
# L2正規化 (fitしていないnormalizer_l2インスタンスを使用)
X_new_transformed = normalizer_l2.transform(X_new)
print("新しいデータのL2正規化結果:")
print(X_new_transformed)
# ノルムの確認 (sqrt(10^2 + 20^2) = sqrt(100 + 400) = sqrt(500))
# 10 / sqrt(500) approx 0.4472
# 20 / sqrt(500) approx 0.8944実行結果:
新しいデータのL2正規化結果:
[[0.4472136 0.89442719 0. ]]このように、fit()で使った元のデータXの情報は一切使われず、transform()に渡されたX_new自体のノルムに基づいて正規化が実行されます。
fit()とtransform()を分割して実行する意味
上記の特性から、Normalizerにおいてfit(X_train)とtransform(X_test)を分割して実行する使い方は、StandardScalerとは根本的に意味が異なります。
StandardScaler.fit(X_train).transform(X_test):X_train(訓練データ)の平均・分散を使って、X_test(テストデータ)を標準化する。
Normalizer.fit(X_train).transform(X_test):fit(X_train)では何も学習しない(selfを返すだけ)。transform(X_test)は、X_test(テストデータ)の各行のノルムを使って、X_testを正規化する。X_trainの情報は一切使用されません。
この動作の違いは、Scikit-learnのパイプラインを組む上で非常に重要なので、必ず覚えておいてください。
注意点:Normalizerは「行」単位で処理する
この記事で何度も強調してきましたが、最後にもう一度確認します。
Normalizerは**「行」単位(サンプルごと)**で処理します。
機械学習の前処理では、StandardScalerやMinMaxScalerのように「列」単位(特徴量ごと)で処理するスケーラーを使う場面の方が多いため、混同しやすいです。
- 列(特徴量)のスケールを揃えたい:
StandardScaler(平均0, 分散1)MinMaxScaler(最小0, 最大1)
- 行(サンプル)のノルム(大きさ)を1に揃えたい:
Normalizer(L1 or L2)
自分がやりたい前処理が「列」単位なのか「行」単位なのかを明確にし、適切なスケーラーを選択するようにしてください。
まとめ:Normalizerを使いこなしデータ前処理の精度を上げよう
今回は、Scikit-learnのNormalizerについて、その基本的な使い方からL1/L2正規化の違い、StandardScalerとの決定的な違いまでを詳しく解説しました。
- Normalizerは、行(サンプル)ごとに処理し、各行のノルム(L1またはL2)を1にします。
- StandardScalerは、列(特徴量)ごとに処理し、各列の平均を0、分散を1にします。
- L2正規化 (
norm='l2') はデフォルトで、ベクトルの「ユークリッド距離」を1にします。迷ったらまずL2を使いましょう。 - L1正規化 (
norm='l1') は、ベクトルの「絶対値の合計」を1にします。外れ値に強い、スパースなデータで使われることがあります。 Normalizerのfit()メソッドは何も学習せず、transform()は入力されたデータ自体のノルムで正規化を実行します。
Normalizerは、特にテキストデータや、ベクトルの「方向」が重要なタスク(コサイン類似度など)で真価を発揮するスケーラーです。
他のスケーラーとの違いを正しく理解し、適切な場面でNormalizerを使いこなして、あなたの機械学習モデルの精度向上に役立ててください。


コメント