knncolle
Collection of KNN methods in C++
Loading...
Searching...
No Matches
L2Normalized.hpp
Go to the documentation of this file.
1#ifndef KNNCOLLE_L2_NORMALIZED_HP
2#define KNNCOLLE_L2_NORMALIZED_HP
3
4#include <vector>
5#include <cmath>
6#include <memory>
7#include <limits>
8#include <cstddef>
9#include <type_traits>
10#include <cstring>
11#include <string>
12#include <filesystem>
13#include <algorithm>
14
15#include "Searcher.hpp"
16#include "Prebuilt.hpp"
17#include "Builder.hpp"
18#include "Matrix.hpp"
19#include "NumericType.hpp"
20#include "utils.hpp"
21
27namespace knncolle {
28
32inline static constexpr const char* l2normalized_prebuilt_save_name = "knncolle::L2Normalized";
33
49template<typename Normalized_>
50std::function<void(const std::filesystem::path&)>& custom_save_for_l2normalized_normalized() {
51 static std::function<void(const std::filesystem::path&)> fun;
52 return fun;
53}
54
58template<typename Data_, typename Normalized_>
59void l2norm(const Data_* ptr, std::size_t ndim, Normalized_* buffer) {
60 if constexpr(std::is_same<Data_, Normalized_>::value) {
61 // If it's the same type, we don't need to do an up-front cast of 'ptr[d]' to Normalized_.
62 Normalized_ l2 = 0;
63 for (std::size_t d = 0; d < ndim; ++d) {
64 l2 += ptr[d] * ptr[d];
65 }
66
67 if (l2 > 0) {
68 l2 = std::sqrt(l2);
69 for (std::size_t d = 0; d < ndim; ++d) {
70 buffer[d] = ptr[d] / l2;
71 }
72 } else {
73 std::fill_n(buffer, ndim, 0);
74 }
75
76 } else {
77 // Otherwise, we do the cast first to avoid any surprises, e.g., from the squared value overflowing an integer 'Data_' type.
78 std::copy_n(ptr, ndim, buffer);
79
80 Normalized_ l2 = 0;
81 for (std::size_t d = 0; d < ndim; ++d) {
82 l2 += buffer[d] * buffer[d];
83 }
84
85 if (l2 > 0) {
86 l2 = std::sqrt(l2);
87 for (std::size_t d = 0; d < ndim; ++d) {
88 buffer[d] /= l2;
89 }
90 }
91 }
92}
93
94template<typename Index_, typename Data_, typename Distance_, typename Normalized_, class Searcher_>
95class L2NormalizedSearcher final : public Searcher<Index_, Data_, Distance_> {
96public:
97 L2NormalizedSearcher(std::unique_ptr<Searcher_> searcher, std::size_t num_dimensions) :
98 my_searcher(std::move(searcher)),
99 buffer(sanisizer::cast<I<decltype(buffer.size())> >(num_dimensions))
100 {}
101
102private:
103 // No way around this; the L2-normalized values must be floating-point,
104 // so the internal searcher must accept floats.
105 static_assert(std::is_floating_point<Normalized_>::value);
106
107 std::unique_ptr<Searcher_> my_searcher;
108 std::vector<Normalized_> buffer;
109
110public:
111 void search(Index_ i, Index_ k, std::vector<Index_>* output_indices, std::vector<Distance_>* output_distances) {
112 my_searcher->search(i, k, output_indices, output_distances);
113 }
114
115 void search(const Data_* ptr, Index_ k, std::vector<Index_>* output_indices, std::vector<Distance_>* output_distances) {
116 auto normalized = buffer.data();
117 l2norm(ptr, buffer.size(), normalized);
118 my_searcher->search(normalized, k, output_indices, output_distances);
119 }
120
121public:
122 bool can_search_all() const {
123 return my_searcher->can_search_all();
124 }
125
126 Index_ search_all(Index_ i, Distance_ threshold, std::vector<Index_>* output_indices, std::vector<Distance_>* output_distances) {
127 return my_searcher->search_all(i, threshold, output_indices, output_distances);
128 }
129
130 Index_ search_all(const Data_* ptr, Distance_ threshold, std::vector<Index_>* output_indices, std::vector<Distance_>* output_distances) {
131 auto normalized = buffer.data();
132 l2norm(ptr, buffer.size(), normalized);
133 return my_searcher->search_all(normalized, threshold, output_indices, output_distances);
134 }
135};
136
137template<typename Index_, typename Data_, typename Distance_>
138Prebuilt<Index_, Data_, Distance_>* load_prebuilt_raw(const std::filesystem::path&);
139
140template<typename Index_, typename Data_, typename Distance_, typename Normalized_>
141class L2NormalizedPrebuilt final : public Prebuilt<Index_, Data_, Distance_> {
142public:
143 L2NormalizedPrebuilt(std::unique_ptr<Prebuilt<Index_, Normalized_, Distance_> > prebuilt) : my_prebuilt(std::move(prebuilt)) {}
144
145private:
146 std::unique_ptr<Prebuilt<Index_, Normalized_, Distance_> > my_prebuilt;
147
148public:
149 Index_ num_observations() const {
150 return my_prebuilt->num_observations();
151 }
152
153 std::size_t num_dimensions() const {
154 return my_prebuilt->num_dimensions();
155 }
156
157public:
158 std::unique_ptr<Searcher<Index_, Data_, Distance_> > initialize() const {
159 return initialize_known();
160 }
161
162 auto initialize_known() const {
163 typedef I<decltype(*(my_prebuilt->initialize_known()))> KnownSearcher;
164 return std::make_unique<L2NormalizedSearcher<Index_, Data_, Distance_, Normalized_, KnownSearcher> >(my_prebuilt->initialize_known(), my_prebuilt->num_dimensions());
165 }
166
167public:
168 void save(const std::filesystem::path& dir) const {
169 quick_save(dir / "ALGORITHM", l2normalized_prebuilt_save_name, std::strlen(l2normalized_prebuilt_save_name));
170 auto norm_type = get_numeric_type<Normalized_>();
171 quick_save(dir / "NORMALIZED", &norm_type, 1);
172
173 auto& cust = custom_save_for_l2normalized_normalized<Normalized_>();
174 if (cust) {
175 cust(dir);
176 }
177
178 const auto indexdir = dir / "INDEX";
179 std::filesystem::create_directory(indexdir);
180 my_prebuilt->save(indexdir);
181 }
182
183 L2NormalizedPrebuilt(const std::filesystem::path& dir) : my_prebuilt(load_prebuilt_raw<Index_, Normalized_, Distance_>(dir / "INDEX")) {}
184};
185
186template<typename Index_, typename Data_, typename Normalized_, typename Matrix_>
187class L2NormalizedMatrix;
188
189template<typename Index_, typename Data_, typename Normalized_, class Extractor_ = MatrixExtractor<Data_> >
190class L2NormalizedMatrixExtractor final : public MatrixExtractor<Normalized_> {
191public:
192 L2NormalizedMatrixExtractor(std::unique_ptr<Extractor_> extractor, std::size_t dim) :
193 my_extractor(std::move(extractor)),
194 buffer(sanisizer::cast<I<decltype(buffer.size())> >(dim))
195 {}
196
197private:
198 std::unique_ptr<Extractor_> my_extractor;
199 std::vector<Normalized_> buffer;
200
201public:
202 const Normalized_* next() {
203 auto raw = my_extractor->next();
204 auto normalized = buffer.data();
205 l2norm(raw, buffer.size(), normalized);
206 return normalized;
207 }
208};
226template<typename Index_, typename Data_, typename Normalized_, typename Matrix_ = Matrix<Index_, Data_> >
227class L2NormalizedMatrix final : public Matrix<Index_, Normalized_> {
228public:
232 L2NormalizedMatrix(const Matrix_& matrix) : my_matrix(matrix) {}
237private:
238 static_assert(std::is_same<decltype(std::declval<Matrix_>().num_observations()), Index_>::value);
239 static_assert(std::is_same<typename std::remove_pointer<decltype(std::declval<Matrix_>().new_extractor()->next())>::type, const Data_>::value);
240
241 const Matrix_& my_matrix;
242
243public:
244 std::size_t num_dimensions() const {
245 return my_matrix.num_dimensions();
246 }
247
248 Index_ num_observations() const {
249 return my_matrix.num_observations();
250 }
251
255 auto new_known_extractor() const {
256 typedef I<decltype(*(my_matrix.new_known_extractor()))> KnownExtractor;
257 return std::make_unique<L2NormalizedMatrixExtractor<Index_, Data_, Normalized_, KnownExtractor> >(my_matrix.new_known_extractor(), num_dimensions());
258 }
259
260 std::unique_ptr<MatrixExtractor<Normalized_> > new_extractor() const {
261 return new_known_extractor();
262 }
263};
264
280template<typename Index_, typename Data_, typename Distance_, typename Normalized_, class Matrix_ = Matrix<Index_, Data_> >
281class L2NormalizedBuilder final : public Builder<Index_, Data_, Distance_, Matrix_> {
282public:
287
299 typedef typename std::conditional<
300 std::is_base_of<Matrix_, NormalizedMatrix>::value,
301 Matrix_,
304
305public:
309 L2NormalizedBuilder(std::shared_ptr<const Builder<Index_, Normalized_, Distance_, BuilderMatrix> > builder) : my_builder(std::move(builder)) {}
310
311private:
312 std::shared_ptr<const Builder<Index_, Normalized_, Distance_, BuilderMatrix> > my_builder;
313
314public:
318 Prebuilt<Index_, Data_, Distance_>* build_raw(const Matrix_& data) const {
319 return build_known_raw(data);
320 }
325public:
329 auto build_known_raw(const Matrix_& data) const {
330 NormalizedMatrix normalized(data);
331 return new L2NormalizedPrebuilt<Index_, Data_, Distance_, Normalized_>(my_builder->build_unique(normalized));
332 }
333
337 auto build_known_unique(const Matrix_& data) const {
338 return std::unique_ptr<I<decltype(*(build_known_raw(data)))> >(build_known_raw(data));
339 }
340
344 auto build_known_shared(const Matrix_& data) const {
345 return std::shared_ptr<I<decltype(*(build_known_raw(data)))> >(build_known_raw(data));
346 }
347};
348
349}
350
351#endif
Interface to build nearest-neighbor indices.
Interface for the input matrix.
Preserve numeric types when saving prebuilt indices.
Interface for prebuilt nearest-neighbor indices.
Interface for searching nearest-neighbor indices.
Interface to build nearest-neighbor search indices.
Definition Builder.hpp:28
virtual Prebuilt< Index_, Data_, Distance_ > * build_raw(const Matrix_ &data) const =0
Wrapper around a builder with L2 normalization.
Definition L2Normalized.hpp:281
L2NormalizedMatrix< Index_, Data_, Normalized_, Matrix_ > NormalizedMatrix
Definition L2Normalized.hpp:286
L2NormalizedBuilder(std::shared_ptr< const Builder< Index_, Normalized_, Distance_, BuilderMatrix > > builder)
Definition L2Normalized.hpp:309
auto build_known_raw(const Matrix_ &data) const
Definition L2Normalized.hpp:329
std::conditional< std::is_base_of< Matrix_, NormalizedMatrix >::value, Matrix_, NormalizedMatrix >::type BuilderMatrix
Definition L2Normalized.hpp:303
auto build_known_shared(const Matrix_ &data) const
Definition L2Normalized.hpp:344
auto build_known_unique(const Matrix_ &data) const
Definition L2Normalized.hpp:337
Wrapper around a matrix with L2 normalization.
Definition L2Normalized.hpp:227
std::size_t num_dimensions() const
Definition L2Normalized.hpp:244
Index_ num_observations() const
Definition L2Normalized.hpp:248
auto new_known_extractor() const
Definition L2Normalized.hpp:255
std::unique_ptr< MatrixExtractor< Normalized_ > > new_extractor() const
Definition L2Normalized.hpp:260
Interface for matrix data.
Definition Matrix.hpp:59
Interface for prebuilt nearest-neighbor search indices.
Definition Prebuilt.hpp:29
Collection of KNN algorithms.
Definition Bruteforce.hpp:31
Prebuilt< Index_, Data_, Distance_ > * load_prebuilt_raw(const std::filesystem::path &dir)
Definition load_prebuilt.hpp:105
void quick_save(const std::filesystem::path &path, const Input_ *const contents, const Length_ length)
Definition utils.hpp:33
std::function< void(const std::filesystem::path &)> & custom_save_for_l2normalized_normalized()
Definition L2Normalized.hpp:50
Miscellaneous utilities for knncolle