こんにちは、クラウドデータ研究所です。
当ブログではこれまでに 重回帰 や ロジスティック回帰 など、PySparkを使った機械学習の手法を取り上げてきました。
今回はシリーズの一つとして、「クラスタリング」を紹介します。
クラスタリングは「答え(ラベル)が与えられていないデータをグループに分ける手法」で、教師なし学習と呼ばれます。
Table of Contents
今回扱う題材:Irisデータセット
「Irisデータセット」は、アヤメという花の品種データです。
花びらやがく片の長さ・幅といった特徴量があり、実際には Setosa, Versicolor, Virginica の3種類の品種ラベルがついています。

今回はラベルを使わずに、クラスタリングだけで「3種類に分かれるか?」を試します。
Sparkセッションの準備
Pysparkを使う時は、まず「セッション」を立ち上げて処理の窓口を作っていきます。
from pyspark.sql import SparkSession
spark = SparkSession.builder.master("local").appName("kmeans_iris").getOrCreate()
データの読み込み
次にローカルに存在するirisのcsvファイルを読み込んでSparkのデータフレームを生成します。
data = spark.read.csv("./data/iris.csv", header=True, inferSchema=True)
data.show(5)
CSVファイルを読み込んで、最初の数行を確認します。
+------------+-----------+------------+-----------+-------+
|sepal_length|sepal_width|petal_length|petal_width|variety|
+------------+-----------+------------+-----------+-------+
| 5.1| 3.5| 1.4| 0.2| Setosa|
| 4.9| 3.0| 1.4| 0.2| Setosa|
| 4.7| 3.2| 1.3| 0.2| Setosa|
| 4.6| 3.1| 1.5| 0.2| Setosa|
| 5.0| 3.6| 1.4| 0.2| Setosa|
| 5.4| 3.9| 1.7| 0.4| Setosa|
| 4.6| 3.4| 1.4| 0.3| Setosa|
| 5.0| 3.4| 1.5| 0.2| Setosa|
| 4.4| 2.9| 1.4| 0.2| Setosa|
| 4.9| 3.1| 1.5| 0.1| Setosa|
| 5.4| 3.7| 1.5| 0.2| Setosa|
| 4.8| 3.4| 1.6| 0.2| Setosa|
カラムは次の通りです。
- sepal_length(がく片の長さ)
- sepal_width(がく片の幅)
- petal_length(花びらの長さ)
- petal_width(花びらの幅)
- variety(品種ラベル ※今回は使わない)
varietyのグルーピングをしてカウント値を見てみると、50個ずつアヤメの種類があることがわかります。
data.groupby("variety").count().show()
+----------+-----+
| variety|count|
+----------+-----+
| Virginica| 50|
| Setosa| 50|
|Versicolor| 50|
+----------+-----+
特徴量をまとめる
機械学習には、予測する為の変数として「目的変数」といい、その為のインプットになる変数の事を「説明変数」といいます。
PySpark MLでは、複数の説明変数、つまり特徴量を「1つのベクトル」にまとめる必要があります。
ここでは VectorAssembler
を使います。
これは複数のカラムを、一つのベクトルのカラムに変換するメソッドです。
from pyspark.ml.feature import VectorAssembler
assemble = VectorAssembler(
inputCols=["sepal_length", "sepal_width", "petal_length", "petal_width"],
outputCol="features"
)
pred = assemble.transform(data)
KMeansでクラスタリング
KMeansとはものすごく簡単に説明すると
- たくさんのデータを「似たもの同士」でグループ分けする方法
- そのとき、グループの真ん中の点(平均=centroid) を目印にして分ける
つまり「人を背の高さと体重で分けたら、背が高くて重い人は同じグループになる」みたいな感じです。
クラスタ数 kを変えて試し、クラスタリングの良さを「シルエット係数」で評価します。
from pyspark.ml.clustering import KMeans
from pyspark.ml.evaluation import ClusteringEvaluator
for k in range(2, 7):
kmeans = KMeans().setK(k).setSeed(1)
model = kmeans.fit(pred)
prediction = model.transform(pred)
evaluator = ClusteringEvaluator()
sil = evaluator.evaluate(prediction)
print(f"k={k}, シルエット係数={sil}")
結果を見てみると、k=3のときに最も良い値になります。
これは、実際の品種が3種類あることと対応していて面白いですね。
シルエット変数 k=3 で実行してみる
シルエット変数を3として、クラスタリングつまりグループ分けをしてみましょう。
kmeans = KMeans().setK(3).setSeed(1)
model = kmeans.fit(pred)
prediction = model.transform(pred)
prediction.groupby("prediction").count().show()
出力例:
+----------+-----+
|prediction|count|
+----------+-----+
| 0| 38|
| 1| 62|
| 2| 50|
+----------+-----+
ちゃんと3つのグループに分けられていることが分かります。
本来は、50個ずつアヤメの種類が散らばっていましたが、PysparkのMLのモジュールを使用してクラスタリングをすることによって上記のように3種類のグルーピングをすることができました。
まとめ
- クラスタリングは「正解ラベルがなくてもグループ分けできる」教師なし学習の代表例
- PySparkでは、
VectorAssembler
で特徴量をまとめてからKMeans
に渡す - 評価には「シルエット係数」を使うと便利
- Irisデータでは、実際の品種数と同じ「k=3」が最適