/* * Copyright (C) 2018 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #include #include #include #include #include "Multinomial.h" #include "NeuralNetworksWrapper.h" #include "philox_random.h" #include "simple_philox.h" namespace android { namespace nn { namespace wrapper { using ::testing::FloatNear; constexpr int kFixedRandomSeed1 = 37; constexpr int kFixedRandomSeed2 = 42; class MultinomialOpModel { public: MultinomialOpModel(uint32_t batch_size, uint32_t class_size, uint32_t sample_size) : batch_size_(batch_size), class_size_(class_size), sample_size_(sample_size) { std::vector inputs; OperandType logitsType(Type::TENSOR_FLOAT32, {batch_size_, class_size_}); inputs.push_back(model_.addOperand(&logitsType)); OperandType samplesType(Type::INT32, {}); inputs.push_back(model_.addOperand(&samplesType)); OperandType seedsType(Type::TENSOR_INT32, {2}); inputs.push_back(model_.addOperand(&seedsType)); std::vector outputs; OperandType outputType(Type::TENSOR_INT32, {batch_size_, sample_size_}); outputs.push_back(model_.addOperand(&outputType)); model_.addOperation(ANEURALNETWORKS_RANDOM_MULTINOMIAL, inputs, outputs); model_.identifyInputsAndOutputs(inputs, outputs); model_.finish(); } void Invoke() { ASSERT_TRUE(model_.isValid()); Compilation compilation(&model_); compilation.finish(); Execution execution(&compilation); tensorflow::random::PhiloxRandom rng(kFixedRandomSeed1); tensorflow::random::SimplePhilox srng(&rng); const int sample_count = batch_size_ * class_size_; for (int i = 0; i < sample_count; ++i) { input_.push_back(srng.RandDouble()); } ASSERT_EQ(execution.setInput(Multinomial::kInputTensor, input_.data(), sizeof(float) * input_.size()), Result::NO_ERROR); ASSERT_EQ(execution.setInput(Multinomial::kSampleCountParam, &sample_size_, sizeof(sample_size_)), Result::NO_ERROR); std::vector seeds{kFixedRandomSeed1, kFixedRandomSeed2}; ASSERT_EQ(execution.setInput(Multinomial::kRandomSeedsTensor, seeds.data(), sizeof(uint32_t) * seeds.size()), Result::NO_ERROR); output_.insert(output_.end(), batch_size_ * sample_size_, 0); ASSERT_EQ(execution.setOutput(Multinomial::kOutputTensor, output_.data(), sizeof(uint32_t) * output_.size()), Result::NO_ERROR); ASSERT_EQ(execution.compute(), Result::NO_ERROR); } const std::vector& GetInput() const { return input_; } const std::vector& GetOutput() const { return output_; } private: Model model_; const uint32_t batch_size_; const uint32_t class_size_; const uint32_t sample_size_; std::vector input_; std::vector output_; }; TEST(MultinomialOpTest, ProbabilityDeltaWithinTolerance) { constexpr int kBatchSize = 8; constexpr int kNumClasses = 10000; constexpr int kNumSamples = 128; constexpr float kMaxProbabilityDelta = 0.025; MultinomialOpModel multinomial(kBatchSize, kNumClasses, kNumSamples); multinomial.Invoke(); std::vector output = multinomial.GetOutput(); std::vector class_counts; class_counts.resize(kNumClasses); for (auto index : output) { class_counts[index]++; } std::vector input = multinomial.GetInput(); for (int b = 0; b < kBatchSize; ++b) { float probability_sum = 0; const int batch_index = kBatchSize * b; for (int i = 0; i < kNumClasses; ++i) { probability_sum += expf(input[batch_index + i]); } for (int i = 0; i < kNumClasses; ++i) { float probability = static_cast(class_counts[i]) / static_cast(kNumSamples); float probability_expected = expf(input[batch_index + i]) / probability_sum; EXPECT_THAT(probability, FloatNear(probability_expected, kMaxProbabilityDelta)); } } } } // namespace wrapper } // namespace nn } // namespace android