Loading src/loss_function/LossFunctionSilhouetteScore.cpp +4 −2 Original line number Diff line number Diff line Loading @@ -88,6 +88,7 @@ LossFunctionSilhouetteScore::LossFunctionSilhouetteScore(const std::shared_ptr<L void LossFunctionSilhouetteScore::set_nfeat(int n_feat) { _silhouette_scores.clear(); _n_feat = n_feat; _a.resize(_n_feat * _n_samp); _n_dim = n_feat + 1; Loading Loading @@ -145,7 +146,7 @@ double LossFunctionSilhouetteScore::calculate_a_i(size_t samp_idx, int cluster_l return sum / static_cast<double>(n_samples_in_cluster - 1); } // b_i for sample samp_idx: min average distance to other clusters double LossFunctionSilhouetteScore::calculate_b_i(size_t sample_idx, int cluster_label) { const double* p1 = &_a[sample_idx * _n_feat]; double best_avg = std::numeric_limits<double>::infinity(); Loading Loading @@ -174,7 +175,7 @@ void LossFunctionSilhouetteScore::populate_sil_score() { for (auto& [cluster_label, indices] : _cluster_indices) { // step by _n_feat to visit each sample once for (size_t i = 0; i < indices.size(); i += _n_feat) { size_t sample_idx = indices[i] / _n_feat; // recover row/sample index size_t sample_idx = indices[i] / _n_feat; a_i = calculate_a_i(sample_idx, cluster_label); b_i = calculate_b_i(sample_idx, cluster_label); double s_i = 0.0; Loading Loading @@ -210,6 +211,7 @@ double LossFunctionSilhouetteScore::test_loss(const std::vector<model_node_ptr>& } double LossFunctionSilhouetteScore::get_silhouette_average() { std::cout << std::endl; if (_silhouette_scores.empty()) return 0.0; return std::accumulate(_silhouette_scores.begin(), _silhouette_scores.end(), 0.0) / static_cast<double>(_silhouette_scores.size()); Loading src/loss_function/silhouette.py +21 −6 Original line number Diff line number Diff line Loading @@ -60,22 +60,37 @@ def run_feature_testing(train_feat1, train_feat2, cluster_labels): if __name__ == "__main__": #Double cluster tests: labels = np.array([0, 0, 1, 1]) if True: if False: #For feats ~0.466666666 train_feat1 = np.array([4.0, 5.0, 6.0, 7.0]) train_feat2 = np.array([2.0, 3.0, 4.0, 5.0]) run_feature_testing(train_feat1, train_feat2, labels) if True: if False: #values ripped from d_matrix dat = np.array([ [-1.86846, -1.9923, -1.95254, -1.56359], [-1.78104, -1.31323, -1.24359, -1.72509], [-1.06531, -1.47307, -1.01745, -1.10234], [-1.96543, -1.29881, -1.92731, -1.49548] [-1.86846, -1.9923], [-1.78104, -1.31323], [-1.06531, -1.47307], [-1.96543, -1.29881] ]) score = silhouette_score(dat, labels) print("Silhouette score for inds:", score) if True: X = np.array([ [1.0, 2.0], [1.2, 2.1], [0.8, 1.9], [5.0, 5.0], [5.1, 5.2], [4.9, 4.8], [9.0, 1.0], [9.1, 1.1], [8.9, 0.9], [2.0, 8.0], [2.1, 8.2], [1.9, 7.8] ]) # Cluster labels labels = np.array([0, 0, 0, 1, 1, 1, 2, 2, 2, 3, 3, 3]) # Compute silhouette score sil_score = silhouette_score(X, labels) print("Silhouette Score:", sil_score) No newline at end of file tests/googletest/loss_function/test_silhouette_score_loss.cc +34 −0 Original line number Diff line number Diff line Loading @@ -197,6 +197,40 @@ TEST_F(LossFunctionSilhouetteScoreTests, ManualDeterminationFeats) EXPECT_NEAR(silhouette_score,0.466666, 0.0001); } TEST_F(LossFunctionSilhouetteScoreTests, MultiClassTest) { _task_sizes_train = {12}; _task_sizes_test = {12}; node_value_arrs::finalize_values_arr(); node_value_arrs::initialize_values_arr(_task_sizes_train, _task_sizes_test, 2, 2, false); std::vector<double> train_feat1 = {1.0,1.2,0.8, 5.0,5.1,4.9, 9.0,9.1,8.9, 2.0,2.1,1.9}; std::vector<double> train_feat2 = {2.0,2.1,1.9, 5.0,5.2,4.8, 1.0,1.1,0.9, 8.0,8.2,7.8}; std::vector<double> test_feat1 = train_feat1; std::vector<double> test_feat2 = train_feat2; _phi.clear(); _model_phi.clear(); _phi.push_back(std::make_shared<FeatureNode>(0, "Feature1", train_feat1, test_feat1, Unit("m"))); _phi.push_back(std::make_shared<FeatureNode>(1, "Feature2", train_feat2, test_feat2, Unit("m"))); _model_phi.push_back(std::make_shared<ModelNode>(_phi[0])); _model_phi.push_back(std::make_shared<ModelNode>(_phi[1])); // 4 classes _prop_train = {0,0,0, 1,1,1, 2,2,2, 3,3,3}; _prop_test = _prop_train; LossFunctionSilhouetteScore loss( _prop_train, _prop_test, _task_sizes_train, _task_sizes_test, false, 2); double silhouette_score = loss(_model_phi); EXPECT_NEAR(silhouette_score, 0.941, 0.001); } } Loading Loading
src/loss_function/LossFunctionSilhouetteScore.cpp +4 −2 Original line number Diff line number Diff line Loading @@ -88,6 +88,7 @@ LossFunctionSilhouetteScore::LossFunctionSilhouetteScore(const std::shared_ptr<L void LossFunctionSilhouetteScore::set_nfeat(int n_feat) { _silhouette_scores.clear(); _n_feat = n_feat; _a.resize(_n_feat * _n_samp); _n_dim = n_feat + 1; Loading Loading @@ -145,7 +146,7 @@ double LossFunctionSilhouetteScore::calculate_a_i(size_t samp_idx, int cluster_l return sum / static_cast<double>(n_samples_in_cluster - 1); } // b_i for sample samp_idx: min average distance to other clusters double LossFunctionSilhouetteScore::calculate_b_i(size_t sample_idx, int cluster_label) { const double* p1 = &_a[sample_idx * _n_feat]; double best_avg = std::numeric_limits<double>::infinity(); Loading Loading @@ -174,7 +175,7 @@ void LossFunctionSilhouetteScore::populate_sil_score() { for (auto& [cluster_label, indices] : _cluster_indices) { // step by _n_feat to visit each sample once for (size_t i = 0; i < indices.size(); i += _n_feat) { size_t sample_idx = indices[i] / _n_feat; // recover row/sample index size_t sample_idx = indices[i] / _n_feat; a_i = calculate_a_i(sample_idx, cluster_label); b_i = calculate_b_i(sample_idx, cluster_label); double s_i = 0.0; Loading Loading @@ -210,6 +211,7 @@ double LossFunctionSilhouetteScore::test_loss(const std::vector<model_node_ptr>& } double LossFunctionSilhouetteScore::get_silhouette_average() { std::cout << std::endl; if (_silhouette_scores.empty()) return 0.0; return std::accumulate(_silhouette_scores.begin(), _silhouette_scores.end(), 0.0) / static_cast<double>(_silhouette_scores.size()); Loading
src/loss_function/silhouette.py +21 −6 Original line number Diff line number Diff line Loading @@ -60,22 +60,37 @@ def run_feature_testing(train_feat1, train_feat2, cluster_labels): if __name__ == "__main__": #Double cluster tests: labels = np.array([0, 0, 1, 1]) if True: if False: #For feats ~0.466666666 train_feat1 = np.array([4.0, 5.0, 6.0, 7.0]) train_feat2 = np.array([2.0, 3.0, 4.0, 5.0]) run_feature_testing(train_feat1, train_feat2, labels) if True: if False: #values ripped from d_matrix dat = np.array([ [-1.86846, -1.9923, -1.95254, -1.56359], [-1.78104, -1.31323, -1.24359, -1.72509], [-1.06531, -1.47307, -1.01745, -1.10234], [-1.96543, -1.29881, -1.92731, -1.49548] [-1.86846, -1.9923], [-1.78104, -1.31323], [-1.06531, -1.47307], [-1.96543, -1.29881] ]) score = silhouette_score(dat, labels) print("Silhouette score for inds:", score) if True: X = np.array([ [1.0, 2.0], [1.2, 2.1], [0.8, 1.9], [5.0, 5.0], [5.1, 5.2], [4.9, 4.8], [9.0, 1.0], [9.1, 1.1], [8.9, 0.9], [2.0, 8.0], [2.1, 8.2], [1.9, 7.8] ]) # Cluster labels labels = np.array([0, 0, 0, 1, 1, 1, 2, 2, 2, 3, 3, 3]) # Compute silhouette score sil_score = silhouette_score(X, labels) print("Silhouette Score:", sil_score) No newline at end of file
tests/googletest/loss_function/test_silhouette_score_loss.cc +34 −0 Original line number Diff line number Diff line Loading @@ -197,6 +197,40 @@ TEST_F(LossFunctionSilhouetteScoreTests, ManualDeterminationFeats) EXPECT_NEAR(silhouette_score,0.466666, 0.0001); } TEST_F(LossFunctionSilhouetteScoreTests, MultiClassTest) { _task_sizes_train = {12}; _task_sizes_test = {12}; node_value_arrs::finalize_values_arr(); node_value_arrs::initialize_values_arr(_task_sizes_train, _task_sizes_test, 2, 2, false); std::vector<double> train_feat1 = {1.0,1.2,0.8, 5.0,5.1,4.9, 9.0,9.1,8.9, 2.0,2.1,1.9}; std::vector<double> train_feat2 = {2.0,2.1,1.9, 5.0,5.2,4.8, 1.0,1.1,0.9, 8.0,8.2,7.8}; std::vector<double> test_feat1 = train_feat1; std::vector<double> test_feat2 = train_feat2; _phi.clear(); _model_phi.clear(); _phi.push_back(std::make_shared<FeatureNode>(0, "Feature1", train_feat1, test_feat1, Unit("m"))); _phi.push_back(std::make_shared<FeatureNode>(1, "Feature2", train_feat2, test_feat2, Unit("m"))); _model_phi.push_back(std::make_shared<ModelNode>(_phi[0])); _model_phi.push_back(std::make_shared<ModelNode>(_phi[1])); // 4 classes _prop_train = {0,0,0, 1,1,1, 2,2,2, 3,3,3}; _prop_test = _prop_train; LossFunctionSilhouetteScore loss( _prop_train, _prop_test, _task_sizes_train, _task_sizes_test, false, 2); double silhouette_score = loss(_model_phi); EXPECT_NEAR(silhouette_score, 0.941, 0.001); } } Loading