diff --git a/DirectProgramming/DPC++/GraphTraversal/hidden-markov-models/CMakeLists.txt b/DirectProgramming/DPC++/GraphTraversal/hidden-markov-models/CMakeLists.txt new file mode 100644 index 0000000000..07ec9bb778 --- /dev/null +++ b/DirectProgramming/DPC++/GraphTraversal/hidden-markov-models/CMakeLists.txt @@ -0,0 +1,30 @@ +# required cmake version +cmake_minimum_required(VERSION 3.5) + +project (hidden-markov-models) + +if(WIN32) + set(CMAKE_CXX_COMPILER "dpcpp-cl") +else() + set(CMAKE_CXX_COMPILER "dpcpp") +endif() + +# Set default build type to RelWithDebInfo if not specified +if (NOT CMAKE_BUILD_TYPE) + message (STATUS "Default CMAKE_BUILD_TYPE not set using Release with Debug Info") + set (CMAKE_BUILD_TYPE "RelWithDebInfo" CACHE + STRING "Choose the type of build, options are: None Debug Release RelWithDebInfo MinSizeRel" + FORCE) +endif() + +set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -O3 -fsycl -std=c++17") + +set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -lOpenCL -lsycl") + +add_executable (hidden-markov-models src/hidden-markov-models.cpp) + +add_custom_target (run + COMMAND hidden-markov-models + WORKING_DIRECTORY ${CMAKE_PROJECT_DIR} +) + diff --git a/DirectProgramming/DPC++/GraphTraversal/hidden-markov-models/License.txt b/DirectProgramming/DPC++/GraphTraversal/hidden-markov-models/License.txt new file mode 100644 index 0000000000..e63c6e13dc --- /dev/null +++ b/DirectProgramming/DPC++/GraphTraversal/hidden-markov-models/License.txt @@ -0,0 +1,7 @@ +Copyright Intel Corporation + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/DirectProgramming/DPC++/GraphTraversal/hidden-markov-models/README.md b/DirectProgramming/DPC++/GraphTraversal/hidden-markov-models/README.md new file mode 100644 index 0000000000..8a880848c6 --- /dev/null +++ b/DirectProgramming/DPC++/GraphTraversal/hidden-markov-models/README.md @@ -0,0 +1,89 @@ +#`DPC++ Hidden Markov Model` Sample +The HMM (Hidden Markov Model) sample presents a statistical model using a Markov process to present graphable nodes that are otherwise in an unobservable state or “hidden”. This technique is helpful in pattern recognition such as speech, handwriting, gesture recognition, part-of-speech tagging, partial discharges and bioinformatics. The sample offloads the complexity of the Markov process to the GPU. + +The directed edges of this graph are possible transitions beetween nodes or states defined with the following parameters: the number of states is N, the transition matrix A is a square matrix of size N. Each element with indexes (i,j) of this matrix determines the probability to move from the state i to the state j on any step of the Markov process (i and j can be the same if the state does not change on the taken step). + +The main assumption of the HMM is that there are visible observations that depend on the current Markov process. That dependency can be described as a conditional probability distribution (represented by emission matrix). The problem is to find out the most likely chain of the hidden Markov states using the given observations set. + +##Requirements and sample info + +| Optimized for | Description +|:--- |:--- +| OS | Linux* Ubuntu* 18.04, Windows 10 +| Hardware | Skylake with GEN9 or newer, +| Software | Intel® oneAPI DPC++ Compiler (beta) +| What you will learn | Implement Viterbi algorithm to get the most likely path that consists of the hidden states +| Time to complete | 1 minute + +##Purpose + +The sample can use GPU offload to compute sequential steps of multiple graph traversals simultaneously. + +This code sample implements the Viterbi algorithm which is a dynamic programming algorithm for finding the most likely sequence of hidden states—called the Viterbi path—that results in a sequence of observed events, especially in the context of Markov information sources and HMM. + +- Initially, the dataset for algorithm processing is generated: initial states probability distribution Pi, transition matrix A, emission matrix B and the sequence or the observations produced by hidden Markov process. +- First, the matrix of Viterbi values on the first states are initialized using distribution Pi and emission matrix B. The matrix of back pointers is initialized with default values -1. +- Then, for each time step the Viterbi matrix is set to the maximal possible value using A, B and Pi. +- Finally, the state with maximum Viterbi value on the last step is set as a final state of the Viterbi path and the previous nodes of this path are detemined using the correspondent rows of back pointers matrix for each of the steps except the last one. + +Note: The implementation uses logarithms of the probabilities to process small numbers correctly and to replace multiplication operations with addition operations. + +##Key Implementation details + +The basic DPC++ implementation explained in the code includes device selector, buffer, accessor, kernel, and command groups. + +## License +This code sample is licensed under MIT license. + +## Building the `DPC++ Hidden Markov Model` Program for CPU and GPU + +### Include Files +The include folder is located at %ONEAPI_ROOT%\dev-utilities\latest\include on your development system. + +### On a Linux* System +1. Build the program using the following `cmake` commands. + ``` + $ cd hidden-markov-models + $ mkdir build + $ cd build + $ cmake .. + $ make + ``` + +2. Run the program: + ``` + make run + ``` + +3. Clean the program using: + ``` + make clean + ``` + +### On a Windows* System Using a Command Line Interface + * Build the program using VS2017 or VS2019 + Right click on the solution file and open using either VS2017 or VS2019 IDE. + Right click on the project in Solution explorer and select Rebuild. + From top menu select Debug -> Start without Debugging. + + * Build the program using MSBuild + Open "x64 Native Tools Command Prompt for VS2017" or "x64 Native Tools Command Prompt for + VS2019" + Run - MSBuild hidden-markov-models.sln /t:Rebuild /p:Configuration="Release" + +### On a Windows* System Using Visual Studio* Version 2017 or Newer +Perform the following steps: +1. Locate and select the `hidden-markov-models.sln` file. +2. Select the configuration 'Debug' or 'Release'. +3. Select **Project** > **Build** menu option to build the selected configuration. +4. Select **Debug** > **Start Without Debugging** menu option to run the program. + +## Running the Sample +### Application Parameters +There are no editable parameters for this sample. + +### Example of Output +Device: Intel(R) Core(TM) i7-6820HQ CPU @ 2.70GHz Intel(R) OpenCL +The Viterbi path is: +19 18 17 16 15 14 13 12 11 10 +The sample completed successfully! \ No newline at end of file diff --git a/DirectProgramming/DPC++/GraphTraversal/hidden-markov-models/hidden-markov-models.filters b/DirectProgramming/DPC++/GraphTraversal/hidden-markov-models/hidden-markov-models.filters new file mode 100644 index 0000000000..5f08be7fdb --- /dev/null +++ b/DirectProgramming/DPC++/GraphTraversal/hidden-markov-models/hidden-markov-models.filters @@ -0,0 +1,22 @@ + + + + + {4FC737F1-C7A5-4376-A066-2A32D752A2FF} + cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx + + + {93995380-89BD-4b04-88EB-625FBE52EBFB} + h;hh;hpp;hxx;hm;inl;inc;ipp;xsd + + + {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} + rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms + + + + + Source Files + + + \ No newline at end of file diff --git a/DirectProgramming/DPC++/GraphTraversal/hidden-markov-models/hidden-markov-models.sln b/DirectProgramming/DPC++/GraphTraversal/hidden-markov-models/hidden-markov-models.sln new file mode 100644 index 0000000000..10106f9039 --- /dev/null +++ b/DirectProgramming/DPC++/GraphTraversal/hidden-markov-models/hidden-markov-models.sln @@ -0,0 +1,25 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 16 +VisualStudioVersion = 16.0.30320.27 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "hidden-markov-models", "hidden-markov-models.vcxproj", "{46454D0B-76F3-45EB-A186-F315A2E22DEA}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|x64 = Debug|x64 + Release|x64 = Release|x64 + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {46454D0B-76F3-45EB-A186-F315A2E22DEA}.Debug|x64.ActiveCfg = Debug|x64 + {46454D0B-76F3-45EB-A186-F315A2E22DEA}.Debug|x64.Build.0 = Debug|x64 + {46454D0B-76F3-45EB-A186-F315A2E22DEA}.Release|x64.ActiveCfg = Release|x64 + {46454D0B-76F3-45EB-A186-F315A2E22DEA}.Release|x64.Build.0 = Release|x64 + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {B1D84B81-F5D5-4459-AA6E-38B695FB908B} + EndGlobalSection +EndGlobal diff --git a/DirectProgramming/DPC++/GraphTraversal/hidden-markov-models/hidden-markov-models.user b/DirectProgramming/DPC++/GraphTraversal/hidden-markov-models/hidden-markov-models.user new file mode 100644 index 0000000000..fa6ed154c1 --- /dev/null +++ b/DirectProgramming/DPC++/GraphTraversal/hidden-markov-models/hidden-markov-models.user @@ -0,0 +1,9 @@ + + + + WindowsLocalDebugger + + + WindowsLocalDebugger + + \ No newline at end of file diff --git a/DirectProgramming/DPC++/GraphTraversal/hidden-markov-models/hidden-markov-models.vcxproj b/DirectProgramming/DPC++/GraphTraversal/hidden-markov-models/hidden-markov-models.vcxproj new file mode 100644 index 0000000000..e894a8cca6 --- /dev/null +++ b/DirectProgramming/DPC++/GraphTraversal/hidden-markov-models/hidden-markov-models.vcxproj @@ -0,0 +1,144 @@ + + + + + Debug + x64 + + + Release + x64 + + + + + + + 15.0 + {46454d0b-76f3-45eb-a186-f315a2e22dea} + Win32Proj + hidden-markov-models + $(WindowsSDKVersion.Replace("\","")) + hidden-markov-models + + + + Application + true + Intel(R) oneAPI DPC++ Compiler + Unicode + + + Application + false + Intel(R) oneAPI DPC++ Compiler + true + Unicode + + + Application + true + Intel(R) oneAPI DPC++ Compiler + Unicode + + + Application + false + Intel(R) oneAPI DPC++ Compiler + true + Unicode + + + + + + + + + + + + + + + + + + + + + true + + + true + + + false + + + false + + + + + + + + + + Console + true + + + + + + + + + %ONEAPI_ROOT%\dev-utilities\latest\include;%(AdditionalIncludeDirectories) + Disabled + Level3 + + + Console + true + /Od;%(SpecifyDevCmplAdditionalOptions) + + + + + + + + + + + Console + true + true + true + + + + + + + + + %ONEAPI_ROOT%\dev-utilities\latest\include;%(AdditionalIncludeDirectories) + Disabled + Level3 + + + Console + true + true + true + /Od;%(SpecifyDevCmplAdditionalOptions) + + + + + + \ No newline at end of file diff --git a/DirectProgramming/DPC++/GraphTraversal/hidden-markov-models/hidden-markov-models.vcxproj.user b/DirectProgramming/DPC++/GraphTraversal/hidden-markov-models/hidden-markov-models.vcxproj.user new file mode 100644 index 0000000000..e631a72cce --- /dev/null +++ b/DirectProgramming/DPC++/GraphTraversal/hidden-markov-models/hidden-markov-models.vcxproj.user @@ -0,0 +1,17 @@ + + + + cpu + WindowsLocalDebugger + CL_CONFIG_USE_NATIVE_DEBUGGER=1 +SYCL_DEVICE_TYPE=CPU +$(LocalDebuggerEnvironment) + + + cpu + WindowsLocalDebugger + CL_CONFIG_USE_NATIVE_DEBUGGER=1 +SYCL_DEVICE_TYPE=CPU +$(LocalDebuggerEnvironment) + + \ No newline at end of file diff --git a/DirectProgramming/DPC++/GraphTraversal/hidden-markov-models/sample.json b/DirectProgramming/DPC++/GraphTraversal/hidden-markov-models/sample.json new file mode 100644 index 0000000000..6dadf9de3f --- /dev/null +++ b/DirectProgramming/DPC++/GraphTraversal/hidden-markov-models/sample.json @@ -0,0 +1,29 @@ +{ + "guid": "A63E408B-75ED-4379-A6B5-AF013C0EBA58", + "name": "hidden-markov-models", + "categories": [ "Toolkit/Intel® oneAPI Base Toolkit/oneAPI DPC++ Compiler/CPU and GPU" ], + "description": "Bitonic Sort using Intel® oneAPI DPC++ Language", + "toolchain": [ "dpcpp" ], + "targetDevice": [ "CPU", "GPU" ], + "languages": [ { "cpp": {} } ], + "os": [ "linux", "windows" ], + "builder": [ "ide", "cmake" ], + "ciTests": { + "linux": [{ + "steps": [ + "mkdir build", + "cd build", + "cmake ..", + "make", + "make run" + ] + }], + "windows": [{ + "steps": [ + "MSBuild hidden-markov-models.sln /t:Rebuild /p:Configuration=\"Release\"", + "cd x64/Release", + "hidden-markov-models.exe" + ] + }] + } +} diff --git a/DirectProgramming/DPC++/GraphTraversal/hidden-markov-models/src/hidden-markov-models.cpp b/DirectProgramming/DPC++/GraphTraversal/hidden-markov-models/src/hidden-markov-models.cpp new file mode 100644 index 0000000000..6b2e91a8c6 --- /dev/null +++ b/DirectProgramming/DPC++/GraphTraversal/hidden-markov-models/src/hidden-markov-models.cpp @@ -0,0 +1,189 @@ +//============================================================== +// Copyright © Intel Corporation +// +// SPDX-License-Identifier: MIT +// ============================================================= +// +// Hidden Markov Models: this code sample implements the Viterbi algorithm which is a dynamic +// programming algorithm for findingthe most likely sequence of hidden states— +// called the Viterbi path—that results in a sequence of observed events, +// especially in the context of Markov information sources and HMM. +// +// The sample can use GPU offload to compute sequential steps of multiple graph traversals simultaneously. +// +// - Initially, the dataset for algorithm processing is generated : initial states probability +// distribution Pi, transition matrix A, emission matrix Band the sequence or the observations +// produced by hidden Markov process. +// - First, the matrix of Viterbi values on the first states are initialized using distribution Pi +// and emission matrix B.The matrix of back pointers is initialized with default values - 1. +// - Then, for each time step the Viterbi matrix is set to the maximal possible value using A, B and Pi. +// - Finally, the state with maximum Viterbi value on the last step is set as a final state of +// the Viterbi pathand the previous nodes of this path are detemined using the correspondent rows +// of back pointers matrix for each of the steps except the last one. +// +// Note: The implementation uses logarithms of the probabilities to process small numbers correctly +// and to replace multiplication operations with addition operations. + +#include +#include +#include +#include +#include +#include + +// dpc_common.hpp can be found in the dev-utilities include folder. +// e.g., $ONEAPI_ROOT/dev-utilities//include/dpc_common.hpp +#include "dpc_common.hpp" + +using namespace sycl; +using namespace std; + +// Matrix size constants. +// The number of hidden states N. +constexpr int N = 20; +// The number of possible observations M. +constexpr int M = 20; +// The lenght of the hidden states sequence T. +constexpr int T = 20; +// The parameter for generating the sequence. +constexpr int seed = 0; +// Minimal double to initialize logarithms for Viterbi values equal to 0. +constexpr double MIN_DOUBLE = -1.0 * std::numeric_limits::max(); + +bool ViterbiCondition(double x, double y, double z, double compare); + +int main() { + try { + // Initializing and generating initial probabilities for the hidden states. + double(*pi) = new double[N]; + for (int i = 0; i < N; ++i) { + pi[i] = sycl::log10(1.0f / N); + } + buffer pi_buf(pi, N); + + //Device initialization. + queue q(default_selector{}, dpc_common::exception_handler); + cout << "Device: " << q.get_device().get_info() << " " + << q.get_device().get_platform().get_info() << "\n"; + + //Buffers initialization. + buffer viterbi(range<2>(N, T)); + buffer back_pointer(range<2>(N, T)); + buffer a(range<2>(N, N)); + buffer b(range<2>(N, M)); + + // Generating transition matrix A for the Markov process. + q.submit([&](handler& h) { + auto a_acc = a.get_access(h); + h.parallel_for(range<2>(N, N), [=](id<2> index) { + // The sum of the probabilities in each row of the matrix A has to be equal to 1. + double prob = 1.0f / N; + // The algorithm computes logarithms of the probability values to improve small numbers processing. + a_acc[index] = sycl::log10(prob); + }); + }); + + // Generating emission matrix B for the Markov process. + q.submit([&](handler& h) { + auto b_acc = b.get_access(h); + h.parallel_for(range<2>(N, M), [=](id<2> index) { + // The sum of the probabilities in each row of the matrix B has to be equal to 1. + double prob = ((index[0] + index[1]) % M) * 2.0f / M / (M - 1); + // The algorithm computes logarithms of the probability values to improve small numbers processing. + b_acc[index] = (prob == 0.0f) ? MIN_DOUBLE : sycl::log10(prob); + }); + }); + + // Generating the sequence of the observations produced by the hidden Markov chain. + int(*seq) = new int[T]; + for (int i = 0; i < T; ++i) { + seq[i] = (i * i + seed) % M; + } + buffer seq_buf(seq, T); + + // Initialization of the Viterbi matrix and the matrix of back pointers. + q.submit([&](handler& h) { + auto v_acc = viterbi.get_access(h); + auto b_ptr_acc = back_pointer.get_access(h); + auto b_acc = b.get_access(h); + auto pi_acc = pi_buf.get_access(h); + auto seq_acc = seq_buf.get_access(h); + h.parallel_for(range<2>(N, T), [=](id<2> index) { + int i = index[0]; + int j = index[1]; + // At starting point only the first Viterbi values are defined and these Values are substituted + // with logarithms due to the following equation: log(x*y) = log(x) + log(y). + v_acc[index] = (j != 0) ? MIN_DOUBLE : pi_acc[i] + b_acc[i][seq_acc[0]]; + // Default values of all the back pointers are (-1) to show that they are not determined yet. + b_ptr_acc[index] = -1; + }); + }); + delete[] pi; + + // The sequential steps of the Viterbi algorithm that define the Viterbi matrix and the matrix + // of back pointers. The product of the Viterbi values and the probabilities is substituted with the sum of + // the logarithms due to the following equation: log (x*y*z) = log(x) + log(y) + log(z). + for (int j = 0; j < T - 1; ++j) { + q.submit([&](handler& h) { + auto v_acc = viterbi.get_access(h); + auto b_ptr_acc = back_pointer.get_access(h); + auto a_acc = a.get_access (h); + auto b_acc = b.get_access (h); + auto seq_acc = seq_buf.get_access (h); + + h.parallel_for(range<2>(N, N), [=](id<2> index) { + int i = index[0], k = index[1]; + // This conditional block finds the maximum possible Viterbi value on + // the current step j for the state i. + if (ViterbiCondition(v_acc[k][j], b_acc[i][seq_acc[j + 1]], a_acc[k][i], v_acc[i][j + 1])) { + v_acc[i][j + 1] = v_acc[k][j] + a_acc[k][i] + b_acc[i][seq_acc[j + 1]]; + b_ptr_acc[i][j + 1] = k; + } + }); + }); + } + delete[] seq; + + // Getting the Viterbi path based on the matrix of back pointers + buffer vit_path(range<1> {T}); + auto v_acc = viterbi.get_access(); + auto b_ptr_acc = back_pointer.get_access(); + auto vit_path_acc = vit_path.get_access(); + double v_max = MIN_DOUBLE; + // Constructing the Viterbi path. The last state of this path is the one with + // the biggest Viterbi value (the most likely state). + for (int i = 0; i < N; ++i) { + if (v_acc[i][T - 1] > v_max) { + v_max = v_acc[i][T - 1]; + vit_path_acc[T - 1] = i; + } + } + + for (int i = T - 2; i >= 0; --i) { + // Every back pointer starting from the last one contains the index of the previous + // point in Viterbi path. + vit_path_acc[i] = b_ptr_acc[vit_path_acc[i + 1]][i + 1]; + } + + cout << "The Viterbi path is: "<< std::endl; + for (int k = 0; k < T; ++k) { + cout << vit_path_acc[k] << " "; + } + cout << std::endl; + + } catch (sycl::exception const& e) { + // Exception processing + cout << "An exception is caught!\n"; + cout << "Error message:" << e.what(); + terminate(); + } + cout << "The sample completed successfully!" << std::endl; + return 0; +} + +// The method checks if all three components of the sum are not equivalent to logarithm of zero +// (that is incorrect value and is substituted with minimal possible value of double) and that +// the Viterbi value on the new step exceeds the current one. +bool ViterbiCondition(double x, double y, double z, double compare) { + return (x > MIN_DOUBLE) && (y > MIN_DOUBLE) && (z > MIN_DOUBLE) && (x + y + z > compare); +}