train_searcher.py 5.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147
  1. import os, sys
  2. import numpy as np
  3. import scann
  4. import argparse
  5. import glob
  6. from multiprocessing import cpu_count
  7. from tqdm import tqdm
  8. from ldm.util import parallel_data_prefetch
  9. def search_bruteforce(searcher):
  10. return searcher.score_brute_force().build()
  11. def search_partioned_ah(searcher, dims_per_block, aiq_threshold, reorder_k,
  12. partioning_trainsize, num_leaves, num_leaves_to_search):
  13. return searcher.tree(num_leaves=num_leaves,
  14. num_leaves_to_search=num_leaves_to_search,
  15. training_sample_size=partioning_trainsize). \
  16. score_ah(dims_per_block, anisotropic_quantization_threshold=aiq_threshold).reorder(reorder_k).build()
  17. def search_ah(searcher, dims_per_block, aiq_threshold, reorder_k):
  18. return searcher.score_ah(dims_per_block, anisotropic_quantization_threshold=aiq_threshold).reorder(
  19. reorder_k).build()
  20. def load_datapool(dpath):
  21. def load_single_file(saved_embeddings):
  22. compressed = np.load(saved_embeddings)
  23. database = {key: compressed[key] for key in compressed.files}
  24. return database
  25. def load_multi_files(data_archive):
  26. database = {key: [] for key in data_archive[0].files}
  27. for d in tqdm(data_archive, desc=f'Loading datapool from {len(data_archive)} individual files.'):
  28. for key in d.files:
  29. database[key].append(d[key])
  30. return database
  31. print(f'Load saved patch embedding from "{dpath}"')
  32. file_content = glob.glob(os.path.join(dpath, '*.npz'))
  33. if len(file_content) == 1:
  34. data_pool = load_single_file(file_content[0])
  35. elif len(file_content) > 1:
  36. data = [np.load(f) for f in file_content]
  37. prefetched_data = parallel_data_prefetch(load_multi_files, data,
  38. n_proc=min(len(data), cpu_count()), target_data_type='dict')
  39. data_pool = {key: np.concatenate([od[key] for od in prefetched_data], axis=1)[0] for key in prefetched_data[0].keys()}
  40. else:
  41. raise ValueError(f'No npz-files in specified path "{dpath}" is this directory existing?')
  42. print(f'Finished loading of retrieval database of length {data_pool["embedding"].shape[0]}.')
  43. return data_pool
  44. def train_searcher(opt,
  45. metric='dot_product',
  46. partioning_trainsize=None,
  47. reorder_k=None,
  48. # todo tune
  49. aiq_thld=0.2,
  50. dims_per_block=2,
  51. num_leaves=None,
  52. num_leaves_to_search=None,):
  53. data_pool = load_datapool(opt.database)
  54. k = opt.knn
  55. if not reorder_k:
  56. reorder_k = 2 * k
  57. # normalize
  58. # embeddings =
  59. searcher = scann.scann_ops_pybind.builder(data_pool['embedding'] / np.linalg.norm(data_pool['embedding'], axis=1)[:, np.newaxis], k, metric)
  60. pool_size = data_pool['embedding'].shape[0]
  61. print(*(['#'] * 100))
  62. print('Initializing scaNN searcher with the following values:')
  63. print(f'k: {k}')
  64. print(f'metric: {metric}')
  65. print(f'reorder_k: {reorder_k}')
  66. print(f'anisotropic_quantization_threshold: {aiq_thld}')
  67. print(f'dims_per_block: {dims_per_block}')
  68. print(*(['#'] * 100))
  69. print('Start training searcher....')
  70. print(f'N samples in pool is {pool_size}')
  71. # this reflects the recommended design choices proposed at
  72. # https://github.com/google-research/google-research/blob/aca5f2e44e301af172590bb8e65711f0c9ee0cfd/scann/docs/algorithms.md
  73. if pool_size < 2e4:
  74. print('Using brute force search.')
  75. searcher = search_bruteforce(searcher)
  76. elif 2e4 <= pool_size and pool_size < 1e5:
  77. print('Using asymmetric hashing search and reordering.')
  78. searcher = search_ah(searcher, dims_per_block, aiq_thld, reorder_k)
  79. else:
  80. print('Using using partioning, asymmetric hashing search and reordering.')
  81. if not partioning_trainsize:
  82. partioning_trainsize = data_pool['embedding'].shape[0] // 10
  83. if not num_leaves:
  84. num_leaves = int(np.sqrt(pool_size))
  85. if not num_leaves_to_search:
  86. num_leaves_to_search = max(num_leaves // 20, 1)
  87. print('Partitioning params:')
  88. print(f'num_leaves: {num_leaves}')
  89. print(f'num_leaves_to_search: {num_leaves_to_search}')
  90. # self.searcher = self.search_ah(searcher, dims_per_block, aiq_thld, reorder_k)
  91. searcher = search_partioned_ah(searcher, dims_per_block, aiq_thld, reorder_k,
  92. partioning_trainsize, num_leaves, num_leaves_to_search)
  93. print('Finish training searcher')
  94. searcher_savedir = opt.target_path
  95. os.makedirs(searcher_savedir, exist_ok=True)
  96. searcher.serialize(searcher_savedir)
  97. print(f'Saved trained searcher under "{searcher_savedir}"')
  98. if __name__ == '__main__':
  99. sys.path.append(os.getcwd())
  100. parser = argparse.ArgumentParser()
  101. parser.add_argument('--database',
  102. '-d',
  103. default='data/rdm/retrieval_databases/openimages',
  104. type=str,
  105. help='path to folder containing the clip feature of the database')
  106. parser.add_argument('--target_path',
  107. '-t',
  108. default='data/rdm/searchers/openimages',
  109. type=str,
  110. help='path to the target folder where the searcher shall be stored.')
  111. parser.add_argument('--knn',
  112. '-k',
  113. default=20,
  114. type=int,
  115. help='number of nearest neighbors, for which the searcher shall be optimized')
  116. opt, _ = parser.parse_known_args()
  117. train_searcher(opt,)