k-means法


やっとこさ、ソースコードをうp


とりあえず、ひどいコードながら完成。javaからC++へ直すの面倒くさかった~
だけどまだまだ、直せるところいっぱいあるよ~w

しかし思ったのだが、これは本当にk-means法なのだろうか?

さてといきなりだが、ちょっとクラスタリングの説明。
今回やっているのは、ハード面のクラスタリングではないです。
簡単に言うと、データ解析手法の一種です。(以下、クラスタリング→クラスター分析)

ただ、普通のデータ解析と違ってクラスター分析は、外的な基準をなしに自動的に分類します。
基準なしといっても結局ある程度はプログラム上で与えるけど・・・

んで、その代表的なやり方にウォード法とk-means法があります。
その中のk-means法を実装したわけです。

k-means法は、別名:K平均法と呼ばれています。
何故に平均なのかと言うと、抽出したクラスタの平均を使って、与えたクラスタ数のK個に分類するからです。
詳しくは、wiki見て下さい。私が説明できることなんてwikiに書いてありますからw
↓wikiのK平均法のページ
http://ja.wikipedia.org/wiki/K%E5%B9%B3%E5%9D%87%E6%B3%95

実装の理由?・・・理由なんてありません。
そこにアルゴリズムがあるから実装しただけです。
(まぁ、できてるのか知らないけど・・・)

参考にしたサイトはココ
Yahoo!リサーチのヤフー・バリュー・インサイト「非階層クラスター分析のアルゴリズム」 http://www.yahoo-vi.co.jp/method/m01_kmeans.html(2009/11/23アクセス)

// K_Means.cpp
// author Takeshi Miyamoto
 
#include <cstdlib>	// srand, rand使用
#include <ctime>	// time使用
#include <iostream>	// cout, endl使用
#include <vector>	// vectorを使用
#include <sstream>	// ostringstream使用

// クラス定義
// 人間クラス
class Person
{
private:
	// 体重
	float weight_;
 
	// 身長
	float height_;
 
public:
	// コンストラクタ
	Person()
		:weight_( 0 )
		,height_( 0 )
	{
	}
 
	// コンストラクタ
	Person( float weight, float height )
		:weight_( weight )
		,height_( height )
	{
	}
 
	// 体重の取得
	float get_weight()
	{
		return weight_;
	}
 
	// 身長の取得
	float get_height()
	{
		return height_;
	}
 
	// toStringメソッド
	const std::string toString()
	{
		std::ostringstream oss;
		oss << "weight=" << weight_ << ", height=" << height_;
		return oss.str();
	}
};
 
// 補助関数群クラス
class Utility
{
public:
	// 最小値
	static float minimum( float a, float b )
	{
		return ((a < b) ? a : b);
	}
 
	// 最大値
	static float maxmum( float a, float b )
	{
		return ((a > b) ? a : b);
	}
};
 
