From e9e2a66203513554a7769fa761c100de1aede7bf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ricardo=20Monta=C3=B1ana?= Date: Mon, 28 Nov 2022 11:39:11 +0100 Subject: [PATCH] Fix entropy and information gain --- fimdlp/CPPFImdlp.cpp | 19 +++++++-- fimdlp/Metrics.cpp | 51 ++++++++++++++++--------- fimdlp/Metrics.h | 5 ++- fimdlp/cppfimdlp.cpython-310-darwin.so | Bin 92328 -> 93184 bytes 4 files changed, 53 insertions(+), 22 deletions(-) diff --git a/fimdlp/CPPFImdlp.cpp b/fimdlp/CPPFImdlp.cpp index 990cd6a..af89e5d 100644 --- a/fimdlp/CPPFImdlp.cpp +++ b/fimdlp/CPPFImdlp.cpp @@ -18,14 +18,16 @@ namespace CPPFImdlp std::vector CPPFImdlp::cutPoints(std::vector &X, std::vector &y) { std::vector cutPts; + std::vector cutIdx; float xPrev, cutPoint; - int yPrev; + int yPrev, idxPrev; std::vector indices = sortIndices(X); xPrev = X.at(indices[0]); yPrev = y.at(indices[0]); + idxPrev = indices[0]; if (debug) { - std::cout << "Entropy: " << Metrics::entropy(y, 0, y.size(), Metrics::numClasses(y)) << std::endl; + std::cout << "Entropy: " << Metrics::entropy(y, indices, 0, y.size(), Metrics::numClasses(y, indices, 0, indices.size())) << std::endl; } for (auto index = indices.begin(); index != indices.end(); ++index) { @@ -37,12 +39,23 @@ namespace CPPFImdlp { std::cout << "Cut point: " << (xPrev + X.at(*index)) / 2 << " //"; std::cout << X.at(*index) << " -> " << y.at(*index) << " yPrev= " << yPrev; - std::cout << "* (" << X.at(*index) << ", " << xPrev << ")=" << ((X.at(*index) + xPrev) / 2) << std::endl; + std::cout << "* (" << X.at(*index) << ", " << xPrev << ")=" + << ((X.at(*index) + xPrev) / 2) << "idxPrev" + << idxPrev << std::endl; } cutPts.push_back(cutPoint); + cutIdx.push_back(idxPrev); } xPrev = X.at(*index); yPrev = y.at(*index); + idxPrev = *index; + } + std::cout << "Information Gain:" << std::endl; + auto nc = Metrics::numClasses(y, indices, 0, indices.size()); + for (auto cutPoint = cutIdx.begin(); cutPoint != cutIdx.end(); ++cutPoint) + { + std::cout << *cutPoint << " -> " << Metrics::informationGain(y, indices, 0, indices.size(), *cutPoint, nc) << std::endl; + // << Metrics::informationGain(y, 0, y.size(), *cutPoint, Metrics::numClasses(y, 0, y.size())) << std::endl; } return cutPts; } diff --git a/fimdlp/Metrics.cpp b/fimdlp/Metrics.cpp index 81ec93c..682b8f7 100644 --- a/fimdlp/Metrics.cpp +++ b/fimdlp/Metrics.cpp @@ -4,15 +4,29 @@ namespace CPPFImdlp Metrics::Metrics() { } - float Metrics::entropy(std::vector &y, int start, int end, int nClasses) + int Metrics::numClasses(std::vector &y, std::vector indices, int start, int end) + { + int nClasses = 1; + int yAnt = y.at(start); + for (auto i = start; i < end; ++i) + { + if (y.at(i) != yAnt) + { + nClasses++; + yAnt = y.at(i); + } + } + return nClasses; + } + float Metrics::entropy(std::vector &y, std::vector &indices, int start, int end, int nClasses) { float entropy = 0; - int nElements = end - start; - std::vector - counts(nClasses, 0); - for (auto i = start; i < end; i++) + int nElements = 0; + std::vector counts(nClasses, 0); + for (auto i = &indices[start]; i != &indices[end]; ++i) { - counts[y[i]]++; + counts[y[*i]]++; + nElements++; } for (auto i = 0; i < nClasses; i++) { @@ -24,17 +38,20 @@ namespace CPPFImdlp } return entropy; } - int Metrics::numClasses(std::vector &y) + float Metrics::informationGain(std::vector &y, std::vector &indices, int start, int end, int cutPoint, int nClasses) { - int nClasses = 1; - int yAnt = y.at(0); - for (auto i = y.begin(); i != y.end(); ++i) - { - if (*i != yAnt) - { - nClasses++; - } - } - return nClasses; + float iGain = 0.0; + float entropy, entropyLeft, entropyRight; + int nClassesLeft, nClassesRight; + int nElementsLeft = cutPoint - start, nElementsRight = end - cutPoint; + int nElements = end - start; + nClassesLeft = Metrics::numClasses(y, indices, start, cutPoint); + nClassesRight = Metrics::numClasses(y, indices, cutPoint, end); + entropy = Metrics::entropy(y, indices, start, end, nClasses); + entropyLeft = Metrics::entropy(y, indices, start, cutPoint, nClassesLeft); + entropyRight = Metrics::entropy(y, indices, cutPoint, end, nClassesRight); + iGain = entropy - (float)nElementsLeft / nElements * entropyLeft - (float)nElementsRight / nElements * entropyRight; + return iGain; } + } diff --git a/fimdlp/Metrics.h b/fimdlp/Metrics.h index f4da032..60ef9a5 100644 --- a/fimdlp/Metrics.h +++ b/fimdlp/Metrics.h @@ -9,8 +9,9 @@ namespace CPPFImdlp { public: Metrics(); - static float entropy(std::vector &, int, int, int); - static int numClasses(std::vector &); + static int numClasses(std::vector &, std::vector, int, int); + static float entropy(std::vector &, std::vector &, int, int, int); + static float informationGain(std::vector &y, std::vector &indices, int start, int end, int cutPoint, int nClasses); }; } #endif \ No newline at end of file diff --git a/fimdlp/cppfimdlp.cpython-310-darwin.so b/fimdlp/cppfimdlp.cpython-310-darwin.so index cb7a93161221dc5a538770440606e4c77e2a32d2..97e36f9a734c9aef570a53d24e7aba028f2076d1 100755 GIT binary patch delta 19834 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 F{|BFca-;wN delta 15394 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