diff --git a/R/src/Makevars b/R/src/Makevars index fa15e8d39..9395dca85 100644 --- a/R/src/Makevars +++ b/R/src/Makevars @@ -35,6 +35,7 @@ OBJECTS = \ $(NATIVEDIR)/PartitionOneDimensionalBoosting.o \ $(NATIVEDIR)/PartitionRandomBoosting.o \ $(NATIVEDIR)/PartitionMultiDimensionalCorner.o \ + $(NATIVEDIR)/PartitionMultiDimensionalFull.o \ $(NATIVEDIR)/PartitionMultiDimensionalTree.o \ $(NATIVEDIR)/PartitionMultiDimensionalStraight.o \ $(NATIVEDIR)/Purify.o \ diff --git a/R/src/Makevars.interpret b/R/src/Makevars.interpret index 4c25e0388..d51b99d91 100644 --- a/R/src/Makevars.interpret +++ b/R/src/Makevars.interpret @@ -35,6 +35,7 @@ OBJECTS = \ $(NATIVEDIR)/PartitionOneDimensionalBoosting.o \ $(NATIVEDIR)/PartitionRandomBoosting.o \ $(NATIVEDIR)/PartitionMultiDimensionalCorner.o \ + $(NATIVEDIR)/PartitionMultiDimensionalFull.o \ $(NATIVEDIR)/PartitionMultiDimensionalTree.o \ $(NATIVEDIR)/PartitionMultiDimensionalStraight.o \ $(NATIVEDIR)/Purify.o \ diff --git a/README.md b/README.md index 0b1216337..2ff797693 100644 --- a/README.md +++ b/README.md @@ -658,6 +658,7 @@ We also build on top of many great packages. Please check them out! - [Proxy endpoints - bridging clinical trials and real world data](https://pdf.sciencedirectassets.com/272371/1-s2.0-S1532046424X00064/1-s2.0-S1532046424001412/main.pdf?X-Amz-Security-Token=IQoJb3JpZ2luX2VjEIn%2F%2F%2F%2F%2F%2F%2F%2F%2F%2FwEaCXVzLWVhc3QtMSJGMEQCIBYgAN6aOVrDnvQ1932tPndUyJ0Dm1nHdMVLiekPVduQAiAzbYe7W%2Bd6Dj8ee42ZeZnQxJwEjEjuGdiUEPx0a2G43SqyBQgSEAUaDDA1OTAwMzU0Njg2NSIMyMkCUNFeDTCUCppMKo8FiVShykb8phR%2F8aWUGE9gfnE5y7X3Jj1ZA2CVldH13T67s536bdTBhjIMF18rV0YP9iMi6B5aGr%2F286ovIJl332fxZ6iQNBIOPTm8kXQDUqvZbknYldiZqUPs69kuC%2FcKnJd1BWnv2SEZwbRuX94rWnRDPDaSoJx%2FVS6o4qsbFjp9%2BMYZr%2BvJzWHKrXAI4W%2Fh9%2BsIa0yvlac3IMWzAeD23HzDNmF0nqjJ6BSZzmDNW4HRIGBTrTUTO40TzQzhaOY7wyGA0Zv8SpWIULI%2FrY8z8EOX%2FU6OhqgyIMKv%2FSx3rUpMi5CrC1WcpnL97j%2FDAijNi4vMfG1b%2BBQIFRu2EmUky76k4w3FYxkCpYj4n4mk9H%2B%2Bc9C%2BdjKjUiayi%2FisIZUD7ISNhQ9oov0kXI1IVTCGKKQC9jqHOvdiA8YbVuMdEzy1Lkx%2B1kiEo79qvSlpTe2BtWAOm2Iequ01XoaMv%2FQb4ajhWKKSkTafzDAxc58aayP1YH49UzQ68Me7ecdHpx3JUHyYnxJGQ82wRpPkfZJA5wCmOUVI%2FBLuwFJyczG0LpALN5IpIqZz%2B8DvDR0xjRoN49dVwhrTSQ9BesvXbi2LKVm1ptacaaKqyx0PwLjQYKOd%2BPI3zCvRxEiM3IKSNFRLsUTyPNEE4E8pMFNxfyEX59yvTQrHwM62P7hvxHs%2BY6CxUGZTKBQwDAgxttJmiO%2BvjCRbTBXZg1WrQdXCkxntBXb15Mnqxo4lyPzUUkLdLAFK%2BLSwzBIcvSw2qG81Y8qhWmBgBT9vfAoSrjxsILFrB3nnz7u9XNNpRxb5Z9NuNG92%2Fpd%2F%2F5VespMY8Q0iwsNqazZ4M4H8UB34JgtrUEY27WrIsDWzLR%2FAYAxU%2BZHrFzCrsae5BjqyASqDBsNqjEkho%2FbuQDT%2F0vGx%2BgAqrksvVX0GrzNgvqnuPyvw6%2F%2B40ZJP5EA4axfltOYb2tNjd18Ngy2A3cd6J57v1G7wYyuSFIUfHGN5LA8BXK7p0x1mNcwN3pKHtAf260gjpsWMG7anvpK%2F3YupTz498C1lAmurJD%2BLN41lq05wBr403cchE41yzqAKHVKVpNq9s6oGHJmq0KJRvk%2FfjZr8oLhod5gtrwLKvLGqULf50L0%3D&X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Date=20241105T092058Z&X-Amz-SignedHeaders=host&X-Amz-Expires=300&X-Amz-Credential=ASIAQ3PHCVTYUKUJCDYI%2F20241105%2Fus-east-1%2Fs3%2Faws4_request&X-Amz-Signature=15f8e40964e2c750ae15e43aa8e7f7c76eef6a76b792e41434d14bed42b31432&hash=d4a3e49b29443e5eea9e5a44c0dc11b3f30b21addbe6d6d20d523c68db23cd23&host=68042c943591013ac2b2430a89b270f6af2c76d8dfd086a07176afe7c76c2c61&pii=S1532046424001412&tid=spdf-4fbabbd8-becb-4526-98d3-c7517914e457&sid=8ab2a095350fc74edc4b8765ecd8c0260edcgxrqa&type=client&tsoh=d3d3LnNjaWVuY2VkaXJlY3QuY29t&ua=0f165f0b050207505b0151&rr=8ddbc55a0d60a380&cc=us) - [Machine Learning Model Reveals Determinators for Admission to Acute Mental Health Wards From Emergency Department Presentations](https://onlinelibrary.wiley.com/doi/epdf/10.1111/inm.13402) - [Towards Cleaner Cities: Estimating Vehicle-Induced PM2.5 with Hybrid EBM-CMA-ES Modeling](https://www.mdpi.com/2305-6304/12/11/827) +- [Predicting Robotic Hysterectomy Incision Time: Optimizing Surgical Scheduling with Machine Learning](https://pmc.ncbi.nlm.nih.gov/articles/PMC11741200/pdf/e2024.00040.pdf) - [Using machine learning to assist decision making in the assessment of mental health patients presenting to emergency departments](https://journals.sagepub.com/doi/full/10.1177/20552076241287364) - [Proposing an inherently interpretable machine learning model for shear strength prediction of reinforced concrete beams with stirrups](https://pdf.sciencedirectassets.com/287527/1-s2.0-S2214509523X00035/1-s2.0-S2214509524005011/main.pdf?X-Amz-Security-Token=IQoJb3JpZ2luX2VjECUaCXVzLWVhc3QtMSJGMEQCIB0r0KsYBZufOjbCVtUtozwn1QKMdLt2tbbfhuJKjWlXAiB5Dfr7p0yyj%2FSfypTLmjPL8WbjGAB3tRACFjyyqQbbfiq8BQiu%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F8BEAUaDDA1OTAwMzU0Njg2NSIMqBpZ2HmN91c%2BJPqpKpAFZtvqQjCScZa4FN%2FeubsPzOk5c%2B58LliO4Zr%2Bn1pm3vtW4I9I1vA29pkhT5was1N3ccPPIm2jNLwJ%2FHiZej7A2SmFv13Ro3sTvhqG%2F6A9Xx70Nx9jOlDPJUmCypKadKp0FGfuhZQuxeN0b%2F1QUUQZG4RpxC%2FXorRRHmb%2FrXcOWBwu4PmLZAkWmTKpncjDI7oj8eh8yBe6%2FA3JkJ14ZyBgR7JnPzR2ZqMdIhvlKoyMn6EnL1Azq2y3qwEMdzSCvz3wH3sT4pClc2vPs6ruQS4CdT3E7BHrf42Q0VnUXWjuy7gt9iRr0vaWR3tD%2FxyrrEKw7XuMHO9L4rQ4Pfn1dhGZ2J8H5ocwJGSh13U5fY6noyaTNViqvHx1oHNMWL03QpkJxmUxYquBWepcDjxEc32V6eGF7Ecm8Vij3s20wdRNcHqxGFKlUCgph48CKUA79iwSGQCkWQh7bq%2FTtowTbSPud7l8xeG1MvfIVy%2B6yzrjqygvPBQs3qkvdoWUrKXe57bhr2jEkKlSdYyp2TJMD6yoYRdTPyFx5xb0KgIt6KQTPmfbqYXkd3FFz3uc0HmWC5NQz6qP9UzNcBhcK8dXo3Dw042pl0HLO1njFaa%2BBfbT89VUVUIqjrAcmHweIl1v7Eyldzr%2BGBXIlsxPO3gPzyPLF2LTggc6dA%2Bswxmgmkv%2B7n5pU5%2F5sxvEhemb%2Fqu%2B8d47O%2Bn6RH8fL4eLGGL2d0dvFvyE7gEwt%2BaU9HsIN0IHqyH5VmaTF5zaKy%2Fn%2BhkF8yGpe5Hq5yNOUGrfQgfyFn4Kqd%2FTVajxIFzk8DEY%2F%2FFtyGJ%2B8BrHV4P%2FYs8R4XcBzPQtyrTuUC1CGmF01Tc2gnnEo4pVPaIjfBk9B%2BXVMc3Mu4Ywy4L%2BsgY6sgFK3hFIXjIfoVjqrIlBvsGYaFiZB1bVKBVy3DRiBgozzYmIVhipN%2FS%2BPok1oETqvYVvLqEVkGcb5W7nUIK16lFgjwDq6ePuxdqSafgOw5jVQroNsDCPRz8B%2F4fg7kv6gs4R9SX7gCaQ2V7L6NxqJDUUqsCMtIYq05Qx43dGByqLoVEz9USpRBmTLQwpGvOmUaGNNwTsCwmt5gRP8UX3CnkwI%2FydxmhrXLEdaUIFVwJbIor9&X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Date=20240604T221639Z&X-Amz-SignedHeaders=host&X-Amz-Expires=300&X-Amz-Credential=ASIAQ3PHCVTY4E2DAHPF%2F20240604%2Fus-east-1%2Fs3%2Faws4_request&X-Amz-Signature=eece32da8855b55208baecc0ce041e79aa03be1c292b58c67ce0215de36cbdb4&hash=46dd1da122f4cea242c6444a811fb16dde5cb8465e88552ac3eaeee97b975e9b&host=68042c943591013ac2b2430a89b270f6af2c76d8dfd086a07176afe7c76c2c61&pii=S2214509524005011&tid=spdf-45c1c4d1-dd97-4c0d-a04f-c30843a79e78&sid=1fea53ed2d5cf1443e4a7c4-33f4bf6475e1gxrqa&type=client&tsoh=d3d3LnNjaWVuY2VkaXJlY3QuY29t&ua=0f155c5f060d565b01055d&rr=88eb49dd2a5f7688&cc=us) - [A hybrid machine learning approach for predicting fiber-reinforced polymer-concrete interface bond strength](https://download.ssrn.com/eaai/e646e179-ec4a-4987-80b5-8d6bbf43ceda-meca.pdf?response-content-disposition=inline&X-Amz-Security-Token=IQoJb3JpZ2luX2VjEBMaCXVzLWVhc3QtMSJHMEUCIFVH%2Ba5TT2NOEqgCl7GMhXBXBZWE9VzzcRFT6kYXzdxYAiEA4yvXsrzNQnNq%2BkJRB0rw1d2p35f418pIO%2FT3PHKoZ%2BoqxgUI%2B%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2FARAEGgwzMDg0NzUzMDEyNTciDCD0kCrKAqamcwb9LCqaBb4zlqjDhNBhf%2Frbe%2FX3lzSjvS58HiJQtbOHmzaM7putg93e7Wk8nPesoiupTH8uB5ejDC7stGJElRZp5ulT5M6CokoMu82ERn15kMpkgptj3MVEmsY9VTCP%2BCbROJ6v4YcAttOOAEzOc2M6li6o0w4IsF8DNXEIJr%2FJvjB3IDYPkrmpIiHl25h3AzfxPuOF01E2rgucLnY0xTyKGnPBBDZ%2FPtcuqlk2NKun3Q9HbcKj8EPJP%2FPupMW3IQvMnhcdJqqLHXs6wL1P42NTw5vtZO2W5WiEC1CNGDFUTSFRdb9hjhpH4JsYl8X%2BSFT6mZ31K2HTWeuigs5nXp1JN8r8r4O021yiVxHAJ6Chnddr0Z19iM5yOZA4H1EhO1rxxL0VF%2F%2F8Ac3GxuEfkBiug5wuL7aNlBNX6720pYfHH%2FgyrqdU5KSDIp8VYw3KgEij0LkizBHQIoolC48VAEMNc%2F8iWOdZpAVYprhEbABbff8%2BW6c4y1N9vmLTkjZkJtZODpzpQVjrHkL9hAOvmXZocEEN6maRoVJx3DlcTHrfQr8%2BQnPQnmajb5x0FHo44xxBIUt7UB4FOc6beDprle%2F7BO2SNEPLw6rJ9e3WJeVaYch46iqk2tiWFroNHDXlQ73CbzV59AEVtLAR29eIf7uyz%2BU0fOAXG5oAsJyB7YXUjH%2Bh79sxJgBq3%2FoqkEja06CFPRhWeqxixc8y9bEU%2FvvjhfbcWcxGY%2Be%2FwnXbemUbSyr26Y5xvADyicKIMexZNjeHBJ9MKMifQ9oh%2FjmudjxtMLbTpA6EAxMelLjhWcoURF0XeTttMEzEuTjO1OXUwMeXSPZ9roJqH3DB4PHi%2B8UIUG1JoVocv7wDu5ZVlMzgmDr0ti1BShKr9szxagq34jCEkJe8BjqxAbm7bsef33J3AImECx0GZeL0R2tFJZ7ctogL261zP7RqJ4T71rDMbpyfX6HfGuNEbWVROKHUexpuH8FZBodmn%2FjDjZSviK1oxQ1L5TDA2rwMsodnThreIad8vSXqxAzx9qng%2BeN2llXkNdIB7WEnkttzcJ24pZqwYnPI%2FsOznTq%2BDJ88mdNPtzph%2FGdVQcR99tV3waapotTEnUjjoqTTSh9aMgi1jIYMGMrJj6Jb4N%2FhWA%3D%3D&X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Date=20250114T024208Z&X-Amz-SignedHeaders=host&X-Amz-Expires=300&X-Amz-Credential=ASIAUPUUPRWEXKDDLJZE%2F20250114%2Fus-east-1%2Fs3%2Faws4_request&X-Amz-Signature=11c6f325f84736d5324ab155663c94231696de52be8910a03bb5e9c18f0d1689&abstractId=5055231) @@ -715,6 +716,7 @@ We also build on top of many great packages. Please check them out! - [Improving Neural Additive Models with Bayesian Principles](https://arxiv.org/pdf/2305.16905.pdf) - [NODE-GAM: Neural Generalized Additive Model for Interpretable Deep Learning](https://arxiv.org/pdf/2106.01613.pdf) - [Scalable Interpretability via Polynomials](https://arxiv.org/pdf/2205.14108v1.pdf) +- [Polynomial Threshold Functions of Bounded Tree-Width: Some Explainability and Complexity Aspects](https://arxiv.org/pdf/2501.08297) - [Neural Basis Models for Interpretability](https://arxiv.org/pdf/2205.14120.pdf) - [ILMART: Interpretable Ranking with Constrained LambdaMART](https://arxiv.org/pdf/2206.00473.pdf) - [Integrating Co-Clustering and Interpretable Machine Learning for the Prediction of Intravenous Immunoglobulin Resistance in Kawasaki Disease](https://ieeexplore.ieee.org/stamp/stamp.jsp?tp=&arnumber=9097874) diff --git a/build.sh b/build.sh index cf11a0e41..abd231630 100644 --- a/build.sh +++ b/build.sh @@ -343,6 +343,7 @@ if [ $is_conda -eq 1 ]; then ${CXX} -c ${CPPFLAGS} ${CXXFLAGS} ${extras} "$code_path/PartitionOneDimensionalBoosting.cpp" -o "$tmp_path/PartitionOneDimensionalBoosting.o" ${CXX} -c ${CPPFLAGS} ${CXXFLAGS} ${extras} "$code_path/PartitionRandomBoosting.cpp" -o "$tmp_path/PartitionRandomBoosting.o" ${CXX} -c ${CPPFLAGS} ${CXXFLAGS} ${extras} "$code_path/PartitionMultiDimensionalCorner.cpp" -o "$tmp_path/PartitionMultiDimensionalCorner.o" + ${CXX} -c ${CPPFLAGS} ${CXXFLAGS} ${extras} "$code_path/PartitionMultiDimensionalFull.cpp" -o "$tmp_path/PartitionMultiDimensionalFull.o" ${CXX} -c ${CPPFLAGS} ${CXXFLAGS} ${extras} "$code_path/PartitionMultiDimensionalTree.cpp" -o "$tmp_path/PartitionMultiDimensionalTree.o" ${CXX} -c ${CPPFLAGS} ${CXXFLAGS} ${extras} "$code_path/PartitionMultiDimensionalStraight.cpp" -o "$tmp_path/PartitionMultiDimensionalStraight.o" ${CXX} -c ${CPPFLAGS} ${CXXFLAGS} ${extras} "$code_path/Purify.cpp" -o "$tmp_path/Purify.o" @@ -383,6 +384,7 @@ if [ $is_conda -eq 1 ]; then "$tmp_path/PartitionOneDimensionalBoosting.o" \ "$tmp_path/PartitionRandomBoosting.o" \ "$tmp_path/PartitionMultiDimensionalCorner.o" \ + "$tmp_path/PartitionMultiDimensionalFull.o" \ "$tmp_path/PartitionMultiDimensionalTree.o" \ "$tmp_path/PartitionMultiDimensionalStraight.o" \ "$tmp_path/Purify.o" \ diff --git a/docs/benchmarks/ebm-benchmark.ipynb b/docs/benchmarks/ebm-benchmark.ipynb index 6ab7f020e..374255b01 100644 --- a/docs/benchmarks/ebm-benchmark.ipynb +++ b/docs/benchmarks/ebm-benchmark.ipynb @@ -1277,6 +1277,40 @@ "\n", " plt.show()" ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "e2e25dfd-02e7-4e14-9187-fc5c7db3ca89", + "metadata": {}, + "outputs": [], + "source": [ + "import numpy as np\n", + "iterations_df = results_df[results_df['name'] == 'iterations'].copy()\n", + "iterations_df['iterations_array'] = iterations_df['str_val'].apply(lambda x: np.array(eval(x.replace('][', '],[').replace(' ', ','))))\n", + "iterations_df = iterations_df.groupby(['task', 'method', 'meta'])['iterations_array'].apply(lambda x: np.mean(np.stack(x), axis=0)).reset_index()\n", + "iterations_df['iterations_array'] = iterations_df['iterations_array'].apply(lambda x: x.mean(axis=1))\n", + "iterations_overall_df = iterations_df.groupby(['method', 'meta'])['iterations_array'].apply(\n", + " lambda x: pd.Series({\n", + " 'avg_index_0': np.nanmean([arr[0] for arr in x if len(arr) > 0]) if any(len(arr) > 0 for arr in x) else np.nan,\n", + " 'avg_index_1': np.nanmean([arr[1] for arr in x if len(arr) > 1]) if any(len(arr) > 1 for arr in x) else np.nan\n", + " })\n", + ").reset_index()\n", + "iterations_overall_df = iterations_overall_df.pivot_table(columns='level_2', index=['method', 'meta'], values='iterations_array').reset_index()\n", + "print(\"EBM ITERATIONS:\\n\")\n", + "print(iterations_overall_df.to_string(index=False))\n", + "\n", + "iterations_df = iterations_df.groupby(['task', 'method', 'meta'])['iterations_array'].apply(\n", + " lambda x: pd.Series({\n", + " 'avg_index_0': np.nanmean([arr[0] for arr in x if len(arr) > 0]) if any(len(arr) > 0 for arr in x) else np.nan,\n", + " 'avg_index_1': np.nanmean([arr[1] for arr in x if len(arr) > 1]) if any(len(arr) > 1 for arr in x) else np.nan\n", + " })\n", + ").reset_index()\n", + "iterations_df = iterations_df.pivot_table(columns='level_3', index=['task', 'method', 'meta'], values='iterations_array').reset_index()\n", + "iterations_df['ratio'] = iterations_df['avg_index_0'] / iterations_df['avg_index_1']\n", + "print(\"\\nEBM ITERATIONS per dataset:\\n\")\n", + "print(iterations_df.to_string(index=False))" + ] } ], "metadata": { diff --git a/python/interpret-core/interpret/develop.py b/python/interpret-core/interpret/develop.py index 9223623b1..e983f1746 100644 --- a/python/interpret-core/interpret/develop.py +++ b/python/interpret-core/interpret/develop.py @@ -19,6 +19,7 @@ "learning_rate_scale": 1.0, "max_cat_threshold": 9223372036854775807, "cat_include": 1.0, + "full_interaction": False, "boost_corners": False, "purify_boosting": False, "purify_result": False, diff --git a/python/interpret-core/interpret/glassbox/_ebm/_ebm.py b/python/interpret-core/interpret/glassbox/_ebm/_ebm.py index 86a7fd93a..e601bcb5b 100644 --- a/python/interpret-core/interpret/glassbox/_ebm/_ebm.py +++ b/python/interpret-core/interpret/glassbox/_ebm/_ebm.py @@ -951,6 +951,7 @@ def fit(self, X, y, sample_weight=None, bags=None, init_score=None): interactions = 0 monotone_constraints = None n_intercept_rounds = 0 + interaction_flags = Native.CalcInteractionFlags_Default else: noise_scale_boosting = None bin_data_weights = None @@ -977,6 +978,9 @@ def fit(self, X, y, sample_weight=None, bags=None, init_score=None): interactions = self.interactions monotone_constraints = self.monotone_constraints n_intercept_rounds = develop.get_option("n_intercept_rounds_initial") + interaction_flags = Native.CalcInteractionFlags_Default + if develop.get_option("full_interaction"): + interaction_flags |= Native.CalcInteractionFlags_Full exclude_features = set() if monotone_constraints is not None: @@ -1233,7 +1237,7 @@ def fit(self, X, y, sample_weight=None, bags=None, init_score=None): combinations(range(n_features_in), 2), exclude, exclude_features, - Native.CalcInteractionFlags_Default, + interaction_flags, max_cardinality, min_samples_leaf, min_hessian, diff --git a/python/interpret-core/interpret/utils/_measure_interactions.py b/python/interpret-core/interpret/utils/_measure_interactions.py index 897535683..a2c884aad 100644 --- a/python/interpret-core/interpret/utils/_measure_interactions.py +++ b/python/interpret-core/interpret/utils/_measure_interactions.py @@ -250,6 +250,10 @@ def measure_interactions( feature_types_in=feature_types_in, ) + interaction_flags = Native.CalcInteractionFlags_Default + if develop.get_option("full_interaction"): + interaction_flags |= Native.CalcInteractionFlags_Full + if isinstance(interactions, int): n_output_interactions = interactions iter_term_features = combinations(range(n_features_in), 2) @@ -268,7 +272,7 @@ def measure_interactions( iter_term_features=iter_term_features, exclude=set(), exclude_features=set(), - calc_interaction_flags=Native.CalcInteractionFlags_Default, + calc_interaction_flags=interaction_flags, max_cardinality=max_cardinality, min_samples_leaf=min_samples_leaf, min_hessian=min_hessian, diff --git a/python/interpret-core/interpret/utils/_native.py b/python/interpret-core/interpret/utils/_native.py index e9b6304d4..9678f65e8 100644 --- a/python/interpret-core/interpret/utils/_native.py +++ b/python/interpret-core/interpret/utils/_native.py @@ -52,6 +52,7 @@ class Native: CalcInteractionFlags_Default = 0x00000000 CalcInteractionFlags_Purify = 0x00000001 CalcInteractionFlags_DisableNewton = 0x00000002 + CalcInteractionFlags_Full = 0x00000004 # AccelerationFlags AccelerationFlags_NONE = 0x00000000 diff --git a/shared/libebm/CalcInteractionStrength.cpp b/shared/libebm/CalcInteractionStrength.cpp index ce7ff42fc..c6ee49d53 100644 --- a/shared/libebm/CalcInteractionStrength.cpp +++ b/shared/libebm/CalcInteractionStrength.cpp @@ -103,6 +103,15 @@ extern ErrorEbm PartitionMultiDimensionalTree(const bool bHessian, #endif // NDEBUG ); +extern double PartitionMultiDimensionalFull(InteractionCore* const pInteractionCore, + const size_t cTensorBins, + const CalcInteractionFlags flags, + const FloatCalc regAlpha, + const FloatCalc regLambda, + const FloatCalc deltaStepMax, + BinBase* aAuxiliaryBinsBase, + BinBase* const aBinsBase); + // there is a race condition for decrementing this variable, but if a thread loses the // race then it just doesn't get decremented as quickly, which we can live with static int g_cLogCalcInteractionStrength = 10; @@ -476,32 +485,17 @@ EBM_API_BODY ErrorEbm EBM_CALLING_CONVENTION CalcInteractionStrength(Interaction BinBase* aAuxiliaryBins = IndexBin(aMainBins, cBytesPerMainBin * cTensorBins); aAuxiliaryBins->ZeroMem(cBytesPerMainBin, cAuxillaryBins); - TensorTotalsBuild(pInteractionCore->IsHessian(), - cScores, - cDimensions, - binSums.m_acBins, - aAuxiliaryBins, - aMainBins -#ifndef NDEBUG - , - aDebugCopyBins, - pDebugMainBinsEnd -#endif // NDEBUG - ); + LOG_0(Trace_Verbose, "CalcInteractionStrength Starting bin sweep loop"); double bestGain; - if(2 == cDimensions) { - LOG_0(Trace_Verbose, "CalcInteractionStrength Starting bin sweep loop"); - - bestGain = PartitionMultiDimensionalStraight(pInteractionCore, + if(0 != (CalcInteractionFlags_Full & flags)) { + bestGain = PartitionMultiDimensionalFull( + pInteractionCore, cTensorBins, flags, regAlpha, regLambda, deltaStepMax, aAuxiliaryBins, aMainBins); + } else { + TensorTotalsBuild(pInteractionCore->IsHessian(), + cScores, cDimensions, binSums.m_acBins, - flags, - cSamplesLeafMin, - hessianMin, - regAlphaCalc, - regLambdaCalc, - deltaStepMax, aAuxiliaryBins, aMainBins #ifndef NDEBUG @@ -510,168 +504,188 @@ EBM_API_BODY ErrorEbm EBM_CALLING_CONVENTION CalcInteractionStrength(Interaction pDebugMainBinsEnd #endif // NDEBUG ); - } else { - size_t cPossibleSplits; - if(IsOverflowBinSize(true, true, bHessian, cScores)) { - // TODO: move this to init - return Error_OutOfMemory; - } - - if(IsOverflowTreeNodeMultiSize(bHessian, cScores)) { - // TODO: move this to init - return Error_OutOfMemory; - } - cPossibleSplits = 0; - - size_t cBytes = 1; - - size_t* pcBins = binSums.m_acBins; - size_t* pcBinsEnd = binSums.m_acBins + cDimensions; - do { - const size_t cBins = *pcBins; - EBM_ASSERT(size_t{2} <= cBins); - const size_t cSplits = cBins - 1; - if(IsAddError(cPossibleSplits, cSplits)) { + if(2 == cDimensions) { + bestGain = PartitionMultiDimensionalStraight(pInteractionCore, + cDimensions, + binSums.m_acBins, + flags, + cSamplesLeafMin, + hessianMin, + regAlphaCalc, + regLambdaCalc, + deltaStepMax, + aAuxiliaryBins, + aMainBins +#ifndef NDEBUG + , + aDebugCopyBins, + pDebugMainBinsEnd +#endif // NDEBUG + ); + } else { + size_t cPossibleSplits; + if(IsOverflowBinSize(true, true, bHessian, cScores)) { + // TODO: move this to init return Error_OutOfMemory; } - cPossibleSplits += cSplits; - if(IsMultiplyError(cBins, cBytes)) { + + if(IsOverflowTreeNodeMultiSize(bHessian, cScores)) { + // TODO: move this to init return Error_OutOfMemory; } - cBytes *= cBins; - ++pcBins; - } while(pcBinsEnd != pcBins); - - // For pairs, this calculates the exact max number of splits. For higher dimensions - // the max number of splits will be less, but it should be close enough. - // Each bin gets a tree node to record the gradient totals, and each split gets a TreeNode - // during construction. Each split contains a minimum of 1 bin on each side, so we have - // cBins - 1 potential splits. - if(IsAddError(cBytes, cBytes - 1)) { - return Error_OutOfMemory; - } - cBytes = cBytes + cBytes - 1; + cPossibleSplits = 0; - const size_t cBytesTreeNodeMulti = GetTreeNodeMultiSize(bHessian, cScores); + size_t cBytes = 1; - if(IsMultiplyError(cBytesTreeNodeMulti, cBytes)) { - return Error_OutOfMemory; - } - cBytes *= cBytesTreeNodeMulti; - - const size_t cBytesBest = cBytesTreeNodeMulti * (size_t{1} + (cDimensions << 1)); - EBM_ASSERT(cBytesBest <= cBytes); + size_t* pcBins = binSums.m_acBins; + size_t* pcBinsEnd = binSums.m_acBins + cDimensions; + do { + const size_t cBins = *pcBins; + EBM_ASSERT(size_t{2} <= cBins); + const size_t cSplits = cBins - 1; + if(IsAddError(cPossibleSplits, cSplits)) { + return Error_OutOfMemory; + } + cPossibleSplits += cSplits; + if(IsMultiplyError(cBins, cBytes)) { + return Error_OutOfMemory; + } + cBytes *= cBins; + ++pcBins; + } while(pcBinsEnd != pcBins); - // double it because we during the multi-dimensional sweep we need the best and we need the current - if(IsAddError(cBytesBest, cBytesBest)) { - return Error_OutOfMemory; - } - const size_t cBytesSweep = cBytesBest + cBytesBest; + // For pairs, this calculates the exact max number of splits. For higher dimensions + // the max number of splits will be less, but it should be close enough. + // Each bin gets a tree node to record the gradient totals, and each split gets a TreeNode + // during construction. Each split contains a minimum of 1 bin on each side, so we have + // cBins - 1 potential splits. - cBytes = EbmMax(cBytes, cBytesSweep); + if(IsAddError(cBytes, cBytes - 1)) { + return Error_OutOfMemory; + } + cBytes = cBytes + cBytes - 1; - double* aWeights = nullptr; - double* pGradient = nullptr; - double* pHessian = nullptr; - void* pTreeNodesTemp = nullptr; - void* pTemp1 = nullptr; + const size_t cBytesTreeNodeMulti = GetTreeNodeMultiSize(bHessian, cScores); - if(0 != (CalcInteractionFlags_Purify & flags)) { - // allocate the biggest tensor that is possible to split into + if(IsMultiplyError(cBytesTreeNodeMulti, cBytes)) { + return Error_OutOfMemory; + } + cBytes *= cBytesTreeNodeMulti; - // TODO: cache this memory allocation so that we don't do it each time + const size_t cBytesBest = cBytesTreeNodeMulti * (size_t{1} + (cDimensions << 1)); + EBM_ASSERT(cBytesBest <= cBytes); - if(IsAddError(size_t{1}, cScores)) { + // double it because we during the multi-dimensional sweep we need the best and we need the current + if(IsAddError(cBytesBest, cBytesBest)) { return Error_OutOfMemory; } - size_t cItems = 1 + cScores; - const bool bUseLogitBoost = bHessian && !(CalcInteractionFlags_DisableNewton & flags); - if(bUseLogitBoost) { - if(IsAddError(cScores, cItems)) { + const size_t cBytesSweep = cBytesBest + cBytesBest; + + cBytes = EbmMax(cBytes, cBytesSweep); + + double* aWeights = nullptr; + double* pGradient = nullptr; + double* pHessian = nullptr; + void* pTreeNodesTemp = nullptr; + void* pTemp1 = nullptr; + + if(0 != (CalcInteractionFlags_Purify & flags)) { + // allocate the biggest tensor that is possible to split into + + // TODO: cache this memory allocation so that we don't do it each time + + if(IsAddError(size_t{1}, cScores)) { + return Error_OutOfMemory; + } + size_t cItems = 1 + cScores; + const bool bUseLogitBoost = bHessian && !(CalcInteractionFlags_DisableNewton & flags); + if(bUseLogitBoost) { + if(IsAddError(cScores, cItems)) { + return Error_OutOfMemory; + } + cItems += cScores; + } + if(IsMultiplyError(sizeof(double), cItems, cTensorBins)) { return Error_OutOfMemory; } - cItems += cScores; + aWeights = static_cast(malloc(sizeof(double) * cItems * cTensorBins)); + if(nullptr == aWeights) { + return Error_OutOfMemory; + } + pGradient = aWeights + cTensorBins; + if(bUseLogitBoost) { + pHessian = pGradient + cTensorBins * cScores; + } } - if(IsMultiplyError(sizeof(double), cItems, cTensorBins)) { + + pTreeNodesTemp = malloc(cBytes); + if(nullptr == pTreeNodesTemp) { + free(aWeights); return Error_OutOfMemory; } - aWeights = static_cast(malloc(sizeof(double) * cItems * cTensorBins)); - if(nullptr == aWeights) { + + pTemp1 = malloc(cPossibleSplits * sizeof(unsigned char)); + if(nullptr == pTemp1) { + free(pTreeNodesTemp); + free(aWeights); return Error_OutOfMemory; } - pGradient = aWeights + cTensorBins; - if(bUseLogitBoost) { - pHessian = pGradient + cTensorBins * cScores; - } - } - pTreeNodesTemp = malloc(cBytes); - if(nullptr == pTreeNodesTemp) { - free(aWeights); - return Error_OutOfMemory; - } + Tensor* const pInnerTermUpdate = Tensor::Allocate(k_cDimensionsMax, cScores); + if(nullptr == pInnerTermUpdate) { + free(pTemp1); + free(pTreeNodesTemp); + free(aWeights); + return Error_OutOfMemory; + } - pTemp1 = malloc(cPossibleSplits * sizeof(unsigned char)); - if(nullptr == pTemp1) { - free(pTreeNodesTemp); - free(aWeights); - return Error_OutOfMemory; - } + error = PartitionMultiDimensionalTree(bHessian, + cScores, + cDimensions, + cDimensions, + flags, + cSamplesLeafMin, + hessianMin, + regAlpha, + regLambda, + deltaStepMax, + aMainBins, + aAuxiliaryBins, + pInnerTermUpdate, + pTreeNodesTemp, + binSums.m_acBins, + aWeights, + pGradient, + pHessian, + &bestGain, + cPossibleSplits, + pTemp1 +#ifndef NDEBUG + , + aDebugCopyBins, + pDebugMainBinsEnd +#endif // NDEBUG + ); - Tensor* const pInnerTermUpdate = Tensor::Allocate(k_cDimensionsMax, cScores); - if(nullptr == pInnerTermUpdate) { + Tensor::Free(pInnerTermUpdate); free(pTemp1); free(pTreeNodesTemp); free(aWeights); - return Error_OutOfMemory; - } - error = PartitionMultiDimensionalTree(bHessian, - cScores, - cDimensions, - cDimensions, - flags, - cSamplesLeafMin, - hessianMin, - regAlpha, - regLambda, - deltaStepMax, - aMainBins, - aAuxiliaryBins, - pInnerTermUpdate, - pTreeNodesTemp, - binSums.m_acBins, - aWeights, - pGradient, - pHessian, - &bestGain, - cPossibleSplits, - pTemp1 + if(Error_None != error) { #ifndef NDEBUG - , - aDebugCopyBins, - pDebugMainBinsEnd -#endif // NDEBUG - ); - - Tensor::Free(pInnerTermUpdate); - free(pTemp1); - free(pTreeNodesTemp); - free(aWeights); - - if(Error_None != error) { -#ifndef NDEBUG - free(aDebugCopyBins); + free(aDebugCopyBins); #endif // NDEBUG - LOG_0(Trace_Verbose, "Exited BoostMultiDimensional with Error code"); + LOG_0(Trace_Verbose, "Exited BoostMultiDimensional with Error code"); - return error; + return error; + } + EBM_ASSERT(!std::isnan(bestGain)); + EBM_ASSERT(0 == bestGain || std::numeric_limits::min() <= bestGain); } - EBM_ASSERT(!std::isnan(bestGain)); - EBM_ASSERT(0 == bestGain || std::numeric_limits::min() <= bestGain); } #ifndef NDEBUG diff --git a/shared/libebm/PartitionMultiDimensionalFull.cpp b/shared/libebm/PartitionMultiDimensionalFull.cpp new file mode 100644 index 000000000..fc90a3e75 --- /dev/null +++ b/shared/libebm/PartitionMultiDimensionalFull.cpp @@ -0,0 +1,164 @@ +// Copyright (c) 2023 The InterpretML Contributors +// Licensed under the MIT license. +// Author: Paul Koch + +#include "pch.hpp" + +#include // size_t, ptrdiff_t + +#include "logging.h" +#include "unzoned.h" // LIKELY + +#define ZONE_main +#include "zones.h" + +#include "GradientPair.hpp" +#include "Bin.hpp" + +#include "ebm_internal.hpp" +#include "ebm_stats.hpp" +#include "InteractionCore.hpp" + +namespace DEFINED_ZONE_NAME { +#ifndef DEFINED_ZONE_NAME +#error DEFINED_ZONE_NAME must be defined +#endif // DEFINED_ZONE_NAME + +template class PartitionMultiDimensionalFullInternal final { + public: + PartitionMultiDimensionalFullInternal() = delete; // this is a static class. Do not construct + + INLINE_RELEASE_UNTEMPLATED static double Func(InteractionCore* const pInteractionCore, + const size_t cTensorBins, + const CalcInteractionFlags flags, + const FloatCalc regAlpha, + const FloatCalc regLambda, + const FloatCalc deltaStepMax, + BinBase* const aAuxiliaryBinsBase, + BinBase* const aBinsBase) { + auto* const aAuxiliaryBins = + aAuxiliaryBinsBase + ->Specialize(); + auto* const aBins = + aBinsBase->Specialize(); + + const size_t cScores = GET_COUNT_SCORES(cCompilerScores, pInteractionCore->GetCountScores()); + const size_t cBytesPerBin = GetBinSize(true, true, bHessian, cScores); + + Bin totalBin; + static constexpr bool bUseStackMemory = k_dynamicScores != cCompilerScores; + auto* const aTotalGradPair = bUseStackMemory ? totalBin.GetGradientPairs() : aAuxiliaryBins->GetGradientPairs(); + + const bool bUseLogitBoost = bHessian && !(CalcInteractionFlags_DisableNewton & flags); + + totalBin.SetCountSamples(0); + totalBin.SetWeight(0); + for(size_t iScore = 0; iScore < cScores; ++iScore) { + aTotalGradPair[iScore].Zero(); + } + + FloatCalc gain = 0; + + auto* pBin = aBins; + const auto* const pBinsEnd = IndexBin(aBins, cBytesPerBin * cTensorBins); + do { + totalBin.SetCountSamples(totalBin.GetCountSamples() + pBin->GetCountSamples()); + totalBin.SetWeight(totalBin.GetWeight() + pBin->GetWeight()); + FloatCalc hess = static_cast(pBin->GetWeight()); + auto* const aGradPair = pBin->GetGradientPairs(); + for(size_t iScore = 0; iScore < cScores; ++iScore) { + aTotalGradPair[iScore] += aGradPair[iScore]; + const FloatCalc grad = static_cast(aGradPair[iScore].m_sumGradients); + if(bUseLogitBoost) { + hess = static_cast(aGradPair[iScore].GetHess()); + } + gain += CalcPartialGain(grad, hess, regAlpha, regLambda, deltaStepMax); + } + pBin = IndexBin(pBin, cBytesPerBin); + } while(pBinsEnd != pBin); + + FloatCalc hessTotal = static_cast(totalBin.GetWeight()); + for(size_t iScore = 0; iScore < cScores; ++iScore) { + const FloatCalc grad = static_cast(aTotalGradPair[iScore].m_sumGradients); + if(bUseLogitBoost) { + hessTotal = static_cast(aTotalGradPair[iScore].GetHess()); + } + gain -= CalcPartialGain(grad, hessTotal, regAlpha, regLambda, deltaStepMax); + } + + return static_cast(gain); + } +}; + +template class PartitionMultiDimensionalFullTarget final { + public: + PartitionMultiDimensionalFullTarget() = delete; // this is a static class. Do not construct + + INLINE_RELEASE_UNTEMPLATED static double Func(InteractionCore* const pInteractionCore, + const size_t cTensorBins, + const CalcInteractionFlags flags, + const FloatCalc regAlpha, + const FloatCalc regLambda, + const FloatCalc deltaStepMax, + BinBase* aAuxiliaryBinsBase, + BinBase* const aBinsBase) { + if(cPossibleScores == pInteractionCore->GetCountScores()) { + return PartitionMultiDimensionalFullInternal::Func( + pInteractionCore, cTensorBins, flags, regAlpha, regLambda, deltaStepMax, aAuxiliaryBinsBase, aBinsBase); + } else { + return PartitionMultiDimensionalFullTarget::Func( + pInteractionCore, cTensorBins, flags, regAlpha, regLambda, deltaStepMax, aAuxiliaryBinsBase, aBinsBase); + } + } +}; + +template class PartitionMultiDimensionalFullTarget final { + public: + PartitionMultiDimensionalFullTarget() = delete; // this is a static class. Do not construct + + INLINE_RELEASE_UNTEMPLATED static double Func(InteractionCore* const pInteractionCore, + const size_t cTensorBins, + const CalcInteractionFlags flags, + const FloatCalc regAlpha, + const FloatCalc regLambda, + const FloatCalc deltaStepMax, + BinBase* aAuxiliaryBinsBase, + BinBase* const aBinsBase) { + return PartitionMultiDimensionalFullInternal::Func( + pInteractionCore, cTensorBins, flags, regAlpha, regLambda, deltaStepMax, aAuxiliaryBinsBase, aBinsBase); + } +}; + +extern double PartitionMultiDimensionalFull(InteractionCore* const pInteractionCore, + const size_t cTensorBins, + const CalcInteractionFlags flags, + const FloatCalc regAlpha, + const FloatCalc regLambda, + const FloatCalc deltaStepMax, + BinBase* aAuxiliaryBinsBase, + BinBase* const aBinsBase) { + const size_t cRuntimeScores = pInteractionCore->GetCountScores(); + + EBM_ASSERT(1 <= cRuntimeScores); + if(pInteractionCore->IsHessian()) { + if(size_t{1} != cRuntimeScores) { + // muticlass + return PartitionMultiDimensionalFullTarget::Func( + pInteractionCore, cTensorBins, flags, regAlpha, regLambda, deltaStepMax, aAuxiliaryBinsBase, aBinsBase); + } else { + return PartitionMultiDimensionalFullInternal::Func( + pInteractionCore, cTensorBins, flags, regAlpha, regLambda, deltaStepMax, aAuxiliaryBinsBase, aBinsBase); + } + } else { + if(size_t{1} != cRuntimeScores) { + // Odd: gradient multiclass. Allow it, but do not optimize for it + return PartitionMultiDimensionalFullInternal::Func( + pInteractionCore, cTensorBins, flags, regAlpha, regLambda, deltaStepMax, aAuxiliaryBinsBase, aBinsBase); + } else { + return PartitionMultiDimensionalFullInternal::Func( + pInteractionCore, cTensorBins, flags, regAlpha, regLambda, deltaStepMax, aAuxiliaryBinsBase, aBinsBase); + } + } +} + +} // namespace DEFINED_ZONE_NAME diff --git a/shared/libebm/inc/libebm.h b/shared/libebm/inc/libebm.h index 994e089f3..1daac057f 100644 --- a/shared/libebm/inc/libebm.h +++ b/shared/libebm/inc/libebm.h @@ -238,6 +238,7 @@ typedef struct _InteractionHandle { #define CalcInteractionFlags_Default (CALC_INTERACTION_FLAGS_CAST(0x00000000)) #define CalcInteractionFlags_Purify (CALC_INTERACTION_FLAGS_CAST(0x00000001)) #define CalcInteractionFlags_DisableNewton (CALC_INTERACTION_FLAGS_CAST(0x00000002)) +#define CalcInteractionFlags_Full (CALC_INTERACTION_FLAGS_CAST(0x00000004)) #define AccelerationFlags_NONE (ACCELERATION_CAST(0x00000000)) #define AccelerationFlags_Nvidia (ACCELERATION_CAST(0x00000001)) diff --git a/shared/libebm/libebm.vcxproj b/shared/libebm/libebm.vcxproj index 5c6605bf2..50d4522a6 100644 --- a/shared/libebm/libebm.vcxproj +++ b/shared/libebm/libebm.vcxproj @@ -57,6 +57,7 @@ + diff --git a/shared/libebm/libebm.vcxproj.filters b/shared/libebm/libebm.vcxproj.filters index a12813a68..64a661384 100644 --- a/shared/libebm/libebm.vcxproj.filters +++ b/shared/libebm/libebm.vcxproj.filters @@ -51,6 +51,7 @@ + diff --git a/shared/libebm/tests/boosting_unusual_inputs.cpp b/shared/libebm/tests/boosting_unusual_inputs.cpp index fb8dd423f..c8036bc92 100644 --- a/shared/libebm/tests/boosting_unusual_inputs.cpp +++ b/shared/libebm/tests/boosting_unusual_inputs.cpp @@ -2314,8 +2314,9 @@ static double RandomizedTesting(const AccelerationFlags acceleration) { double validationMetricIteration = 0.0; for(size_t iRound = 0; iRound < cRounds; ++iRound) { for(IntEbm iTerm = 0; iTerm < static_cast(terms.size()); ++iTerm) { - const IntEbm cRealBins = features[terms[iTerm][0]].CountRealBins(); - const IntEbm cDimensions = terms[iTerm].size(); + const IntEbm cRealBins = + features[static_cast(terms[static_cast(iTerm)][0])].CountRealBins(); + const IntEbm cDimensions = terms[static_cast(iTerm)].size(); const TermBoostFlags boostFlags = static_cast(ChooseAny(rng, boostFlagsAny) | ChooseFrom(rng, boostFlagsChoose)); @@ -2333,10 +2334,10 @@ static double RandomizedTesting(const AccelerationFlags acceleration) { // we allow 1 cut more than the number of bins to test excessive leaves. const IntEbm cLeaves = 1 + TestRand(rng, cRealBins + 1); - const std::vector leaves(cDimensions, cLeaves); + const std::vector leaves(static_cast(cDimensions), cLeaves); const MonotoneDirection direction = 0 == TestRand(rng, 5) ? static_cast(TestRand(rng, 2) * 2 - 1) : 0; - const std::vector monotonicity(cDimensions, direction); + const std::vector monotonicity(static_cast(cDimensions), direction); validationMetricIteration = test.Boost(iTerm, boostFlags, diff --git a/shared/libebm/tests/interaction_unusual_inputs.cpp b/shared/libebm/tests/interaction_unusual_inputs.cpp index d114e3f20..0895c99b5 100644 --- a/shared/libebm/tests/interaction_unusual_inputs.cpp +++ b/shared/libebm/tests/interaction_unusual_inputs.cpp @@ -543,3 +543,18 @@ TEST_CASE("tweedie, interaction") { double metricReturn = test.TestCalcInteractionStrength({0, 1}, CalcInteractionFlags_DisableNewton); CHECK_APPROX(metricReturn, 1.25); } + +TEST_CASE("Full tensor interaction strength, interaction, regression") { + TestInteraction test1 = TestInteraction(Task_Regression, + {FeatureTest(2), FeatureTest(2)}, + { + TestSample({0, 0}, 2.0), + TestSample({0, 1}, 3.0), + TestSample({1, 0}, 5.0), + TestSample({1, 1}, 7.0), + }); + + double metricReturn = test1.TestCalcInteractionStrength({0, 1}, CalcInteractionFlags_Full); + + CHECK(7.375 == metricReturn); +} diff --git a/shared/libebm/tests/libebm_test.cpp b/shared/libebm/tests/libebm_test.cpp index c7d18e495..91fbdeec2 100644 --- a/shared/libebm/tests/libebm_test.cpp +++ b/shared/libebm/tests/libebm_test.cpp @@ -1025,7 +1025,7 @@ extern IntEbm ChooseAny(std::vector& rng, const std::vector& rng, const std::vector& options) { - return options[TestRand(rng, options.size())]; + return options[static_cast(TestRand(rng, options.size()))]; } extern std::vector MakeRandomDataset(std::vector& rng,