From 855ae59d44e4131b36a58ec9354714c2b73a8c92 Mon Sep 17 00:00:00 2001 From: caoying03 Date: Thu, 3 Aug 2017 18:52:05 +0800 Subject: [PATCH] add KmaxSeqScoreLayer implementation. --- doc/api/v2/config/layer.rst | 5 + paddle/gserver/layers/KmaxSeqScoreLayer.cpp | 115 ++++++++++++++++++ paddle/gserver/tests/test_KmaxSeqScore.cpp | 77 +++++++++++- .../paddle/trainer_config_helpers/layers.py | 24 +++- 4 files changed, 217 insertions(+), 4 deletions(-) create mode 100644 paddle/gserver/layers/KmaxSeqScoreLayer.cpp diff --git a/doc/api/v2/config/layer.rst b/doc/api/v2/config/layer.rst index 372272a53..8b636a9ab 100644 --- a/doc/api/v2/config/layer.rst +++ b/doc/api/v2/config/layer.rst @@ -257,6 +257,11 @@ seq_concat .. autoclass:: paddle.v2.layer.seq_concat :noindex: +kmax_sequence_score +------------------- +.. autoclass:: paddle.v2.layer.kmax_sequence_score + :noindex: + Reshaping Layers ================ diff --git a/paddle/gserver/layers/KmaxSeqScoreLayer.cpp b/paddle/gserver/layers/KmaxSeqScoreLayer.cpp new file mode 100644 index 000000000..d747db9b4 --- /dev/null +++ b/paddle/gserver/layers/KmaxSeqScoreLayer.cpp @@ -0,0 +1,115 @@ +/* Copyright (c) 2016 PaddlePaddle Authors. All Rights Reserve. + +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 "Layer.h" + +namespace paddle { + +class KmaxSeqScoreLayer : public Layer { +private: + MatrixPtr scores_; + size_t beamSize_; + void kmaxScorePerSeq(const real* score, + real* sortedRes, + const ICpuGpuVectorPtr seqStartPos); + +public: + explicit KmaxSeqScoreLayer(const LayerConfig& config) : Layer(config) {} + + bool init(const LayerMap& layerMap, + const ParameterMap& parameterMap) override; + + void forward(PassType passType) override; + void backward(const UpdateCallback& callback = nullptr) override; +}; + +REGISTER_LAYER(kmax_seq_score, KmaxSeqScoreLayer); + +bool KmaxSeqScoreLayer::init(const LayerMap& layerMap, + const ParameterMap& parameterMap) { + bool ret = Layer::init(layerMap, parameterMap); + CHECK_EQ(1UL, inputLayers_.size()); + + beamSize_ = config_.beam_size(); + CHECK_GE(beamSize_, 1LU); + + setNeedSequenceInfo(false); + return ret; +} + +void KmaxSeqScoreLayer::kmaxScorePerSeq(const real* scores, + real* sortedIds, + const ICpuGpuVectorPtr seqStartPos) { + int* starts = seqStartPos->getMutableData(false); + std::vector indices; + for (size_t i = 0; i < seqStartPos->getSize() - 1; ++i) { + int seqLen = starts[i + 1] - starts[i]; + int k = std::min(static_cast(beamSize_), seqLen); + + indices.resize(seqLen, 0); + std::iota(begin(indices), end(indices), 0.); + std::vector tmpScore(scores + starts[i], scores + starts[i + 1]); + std::partial_sort( + begin(indices), + begin(indices) + k, + end(indices), + [&](size_t a, size_t b) { return tmpScore[a] > tmpScore[b]; }); + memcpy(sortedIds + (i * beamSize_), indices.data(), k * sizeof(real)); + } +} + +void KmaxSeqScoreLayer::forward(PassType passType) { + Layer::forward(passType); + + const Argument& input = getInput(0); + const MatrixPtr inputScore = getInputValue(0); + + CHECK(input.hasSeq() || input.hasSubseq()) + << "input of " << getName() + << " must be a sequence or a nested sequence."; + CHECK_EQ(input.value->getWidth(), 1UL) + << "input of " << getName() + << " is score over a sequence or a nested sequence, so its width " + << " must be 1."; + + if (useGpu_) { + // this Layer runs only in CPU, if the model is runing on GPU, + // then copy the input to this layer from GPU to CPU. + Matrix::resizeOrCreate(scores_, + inputScore->getHeight(), + 1, + false /* trans */, + false /* useGpu */); + scores_->copyFrom(*inputScore); + } else { + scores_ = inputScore; + } + + MatrixPtr outputValue = getOutputValue(); + Matrix::resizeOrCreate( + outputValue, + input.hasSubseq() ? input.getNumSubSequences() : input.getNumSequences(), + beamSize_); + outputValue->one(); + outputValue->mulScalar(-1.); + + kmaxScorePerSeq(scores_->getData(), + output_.value->getData(), + input.hasSeq() ? input.subSequenceStartPositions + : input.sequenceStartPositions); +} + +void KmaxSeqScoreLayer::backward(const UpdateCallback& callback) {} + +} // namespace paddle diff --git a/paddle/gserver/tests/test_KmaxSeqScore.cpp b/paddle/gserver/tests/test_KmaxSeqScore.cpp index a8bd5349c..e3530977c 100644 --- a/paddle/gserver/tests/test_KmaxSeqScore.cpp +++ b/paddle/gserver/tests/test_KmaxSeqScore.cpp @@ -13,6 +13,7 @@ See the License for the specific language governing permissions and limitations under the License. */ #include +#include #include #include #include "ModelConfig.pb.h" @@ -30,12 +31,84 @@ DECLARE_bool(use_gpu); DECLARE_int32(gpu_id); DECLARE_bool(thread_local_rand_use_global_seed); +vector randSampling(int range, int n) { + srand(1); + CHECK_GE(range, n); + vector num(range); + iota(begin(num), end(num), 0); + if (range == n) return num; + + random_shuffle(begin(num), end(num)); + num.resize(n); + return num; +} + +void genRandomSeqInfo(vector& seqStartPosition, + vector& subSeqStartPosition) { + const int maxSeqNum = 5; + // generate random start position information + int seqNum = 1 + (rand() % maxSeqNum); + seqStartPosition.resize(seqNum + 1, 0); + subSeqStartPosition.resize(1, 0); + + for (int i = 0; i < seqNum; ++i) { + int subSeqLen = 1 + (rand() % maxSeqNum); + for (int j = 0; j < subSeqLen; ++j) + subSeqStartPosition.push_back(subSeqStartPosition.back() + subSeqLen); + seqStartPosition[i + 1] = subSeqStartPosition.back(); + } +} + +void genRandomGroundTruth(real* values, + vector>& groundTruth, + vector& seqStartPosition, + vector& subSeqStartPosition, + bool useSubseqInfo, + size_t beamSize) { + auto genData = [&](real* values, vector& startPos, size_t beamSize) { + groundTruth.resize(startPos.size() - 1, vector(beamSize, -1)); + + for (size_t i = 0; i < startPos.size() - 1; ++i) { + int seqLen = startPos[i + 1] - startPos[i]; + vector pos = + randSampling(seqLen, min(static_cast(beamSize), seqLen)); + for (size_t j = 0; j < pos.size(); ++j) { + groundTruth[i][j] = pos[j]; + values[subSeqStartPosition[i] + pos[j]] = 1.; + } + } + }; + + if (useSubseqInfo) + genData(values, subSeqStartPosition, beamSize); + else + genData(values, seqStartPosition, beamSize); +} + // Test that the batchNormLayer can be followed by a ConvLayer TEST(Layer, kmaxSeqScoreLayer) { - for (auto hasSubseq : {true, false}) { - for (auto useGpu : {true, false}) { + const size_t beamSize = 5; + + vector seqStartPosition; + vector subSeqStartPosition; + genRandomSeqInfo(seqStartPosition, subSeqStartPosition); + MatrixPtr inValue = + Matrix::create(subSeqStartPosition.back(), 1, false, false); + inValue->randomizeUniform(); + + for (auto hasSubseq : {false, true}) { + vector> groundTruth; + genRandomGroundTruth(inValue->getData(), + groundTruth, + seqStartPosition, + subSeqStartPosition, + hasSubseq, + beamSize); + + for (auto useGpu : {false, true}) { TestConfig config; config.layerConfig.set_type("kmax_seq_score"); + config.layerConfig.set_beam_size(beamSize); config.inputDefs.push_back( {hasSubseq ? INPUT_HASSUB_SEQUENCE_DATA : INPUT_SEQUENCE_DATA, "layer_0", diff --git a/python/paddle/trainer_config_helpers/layers.py b/python/paddle/trainer_config_helpers/layers.py index 62269d37f..085ad8658 100755 --- a/python/paddle/trainer_config_helpers/layers.py +++ b/python/paddle/trainer_config_helpers/layers.py @@ -6112,7 +6112,8 @@ def clip_layer(input, min, max, name=None): :type min: double :param max: The upper threshold for clipping. :type max: double - :return: LayerOutput + :return: LayerOutput object. + :rtype: LayerOutput """ Layer( name=name, @@ -6127,8 +6128,27 @@ def clip_layer(input, min, max, name=None): @wrap_name_default() @layer_support() def kmax_sequence_score_layer(input, name=None, beam_size=1): + """ + This layer accepts one input which is scores over a sequence or a nested + sequence, and returns indices of beam_size sequences with highest scores. + + .. code-block:: python + + kmax_indices = kmax_sequence_score_layer(input=input_layer, beam_size) + + + :param name: The Layer Name. + :type name: basestring + :param input: The input layer. It is scores over a sequence or a nested + sequence and its size must be 1. + :type input: LayerOutput. + :param beam_size: squence indices with top beam_size scores are returned. + :type beam_size: double + :return: LayerOutput object. + :rtype: LayerOutput + """ assert isinstance(input, LayerOutput), ("kmax_sequence_score_layer " - "accept only one input.") + "accepts only one input.") assert input.size == 1, ( "input of kmax_sequence_score_layer is a score" "over a sequence or a nested sequence, so its width must be 1.") -- GitLab