// メイン処理
int main()
{
	// 乱数の種を設定
	srand( (unsigned)time( NULL ) );
 
	// 体重、身長の表
	Person persons[] =
	{
		// 数値は適当
		//		体重   身長
		Person( 45.0f, 160.0f ),
		Person( 46.0f, 161.0f ),
		Person( 47.0f, 163.0f ),
		Person( 48.0f, 166.0f ),
		Person( 49.0f, 170.0f ),
		Person( 50.0f, 175.0f ),
		Person( 45.0f, 171.0f ),
		Person( 46.0f, 161.0f ),
		Person( 47.0f, 177.0f ),
		Person( 48.0f, 168.0f ),
		Person( 49.0f, 164.0f ),
		Person( 49.0f, 173.0f ),
		Person( 55.0f, 171.0f ),
		Person( 60.0f, 178.0f ),
		Person( 65.0f, 180.0f ),
		Person( 70.0f, 181.0f ),
		Person( 75.0f, 185.0f ),
		Person( 55.0f, 183.0f ),
		Person( 60.0f, 186.0f ),
		Person( 65.0f, 189.0f ),
		Person( 70.0f, 165.0f ),
		Person( 65.0f, 170.0f ),
	};
	const int person_count = sizeof( persons ) / sizeof( persons[0] );
	std::cout << "person count=" << person_count << std::endl;
 
	// 内容表示
	std::cout << "体重、身長を表示する" << std::endl;
	for( int i = 0; i < person_count; ++i )
	{
		std::cout << "[" << i << "]=(" << persons[i].toString() << ")" << std::endl;
	}
	std::cout << std::endl;
 
	// クラスタリング[ここから]
	// ランダムに仮のクラスター値(cluster_count)を決定する(今回は適当に3としとく)
	// 本当はこのクラスター値(cluster_count)の決定はかなり重要で、色々やんなきゃいけないらしい(今回は端折るw)
	const int cluster_count = 3;
	int seed[cluster_count];
	int index = 0;
	while( index != cluster_count )
	{
		// ランダム値を取得
		int rand_number = rand() % (person_count - 1);
 
		// クラスター値に同一値が存在するか調べる
		bool judge = false;
		for( int i = 0; i < cluster_count; ++i )
		{
			if( seed[i] == rand_number ) { judge = true; }
		}
 
		// 同一値が存在するか?
		if( judge == false )
		{
			seed[index++] = rand_number;
		}
	}
	// ランダム値を表示する
	std::cout << "ランダム値を表示する" << std::endl;
	for( int i = 0; i < cluster_count; ++i )
	{
		std::cout << seed[i] << std::endl;
	}
	std::cout << std::endl;
 
	// クラスタリング後の格納vector
	std::vector< Person > vec[cluster_count];
	vec->clear();
 
	// 一回目のクラスタリング
	for( int i = 0; i < person_count; ++i )
	{
		// 各クラスター(seed[?])のどこに一番近いか調べる為、二点間の距離を算出する
		float weight0 =
			(persons[seed[0]].get_weight() - persons[i].get_weight()) * (persons[seed[0]].get_weight() - persons[i].get_weight());
		float height0 =
			(persons[seed[0]].get_height() - persons[i].get_height()) * (persons[seed[0]].get_height() - persons[i].get_height());;
		float p0 = weight0 + height0;
 
		float weight1 =
			(persons[seed[1]].get_weight() - persons[i].get_weight()) * (persons[seed[1]].get_weight() - persons[i].get_weight());
		float height1 =
			(persons[seed[1]].get_height() - persons[i].get_height()) * (persons[seed[1]].get_height() - persons[i].get_height());;
		float p1 = weight1 + height1;
 
		float weight2 =
			(persons[seed[2]].get_weight() - persons[i].get_weight()) * (persons[seed[2]].get_weight() - persons[i].get_weight());
		float height2 =
			(persons[seed[2]].get_height() - persons[i].get_height()) * (persons[seed[2]].get_height() - persons[i].get_height());;
		float p2 = weight2 + height2;
 
		// 二点間の処理と距離の最小値を表示する
		std::cout << "[" << i << "]=(p1=" << p0 << "), (p2=" << p1 << "), (p3=" << p2 << ")" << std::endl;
		std::cout << Utility::minimum( p0, Utility::minimum( p1, p2 ) ) << std::endl;
 
		// 最小値をvectorへ格納する(う~ん、とても美しくない糞コードだ・・・)
		// p0とp1のどちらが小さいか?
		if( p0 < p1 )
		{
			// p0とp2はどちらが小さいか?
			if( p0 < p2 )
			{
				vec[0].push_back( persons[i] );
			}
			else
			{
				vec[2].push_back( persons[i] );
			}
		}
		else
		{
			// p1とp2はどちらが小さいか?
			if( p1 < p2 )
			{
				vec[1].push_back( persons[i] );
			}
			else
			{
				vec[2].push_back( persons[i] );
			}
		}
	}
	std::cout << std::endl;
 
	// vectorの中身を確認する
	for( unsigned int i = 0; i < cluster_count; ++i )
	{
		unsigned int size = vec[i].size();
		for( unsigned int j = 0; j < size; ++j )
		{
			std::cout << "(" << vec[i][j].toString() << ") ";
		}
		std::cout << std::endl;
	}
 
	// 各クラスター(vector)から新しい重心を求める
	Person avg_person[cluster_count];
	for( unsigned int i = 0; i < cluster_count; ++i )
	{
		// 平均を求める
		unsigned int size = vec[i].size();
		float avg_weight = 0, avg_height = 0;
		for( unsigned int j = 0; j < size; ++j )
		{
			avg_weight += vec[i][j].get_weight();
			avg_height += vec[i][j].get_height();
		}
		avg_weight /= size;
		avg_height /= size;
		avg_person[i] = Person( avg_weight, avg_height );
	}
	// 重心の数値を確認する
	for( int i = 0; i < cluster_count; ++i )
	{
		std::cout << "[" << i << "]=(" << avg_person[i].toString() << ")" << std::endl;
	}
	std::cout << std::endl;
 
	// 前回と同じ重心の平均値になるまで繰り返す(あ~、なんて冗長なプログラム・・・同じことを二回もやってる)
	// 今回は、必ずループが終わるように最高でも10回目までしか平均を取らない(一応)
	bool is_equals_avg = false;
	int ten_count = 0;
	do
	{
		// クラスタリング後の格納vectorをクリアする
		for( int i = 0; i < cluster_count; ++i )
		{
			vec[i].clear();
		}
 
		// クラスタリングする(以下しばらく同じことやってる冗長部分)
		for( int i = 0; i < person_count; ++i )
		{
			// 各クラスター(seed[?])のどこに一番近いか調べる為、二点間の距離を算出する
			float weight0 =
				(avg_person[0].get_weight() - persons[i].get_weight()) * (avg_person[0].get_weight() - persons[i].get_weight());
			float height0 =
				(avg_person[0].get_height() - persons[i].get_height()) * (avg_person[0].get_height() - persons[i].get_height());;
			float p0 = weight0 + height0;
 
			float weight1 =
				(avg_person[1].get_weight() - persons[i].get_weight()) * (avg_person[1].get_weight() - persons[i].get_weight());
			float height1 =
				(avg_person[1].get_height() - persons[i].get_height()) * (avg_person[1].get_height() - persons[i].get_height());;
			float p1 = weight1 + height1;
 
			float weight2 =
				(avg_person[2].get_weight() - persons[i].get_weight()) * (avg_person[2].get_weight() - persons[i].get_weight());
			float height2 =
				(avg_person[2].get_height() - persons[i].get_height()) * (avg_person[2].get_height() - persons[i].get_height());;
			float p2 = weight2 + height2;
 
			// 最小値はどれか?
			// p0とp1のどちらが小さいか?
			if( p0 < p1 )
			{
				// p0とp2はどちらが小さいか?
				if( p0 < p2 )
				{
					vec[0].push_back( persons[i] );
				}
				else
				{
					vec[2].push_back( persons[i] );
				}
			}
			else
			{
				// p1とp2はどちらが小さいか?
				if( p1 < p2 )
				{
					vec[1].push_back( persons[i] );
				}
				else
				{
					vec[2].push_back( persons[i] );
				}
			}
		}
		// リストの中を確認する
		for( unsigned int i = 0; i < cluster_count; ++i )
		{
			unsigned int size = vec[i].size();
			for( unsigned int j = 0; j < size; ++j )
			{
				std::cout << "(" << vec[i][j].toString() << ") ";
			}
			std::cout << std::endl;
		}
 
		// 前の平均値を格納
		Person prev_avg_person[cluster_count];
		for( int i = 0; i < cluster_count; ++i )
		{
			prev_avg_person[i] = avg_person[i];
		}
 
		// 重心の再計算
		for( unsigned int i = 0; i < cluster_count; ++i )
		{
			// 平均を求める
			unsigned int size = vec[i].size();
			float avg_weight = 0, avg_height = 0;
			for( unsigned int j = 0; j < size; ++j )
			{
				avg_weight += vec[i][j].get_weight();
				avg_height += vec[i][j].get_height();
			}
			avg_weight /= size;
			avg_height /= size;
			avg_person[i] = Person( avg_weight, avg_height );
		}
		// 重心の数値を確認する
		for( int i = 0; i < cluster_count; ++i )
		{
			std::cout << "[" << i << "]=(" << avg_person[i].toString() << ")" << std::endl;
		}
		std::cout << std::endl;
 
		// 前と同じ重心の平均値か?(どれか一つでも相違があればtrue)
		is_equals_avg = false;
		for( int i = 0; i < cluster_count; ++i )
		{
			if( (avg_person[i].get_weight() != prev_avg_person[i].get_weight()) ||
				(avg_person[i].get_height() != prev_avg_person[i].get_height()) )
			{
				is_equals_avg = true;
			}
		}
		std::cout << "is equals avg=" << ((is_equals_avg == 0 ? "true" : "false")) << std::endl;
 
		// カウントアップ
		std::cout << "loop count=" << ten_count << std::endl;
		++ten_count;
 
		// 改行
		std::cout << std::endl;
	} while( is_equals_avg == true && ten_count < 10 );
	// クラスタリング[ここまで]
 
	return 0;
}
 

タグ:

+ タグ編集
  • タグ:
最終更新:2009年11月23日 19:17
ツールボックス

下から選んでください:

新しいページを作成する
ヘルプ / FAQ もご覧ください。